<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾插件推荐</title>
    <link>https://itindex.net/categories/插件</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/categories/插件</link>
    </image>
    <item>
      <title>chrome 插件开发指南（Manifest V3） - 掘金</title>
      <link>https://itindex.net/detail/62714-chrome-%E6%8F%92%E4%BB%B6-%E5%BC%80%E5%8F%91</link>
      <description>&lt;div&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-EKh53" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;一、什么是 Chrome插件&lt;/h1&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-lMvZ7" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;1.1 概述&lt;/h2&gt;    &lt;p&gt;严格来讲，我们正在说的东西应该叫 Chrome 扩展（Chrome Extension）,真正意义上的 Chrome 插件是更底层的浏览器功能扩展，需要对浏览器源码有一定掌握才有能力去开发。鉴于 Chrome 插件的叫法已经习惯，本文中也全部采用这种叫法。在百度指数里也没有收录“chrome 扩展”这个词，只有“chrome 插件”。&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;Chrome 插件是一个用 Web 技术开发、用来增强浏览器功能的软件，它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的压缩包。&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;另外，其实不只是前端技术，Chrome 还可以配合 C++ 编写的 dll 动态链接库实现一些更底层的功能（NPAPI），比如全屏幕截图等。&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;由于安全原因，Chrome 浏览器42以上版本已经陆续不在支持 NPAPI 插件，取而代之的是更安全的 PPAPI。&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-Dd57U" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;1.2 Chrome 插件是如何工作的&lt;/h2&gt;    &lt;p&gt;      &lt;strong&gt;扩展是基于诸如 HTML、 JavaScript 和 CSS 之类的 Web 技术构建的。它们运行在一个独立的沙箱执行环境中，并与 Chrome 浏览器进行交互。&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4310daf9782a49a595395bfd4ea3c034~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;      &lt;br /&gt;从图中可以看出，存在三个进程：      &lt;strong&gt;扩展进程&lt;/strong&gt;（Extension Process）、      &lt;strong&gt;页面渲染进程&lt;/strong&gt;（Render Process）、      &lt;strong&gt;浏览器进程&lt;/strong&gt;（Browser Process）。      &lt;br /&gt;1）扩展进程中运行Extension Page，Extension Page主要包括backgrount.html和popup.html：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;backgrount.html中没有任何内容，是通过background.js创建生成，当浏览器打开时，会自动加载插件的background.js文件，它独立于网页并且一直运行在后台，它主要通过调用浏览器提供的API和浏览器进行交互；&lt;/li&gt;      &lt;li&gt;popup.html则不同，它有内容，是一个实实在在的页面，和我们普通的web页面一样，由html、css、Javascript组成，它是按需加载的，需要用户去点击地址栏的按钮去触发，才能弹出页面。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;2）渲染进程主要运行Web Page,当打开页面时，会将content_script.js加载并注入到该网页的环境中，它和网页中引入的Javascript一样，可以操作该网页的DOM Tree，改变页面的展示效果；      &lt;br /&gt;3）浏览器进程在这里更多起到桥梁作用，作为中转可以实现Extension Page和content_script.js之间的消息通信。      &lt;a href="https://link.juejin.cn?target=" name="user-content-TwGZV" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;1.3 延伸阅读：插件和扩展的区别&lt;/h2&gt;    &lt;blockquote&gt;      &lt;p&gt;扩展（Extension）指的是通过调用 Chrome 提供的 Chrome API 来扩展浏览器功能的一种组件，工作在浏览器层面，使用 HTML + Javascript 语言开发。比如著名的 AdBlock plus。&lt;/p&gt;&lt;/blockquote&gt;    &lt;blockquote&gt;      &lt;p&gt;插件（Plug-in）指的是通过调用 Webkit 内核 NPAPI 来扩展内核功能的一种组件，工作在内核层面，理论上可以用任何一种生成本地二进制程序的语言开发，比如C/C++、Delphi等。比如Flash player插件，就属于这种类型。一般在网页中 object 或者 embed 标签声明的部分，就要靠插件来渲染。&lt;/p&gt;&lt;/blockquote&gt;    &lt;blockquote&gt;      &lt;p&gt;从安全性上来看，由于插件一般实现的都是比较底层的功能，所以一旦出现问题，往往就会牵涉到整个操作系统，像 Flash 就属于经常被扒出高危漏洞的那一类。相比较之下，扩展出现问题，其危害性往往类似于浏览器漏洞。不过 Chrome Extension 在为用户带来便利的同时，也的确带来了不少安全问题，即便是在 Chrome 应用商店中的应用也不能保证绝对安全，Google 自己也下线过一些有安全隐患的扩展。&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-W5nLb" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;二、Chrome 插件能做什么&lt;/h1&gt;    &lt;p&gt;      &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e94cae66b886471a83152e3c96e5d4b4~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;      &lt;br /&gt;扩展允许你通过使用 API 修改浏览器行为和访问网页内容来“扩展”浏览器。      &lt;br /&gt;扩展 API 允许扩展的代码访问浏览器本身的特性: 激活选项卡、修改网络请求等等。&lt;/p&gt;    &lt;p&gt;插件能力概述：&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;&lt;/th&gt;        &lt;th&gt;&lt;/th&gt;        &lt;th&gt;API&lt;/th&gt;        &lt;th&gt;备注&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;自定义扩展用户界面&lt;/td&gt;        &lt;td&gt;控制一个扩展的显示的图标&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Faction%2F" target="_blank" title=""&gt;Action&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;添加触发操作的键盘快捷键&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fcommands%2F" target="_blank" title=""&gt;Commands&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;添加页面右键菜单&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FcontextMenus%2F" target="_blank" title=""&gt;Context Menus&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;向地址栏添加关键字功能&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fomnibox%2F" target="_blank" title=""&gt;Omnibox&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;创建新标签卡、书签页或历史记录页&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Foverride%2F" target="_blank" title=""&gt;Override Pages&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;在工具栏中动态显示图标。&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FpageAction%2F" target="_blank" title=""&gt;Page Actions&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;构建扩展工具&lt;/td&gt;        &lt;td&gt;无障碍扩展服务&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fa11y%2F" target="_blank" title=""&gt;Accessibility (a11y)&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;有趣的事情发生时，做出检测和反应&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fmigrating_to_service_workers%2F" target="_blank" title=""&gt;Service Workers&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;使用语言和语言环境&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fi18n%2F" target="_blank" title=""&gt;Internationalization&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;获得OAuth2访问令牌&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fidentity%2F" target="_blank" title=""&gt;Identity&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;管理已安装和正在运行的扩展插件&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fmanagement%2F" target="_blank" title=""&gt;Management&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;通过 Content Script 与其父扩展进行通信&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fmessaging%2F" target="_blank" title=""&gt;Message Passing&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;让用户自定义扩展&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Foptions%2F" target="_blank" title=""&gt;Options Pages&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;修改一个扩展的权限&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fpermissions%2F" target="_blank" title=""&gt;Permissions&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;存储和检索数据&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fstorage%2F" target="_blank" title=""&gt;Storage&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;修改和监听 Chrome 浏览器&lt;/td&gt;        &lt;td&gt;创建、组织和操作书签行为&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fbookmarks%2F" target="_blank" title=""&gt;Bookmarks&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;从用户的本地配置文件中删除浏览数据&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FbrowsingData%2F" target="_blank" title=""&gt;Browsing Data&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;以编程方式启动、监视、操作和搜索下载&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fdownloads%2F" target="_blank" title=""&gt;Downloads&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;管理 Chrome 的字体设置&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FfontSettings%2F" target="_blank" title=""&gt;Font Settings&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;与浏览器访问页面的记录交互&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fhistory%2F" target="_blank" title=""&gt;History&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;控制 Chrome 的隐私特性&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fprivacy%2F" target="_blank" title=""&gt;Privacy&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;管理 Chrome 的代理设置&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fproxy%2F" target="_blank" title=""&gt;Proxy&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;从浏览会话查询和还原选项卡和窗口&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fsessions%2F" target="_blank" title=""&gt;Sessions&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;在浏览器中创建、修改和重新排列选项卡&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Ftabs%2F" target="_blank" title=""&gt;Tabs&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;访问用户访问次数最多的 URL&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FtopSites%2F" target="_blank" title=""&gt;Top Sites&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;更改浏览器的整体外观&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fthemes%2F" target="_blank" title=""&gt;Themes&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;在浏览器中创建、修改和重新排列窗口&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fwindows%2F" target="_blank" title=""&gt;Windows&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;修改和监听网页&lt;/td&gt;        &lt;td&gt;扩展临时访问当前活动选项卡的权限&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fmanifest%2FactiveTab%2F" target="_blank" title=""&gt;Active Tab&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;自定义网站特性，如 cookies、 JavaScript 和插件&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FcontentSettings%2F" target="_blank" title=""&gt;Content Settings&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;在网页上下文中运行 JavaScript 代码&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fcontent_scripts%2F" target="_blank" title=""&gt;Content Scripts&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;浏览和修改浏览器的 Cookie 系统&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fcookies%2F" target="_blank" title=""&gt;Cookies&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;使用 XMLHttpRequest 从远程服务器发送和接收数据&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fxhr%2F" target="_blank" title=""&gt;Cross-Origin XHR&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;在不需要许可的情况下对页面内容执行操作&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FdeclarativeContent%2F" target="_blank" title=""&gt;Declarative Content&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;捕获屏幕、单个窗口或选项卡的内容&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FdesktopCapture%2F" target="_blank" title=""&gt;Desktop Capture&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;将选项卡的源信息保存为 MHTML&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FpageCapture%2F" target="_blank" title=""&gt;Page Capture&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;与标签页互动媒体流交互&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FtabCapture%2F" target="_blank" title=""&gt;Tab Capture&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;接收 in-flight 导航请求状态的通知&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FwebNavigation%2F" target="_blank" title=""&gt;Web Navigation&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;提供规则告诉 Chrome 如何拦截、阻止或修改 in-flight 的请求。&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2FdeclarativeNetRequest%2F" target="_blank" title=""&gt;Declarative Net Request&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;打包、部署和更新&lt;/td&gt;        &lt;td&gt;使用 Chrome Web Store 托管和更新扩展&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fhosting%2F" target="_blank" title=""&gt;Chrome Web Store&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;在指定的网络或其他软件上分发扩展&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fexternal_extensions%2F" target="_blank" title=""&gt;Other Deployment Options&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;扩展 Chrome DevTools&lt;/td&gt;        &lt;td&gt;测试网络交互，调试 JavaScript，修改 DOM 和 CSS&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fdebugger%2F" target="_blank" title=""&gt;Debugger&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;&lt;/td&gt;        &lt;td&gt;为 Chrome 开发工具添加功能&lt;/td&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fdevtools%2F" target="_blank" title=""&gt;Devtools&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;几个插件例子：&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;          &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ebe1bc181cb04ad59c537dfa1c5bda0d~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/th&gt;        &lt;th&gt;          &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/897dd5e6d312405a962d6332f363b88a~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/th&gt;        &lt;th&gt;          &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/10f61779223544b1a7152ad3b0163f08~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/th&gt;        &lt;th&gt;          &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4cb545f6cce347f5b4d1a33fa27027b2~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;Google 翻译&lt;/td&gt;        &lt;td&gt;Adblock Plus&lt;/td&gt;        &lt;td&gt;Artemis and Britomartis&lt;/td&gt;        &lt;td&gt;Octotree - GitHub code tree)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;一句话总结：      &lt;strong&gt;Chrome扩展插件是用前端的技术栈，来定制浏览器的功能，改善用户体验&lt;/strong&gt;。      &lt;a href="https://link.juejin.cn?target=" name="user-content-KEtrs" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;三、主要构成&lt;/h1&gt;    &lt;p&gt;      &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fe429775053f46a487db3e1d1c15a8f5~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;一个chrome插件通常由3类文件组成：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;配置文件 manifest.json&lt;/li&gt;      &lt;li&gt;图片、css等资源文件&lt;/li&gt;      &lt;li&gt;js脚本文件，包括popup.js、background.js、content_script.js等        &lt;a href="https://link.juejin.cn?target=" name="user-content-bLg1e" title=""&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;h2&gt;3.1 Manifest.json&lt;/h2&gt;    &lt;p&gt;每一个扩展都有一个json格式的清单文件，用于配置扩展的名称、版本号、图标、权限、脚本路径等信息；      &lt;br /&gt;文件内容如下所示：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{&amp;quot;manifest_version&amp;quot;:3,&amp;quot;name&amp;quot;:&amp;quot;MStars&amp;quot;,&amp;quot;description&amp;quot;:&amp;quot;A chrome extension for sgfe&amp;quot;,&amp;quot;options_page&amp;quot;:&amp;quot;options.html&amp;quot;,&amp;quot;background&amp;quot;:{&amp;quot;service_worker&amp;quot;:&amp;quot;background.bundle.js&amp;quot;},&amp;quot;action&amp;quot;:{&amp;quot;default_popup&amp;quot;:&amp;quot;popup.html&amp;quot;,&amp;quot;default_icon&amp;quot;:&amp;quot;master-34.png&amp;quot;},&amp;quot;chrome_url_overrides&amp;quot;:{&amp;quot;newtab&amp;quot;:&amp;quot;newtab.html&amp;quot;},&amp;quot;icons&amp;quot;:{&amp;quot;128&amp;quot;:&amp;quot;master.png&amp;quot;},&amp;quot;content_scripts&amp;quot;:[{&amp;quot;matches&amp;quot;:[&amp;quot;http://*/*&amp;quot;,&amp;quot;https://*/*&amp;quot;,&amp;quot;&amp;lt;all_urls&amp;gt;&amp;quot;],&amp;quot;js&amp;quot;:[&amp;quot;contentScript.bundle.js&amp;quot;],&amp;quot;css&amp;quot;:[&amp;quot;content.styles.css&amp;quot;]}],&amp;quot;web_accessible_resources&amp;quot;:[{&amp;quot;resources&amp;quot;:[&amp;quot;injectScript.bundle.js&amp;quot;,&amp;quot;content.styles.css&amp;quot;,&amp;quot;master.png&amp;quot;,&amp;quot;master-34.png&amp;quot;,&amp;quot;vs/*&amp;quot;],&amp;quot;matches&amp;quot;:[&amp;quot;http://*/*&amp;quot;,&amp;quot;https://*/*&amp;quot;,&amp;quot;&amp;lt;all_urls&amp;gt;&amp;quot;]}],&amp;quot;permissions&amp;quot;:[&amp;quot;webRequest&amp;quot;,&amp;quot;storage&amp;quot;,&amp;quot;contextMenus&amp;quot;,&amp;quot;bookmarks&amp;quot;],&amp;quot;host_permissions&amp;quot;:[&amp;quot;&amp;lt;all_urls&amp;gt;&amp;quot;],&amp;quot;content_security_policy&amp;quot;:{&amp;quot;extension_pages&amp;quot;:&amp;quot;script-src &amp;apos;self&amp;apos;;object-src &amp;apos;none&amp;apos;&amp;quot;}}复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-Rm44n" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.1.1 Manifest V2&lt;/h3&gt;    &lt;p&gt;自2022年1月17日起，Chrome 应用商店已经停止接受新的 Manifest V2扩展。      &lt;br /&gt;2023年6月 Chrome 115开始，关闭对 Manifest V2扩展的支持。      &lt;br /&gt;2024年1月，Chrome 应用商店将删除所有的 Manifest V2扩展。      &lt;br /&gt;      &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/504e87f955c442649f823966ad6345ca~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;      &lt;br /&gt;（Manifest V2 support timeline）      &lt;a href="https://link.juejin.cn?target=" name="user-content-NTi40" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.1.2 Manifest V3&lt;/h3&gt;    &lt;p&gt;2020年年底推出V3版本，在安全性、隐私性和性能方面得到了增强；还可以使用更现代的开放 Web 技术，比如      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fmigrating_to_service_workers%2F" target="_blank" title=""&gt;service works&lt;/a&gt;和      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fpromises%2F" target="_blank" title=""&gt;promises&lt;/a&gt;。&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;Manifest V3 is available beginning with Chrome 88, and the Chrome Web Store begins accepting Manifest V3 extensions in January 2021.&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;详细文档：      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fmanifest%2F" target="_blank" title=""&gt;developer.chrome.com/docs/extens…&lt;/a&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-aayes" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;3.2 popup&lt;/h2&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-tJQoa" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.2.1 概述&lt;/h3&gt;    &lt;p&gt;popup 是点击插件图标时打开的一个页面，点击 popup 之外的区域会收起，一般用来做一些临时性的交互。      &lt;br /&gt;      &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f1fa7bf310f246e0b10abd4deb84b47f~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;      &lt;br /&gt;（Web Vitals）      &lt;a href="https://link.juejin.cn?target=" name="user-content-eGm3k" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.2.2 简单例子&lt;/h3&gt;    &lt;p&gt;1、 创建 manifest.json 文件：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{&amp;quot;manifest_version&amp;quot;:3,&amp;quot;name&amp;quot;:&amp;quot;Hello Extensions&amp;quot;,&amp;quot;description&amp;quot;:&amp;quot;Base Level Extension&amp;quot;,&amp;quot;version&amp;quot;:&amp;quot;1.0&amp;quot;,&amp;quot;action&amp;quot;:{&amp;quot;default_popup&amp;quot;:&amp;quot;hello.html&amp;quot;,&amp;quot;default_icon&amp;quot;:&amp;quot;hello_extensions.png&amp;quot;}}复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;2、创建 html 文件：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Hello Extensions&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;ol start="3"&gt;      &lt;li&gt;点击 action 后，就会看到：        &lt;br /&gt;        &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ce2d3d9f9b0b408ab404241e03ea7e6e~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;        &lt;a href="https://link.juejin.cn?target=" name="user-content-J81hb" title=""&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;h2&gt;3.3 content-scripts&lt;/h2&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-ATaOb" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.3.1 概述&lt;/h3&gt;    &lt;p&gt;content-scripts 是在网页上下文中运行的文件。通过使用标准的文档对象模型(Document Object Model，DOM) ，它们能够读取浏览器访问的网页的详细信息，对它们进行更改。&lt;/p&gt;    &lt;p&gt;如主题、样式、布局定制、广告拦截等等。&lt;/p&gt;    &lt;p&gt;大部分浏览器插件都在围绕 content-scripts 做一些事情，background、popup、options等都为之服务。      &lt;br /&gt;配置示例：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;&amp;quot;content_scripts&amp;quot;:[{&amp;quot;matches&amp;quot;:[&amp;quot;http://*/*&amp;quot;,&amp;quot;https://*/*&amp;quot;,&amp;quot;&amp;lt;all_urls&amp;gt;&amp;quot;],&amp;quot;js&amp;quot;:[&amp;quot;contentScript.bundle.js&amp;quot;],&amp;quot;css&amp;quot;:[&amp;quot;content.styles.css&amp;quot;]}],复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-wdwFG" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.3.2 特性&lt;/h3&gt;    &lt;p&gt;1）和原始页面共享 DOM，但是不能共享 JS，不能访问页面中的 JS（比如变量）。      &lt;br /&gt;2）网络请求受到同源策略限制。      &lt;br /&gt;3）只能访问以下 Chrome API：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fi18n%2F" target="_blank" title=""&gt;i18n&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fstorage%2F" target="_blank" title=""&gt;storage&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%2F" target="_blank" title=""&gt;runtime&lt;/a&gt;:        &lt;ul&gt;          &lt;li&gt;            &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23method-connect" target="_blank" title=""&gt;connect&lt;/a&gt;&lt;/li&gt;          &lt;li&gt;            &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23method-getManifest" target="_blank" title=""&gt;getManifest&lt;/a&gt;&lt;/li&gt;          &lt;li&gt;            &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23method-getURL" target="_blank" title=""&gt;getURL&lt;/a&gt;&lt;/li&gt;          &lt;li&gt;            &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23property-id" target="_blank" title=""&gt;id&lt;/a&gt;&lt;/li&gt;          &lt;li&gt;            &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23event-onConnect" target="_blank" title=""&gt;onConnect&lt;/a&gt;&lt;/li&gt;          &lt;li&gt;            &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23event-onMessage" target="_blank" title=""&gt;onMessage&lt;/a&gt;&lt;/li&gt;          &lt;li&gt;            &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23method-sendMessage" target="_blank" title=""&gt;sendMessage&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;其他 API 都不能直接访问，但是可以通过通信让 background 来进行调用。      &lt;a href="https://link.juejin.cn?target=" name="user-content-utfXS" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.3.3 简单例子&lt;/h3&gt;    &lt;p&gt;在页面中增加一个按钮。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;functioninJectBtn() {if(document.querySelector(&amp;apos;#openInIDE&amp;apos;))return;document.querySelector(&amp;apos;.repo-detail-base-info .btn-box&amp;apos;)
    .insertAdjacentHTML(&amp;apos;beforeend&amp;apos;,&amp;apos;&amp;lt;button id=&amp;quot;openInIDE&amp;quot; type=&amp;quot;button&amp;quot; class=&amp;quot;mtd-btn mtd-btn-warning&amp;quot;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;div class=&amp;quot;mtd-button-content&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;mtdicon mtdicon-link-o&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;Open In WebIDE&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&amp;apos;);document.querySelector(&amp;apos;#openInIDE&amp;apos;).addEventListener(&amp;apos;click&amp;apos;,() =&amp;gt;{window.open(&amp;apos;https://xxx.com/&amp;apos;);
  });
}functioninit() {varheartBeat =setInterval(() =&amp;gt;{varwantedEl =document.querySelector(&amp;apos;.repo-detail-base-info .btn-box&amp;apos;);if(wantedEl) {clearInterval(heartBeat);inJectBtn();
    }
  },1000);
}init();复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-lic0u" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.3.4 延伸阅读：如何访问页面中的 JS&lt;/h3&gt;    &lt;blockquote&gt;      &lt;p&gt;content-scripts 不能访问页面中的 js，它可以操作 DOM，但是 DOM 却不能调用它，所以就有了通过 DOM 操作的方式向页面动态注入 JS 的操作。&lt;/p&gt;&lt;/blockquote&gt;    &lt;pre&gt;      &lt;code&gt;functioninjectJs(jsPath) {
  jsPath = jsPath ||&amp;apos;injectScript.bundle.js&amp;apos;;vartemp =document.createElement(&amp;apos;script&amp;apos;);
  temp.setAttribute(&amp;apos;type&amp;apos;,&amp;apos;text/javascript&amp;apos;);// chrome-extension://mapfodeofmlldcgdgahpjiefememgeei/injectScript.bundle.jstemp.src= chrome.runtime.getURL(jsPath);
  temp.onload=function() {// 放在页面不好看，执行完后移除掉this.parentNode?.removeChild(this);
  };document.head.appendChild(temp);
}复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;injected 的内容需要在资源列表中进行声明：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;&amp;quot;web_accessible_resources&amp;quot;:[{&amp;quot;resources&amp;quot;:[&amp;quot;injectScript.bundle.js&amp;quot;,],&amp;quot;matches&amp;quot;:[&amp;quot;http://*/*&amp;quot;,&amp;quot;https://*/*&amp;quot;,&amp;quot;&amp;lt;all_urls&amp;gt;&amp;quot;]}]复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;例子：拦截 xhr 请求。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// &amp;quot;injectScript.bundle.js&amp;quot;xhook.after(function(request, response) {if(request.url.match(/rest\/api.+files.+/)) {console.log(response?.data)
  }
});复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-biHQT" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;3.4 background&lt;/h2&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-qM82E" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.4.1 概述&lt;/h3&gt;    &lt;p&gt;插件是基于事件的用来修改或增强 Chrome 浏览体验的程序。事件是浏览器触发的，例如导航到新页、删除书签或关闭选项卡。插件在 background 中监视这些事件，然后根据指定的指令进行响应。      &lt;br /&gt;background 一旦加载完成，只要执行某个操作（比如发起网络请求或调用 Chrome API）就会一直运行，此外，在关闭所有可见视图和所有消息端口之前，不会被卸载。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;总而言之，background 为所有的视图和消息端口服务。&lt;/strong&gt;&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;background 在需要的时候被加载，空闲的时候被卸载。一些事件的例子包括:&lt;/p&gt;      &lt;ul&gt;        &lt;li&gt;插件首次安装或者更新版本；&lt;/li&gt;        &lt;li&gt;backgroud 正在监听一些事件，这些事件被触发；&lt;/li&gt;        &lt;li&gt;content script 或者其他扩展发送消息；&lt;/li&gt;        &lt;li&gt;插件中的其他视图调用          &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23method-getBackgroundPage" target="_blank" title=""&gt;runtime.getBackgroundPage&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-yAMcn" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;3.4.2 简单例子&lt;/h3&gt;    &lt;p&gt;创建一个右键菜单。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;functioncreateContextMenus() {
  chrome.contextMenus.create({type:&amp;apos;normal&amp;apos;,id:&amp;apos;savePage&amp;apos;,title:&amp;apos;保存页面&amp;apos;,checked:false,
  });
}
chrome.runtime.onInstalled.addListener(() =&amp;gt;{createContextMenus();
});复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-m17Dq" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;四、其他展现形式&lt;/h1&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-U2IC9" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;4.1 homepage_url&lt;/h2&gt;    &lt;p&gt;      &lt;strong&gt;插件主页，免费广告位！&lt;/strong&gt;      &lt;br /&gt;      &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d2e98eab27e94da5ba6c31c679e0f24c~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{&amp;quot;homepage_url&amp;quot;:&amp;quot;https://km.xxxx.com&amp;quot;, }复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-ow0L1" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;4.2 Options页面&lt;/h2&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-dEugk" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;4.2.1 概述&lt;/h3&gt;    &lt;p&gt;插件的配置页面。      &lt;a href="https://link.juejin.cn?target=" name="user-content-P3z2V" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;4.2.2 配置&lt;/h3&gt;    &lt;p&gt;有两种配置方法，对应两种展现形式。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{// 方式1&amp;quot;options_page&amp;quot;:&amp;quot;options.html&amp;quot;,// 方式2 优先级高&amp;quot;options_ui&amp;quot;:{&amp;quot;page&amp;quot;:&amp;quot;options.html&amp;quot;},}复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;          &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/900b23ab7f864519b005a7666bf7ae9b~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/th&gt;        &lt;th&gt;          &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3a454cce32b142ec9753d4d486559133~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-e54oO" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;4.3 Devtools&lt;/h2&gt;    &lt;p&gt;开发者工具。&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;          &lt;img alt="devtools1.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/257a82315778494d96f9cdc9f293c2f9~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?"&gt;&lt;/img&gt;&lt;/th&gt;        &lt;th&gt;          &lt;img alt="devtools2.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a142ee098df54ae184426ac7536dc97f~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?"&gt;&lt;/img&gt;&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;          &lt;a href="https://link.juejin.cn?target=" name="user-content-X2ogX" title=""&gt;&lt;/a&gt;&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;h2&gt;4.4 override（覆盖特定页面）&lt;/h2&gt;    &lt;p&gt;可以将 Chrome 默认的一些特定的页面改为使用扩展提供的页面：&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;页面名称&lt;/th&gt;        &lt;th&gt;url&lt;/th&gt;        &lt;th&gt;配置&lt;/th&gt;        &lt;th&gt;备注&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;新标签页&lt;/td&gt;        &lt;td&gt;chrome://newtab&lt;/td&gt;        &lt;td&gt;&amp;quot;chrome_url_overrides&amp;quot;:          &lt;br /&gt;{          &lt;br /&gt;&amp;quot;newtab&amp;quot;: &amp;quot;newtab.html&amp;quot;          &lt;br /&gt;}&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;历史记录&lt;/td&gt;        &lt;td&gt;chrome://history&lt;/td&gt;        &lt;td&gt;&amp;quot;chrome_url_overrides&amp;quot;:          &lt;br /&gt;{          &lt;br /&gt;&amp;quot;history&amp;quot;: &amp;quot;history.html&amp;quot;          &lt;br /&gt;}&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;书签&lt;/td&gt;        &lt;td&gt;chrome://bookmarks&lt;/td&gt;        &lt;td&gt;&amp;quot;chrome_url_overrides&amp;quot;:          &lt;br /&gt;{          &lt;br /&gt;&amp;quot;bookmarks&amp;quot;: &amp;quot;bookmarks.html&amp;quot;          &lt;br /&gt;}&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;blockquote&gt;      &lt;p&gt;注意点：&lt;/p&gt;      &lt;ol&gt;        &lt;li&gt;不能替换隐身模式下的新标签页；&lt;/li&gt;        &lt;li&gt;一个插件只能覆盖一个页面。&lt;/li&gt;&lt;/ol&gt;&lt;/blockquote&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-p9AI7" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;4.5 Omnibox&lt;/h2&gt;    &lt;p&gt;Chrome和其他浏览器相比一个最大的区别就是地址栏——其实不仅仅是地址栏，而是一个多功能的输入框，Google将其称为omnibox（中文为“多功能框”）。我们熟悉的一个功能就是用户可以直接在omnibox搜索关键字，Chrome也将omnibox开放给开发者，这使得omnibox更加强大。      &lt;br /&gt;      &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d695c08e3aae427385f7db62948d4965~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-ONPkn" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;五、通讯&lt;/h1&gt;    &lt;p&gt;由于 content script 运行在网页的上下文中，而不在扩展中，因此它们通常需要某种方式来与扩展的其余部分进行通信。&lt;/p&gt;    &lt;p&gt;从浏览器插件内的视角来看通讯：      &lt;br /&gt;      &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9bf02376c1db4de0bb4ee3b1050ccef7~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp"&gt;&lt;/img&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-PbUPv" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;5.1 简单的一次性请求&lt;/h2&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-QnS8Z" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;5.1.1 从 content-scripts 发起请求&lt;/h3&gt;    &lt;pre&gt;      &lt;code&gt;chrome.runtime.sendMessage({greeting:&amp;quot;hello&amp;quot;},function(response) {console.log(response.farewell);
});复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-eUUaW" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;5.1.2 发送消息到 content-scripts&lt;/h3&gt;    &lt;pre&gt;      &lt;code&gt;chrome.tabs.query({active:true,currentWindow:true},function(tabs) {
  chrome.tabs.sendMessage(tabs[0].id, {greeting:&amp;quot;hello&amp;quot;},function(response) {console.log(response.farewell);
  });
});复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-FiSMk" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;5.1.3 接受消息&lt;/h3&gt;    &lt;pre&gt;      &lt;code&gt;chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {console.log(sender.tab?&amp;quot;from a content script:&amp;quot;+ sender.tab.url:&amp;quot;from the extension&amp;quot;);if(request.greeting===&amp;quot;hello&amp;quot;)sendResponse({farewell:&amp;quot;goodbye&amp;quot;});
  }
);复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-aFtMN" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;5.2 长链接&lt;/h2&gt;    &lt;p&gt;可以使用      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23method-connect" target="_blank" title=""&gt;runtime.connect&lt;/a&gt;和      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Ftabs%23method-connect" target="_blank" title=""&gt;tabs.connect&lt;/a&gt;建立一个长链接进行通讯。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// port1varport = chrome.runtime.connect({name:&amp;quot;knockknock&amp;quot;});
port.postMessage({joke:&amp;quot;Knock knock&amp;quot;});
port.onMessage.addListener(function(msg) {if(msg.question===&amp;quot;Who&amp;apos;s there?&amp;quot;)
    port.postMessage({answer:&amp;quot;Madame&amp;quot;});elseif(msg.question===&amp;quot;Madame who?&amp;quot;)
    port.postMessage({answer:&amp;quot;Madame... Bovary&amp;quot;});
});// port2chrome.runtime.onConnect.addListener(function(port) {console.assert(port.name===&amp;quot;knockknock&amp;quot;);
  port.onMessage.addListener(function(msg) {if(msg.joke===&amp;quot;Knock knock&amp;quot;)
      port.postMessage({question:&amp;quot;Who&amp;apos;s there?&amp;quot;});elseif(msg.answer===&amp;quot;Madame&amp;quot;)
      port.postMessage({question:&amp;quot;Madame who?&amp;quot;});elseif(msg.answer===&amp;quot;Madame... Bovary&amp;quot;)
      port.postMessage({question:&amp;quot;I don&amp;apos;t get it.&amp;quot;});
  });
});复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-W00rF" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;5.3 跨插件通讯&lt;/h2&gt;    &lt;p&gt;除了在插件中的不同组件之间发送消息之外，还可以使用消息传递 API 与其他插件进行通信。      &lt;br /&gt;可以使用      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23event-onMessageExternal" target="_blank" title=""&gt;runtime.onMessageExternal&lt;/a&gt;和      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Freference%2Fruntime%23event-onConnectExternal" target="_blank" title=""&gt;runtime.onConnectExternal&lt;/a&gt;来监听传入的请求和连接。      &lt;br /&gt;监听消息：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// For simple requests:chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse) {if(sender.id=== blocklistedExtension)return;// don&amp;apos;t allow this extension accesselseif(request.getTargetData)sendResponse({targetData: targetData});elseif(request.activateLasers) {varsuccess =activateLasers();sendResponse({activateLasers: success});
    }
  });// For long-lived connections:chrome.runtime.onConnectExternal.addListener(function(port) {
  port.onMessage.addListener(function(msg) {// See other examples for sample onMessage handlers.});
});复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;发送消息：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// The ID of the extension we want to talk to.varlaserExtensionId =&amp;quot;abcdefghijklmnoabcdefhijklmnoabc&amp;quot;;// Make a simple request:chrome.runtime.sendMessage(laserExtensionId, {getTargetData:true},function(response) {if(targetInRange(response.targetData))
      chrome.runtime.sendMessage(laserExtensionId, {activateLasers:true});
  }
);// Start a long-running conversation:varport = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-NVBXf" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;5.4 从网页发送信息&lt;/h2&gt;    &lt;p&gt;插件可以接收和响应来自常规网页的消息。要使用这个特性，必须在 Manifent.json 中指定要与哪些网站通信。      &lt;br /&gt;通讯方式和跨插件通讯方式类似。      &lt;a href="https://link.juejin.cn?target=" name="user-content-VaGTs" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h2&gt;5.5 Native通讯&lt;/h2&gt;    &lt;p&gt;Chrome 插件可以与原生应用进行通讯，原生应用可以通过注册一个 Native Messaging Host，Chrome 以一个单独的进程启动 Host，并使用标准输入和标准输出流进行通信。      &lt;br /&gt;详情可参考：      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fapps%2FnativeMessaging%2F" target="_blank" title=""&gt;developer.chrome.com/docs/apps/n…&lt;/a&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-Q3V2A" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;六、如何查看某个插件的源码&lt;/h1&gt;    &lt;ol&gt;      &lt;li&gt;开源的， 如        &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fadblockplus%2Fadblockplus" target="_blank" title=""&gt;github.com/adblockplus…&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;本地查看        &lt;ol&gt;          &lt;li&gt;第一步：打开开发者模式，找到对应插件的id&lt;/li&gt;          &lt;li&gt;第二步：本地插件目录，/Users/mac/Library/Application Support/Google/Chrome/Default/Extensions            &lt;a href="https://link.juejin.cn?target=" name="user-content-rwwgW" title=""&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;h1&gt;七、最佳实践推荐&lt;/h1&gt;    &lt;p&gt;喜欢使用 React + TS：      &lt;br /&gt;      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fchibat%2Fchrome-extension-typescript-starter" target="_blank" title=""&gt;github.com/chibat/chro…&lt;/a&gt;      &lt;br /&gt;      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Flxieyang%2Fchrome-extension-boilerplate-react" target="_blank" title=""&gt;github.com/lxieyang/ch…&lt;/a&gt;      &lt;br /&gt;喜欢使用 rxjs：      &lt;br /&gt;      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Falibaba%2Fbrowser-extension-kit" target="_blank" title=""&gt;github.com/alibaba/bro…&lt;/a&gt;      &lt;br /&gt;不想要任何框架：      &lt;br /&gt;      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FSimGus%2Fchrome-extension-v3-starter" target="_blank" title=""&gt;github.com/SimGus/chro…&lt;/a&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-OUq2p" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;八、适配其他浏览器&lt;/h1&gt;    &lt;p&gt;切换到 chromium 内核的浏览器适配工作还是比较小的，firefox 也支持 chrome API。&lt;/p&gt;    &lt;p&gt;我们只需要对照各浏览器厂商提供的开发文档，关注我们用到的 API 是否有差异，有差异的部分，做一下兼容即可。&lt;/p&gt;    &lt;p&gt;可参考各浏览器的 extension 开发文档：&lt;/p&gt;    &lt;p&gt;firefox：      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FMozilla%2FAdd-ons%2FWebExtensions%2FBuild_a_cross_browser_extension" target="_blank" title=""&gt;developer.mozilla.org/en-US/docs/…&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;Edge：      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Flearn.microsoft.com%2Fzh-cn%2Fmicrosoft-edge%2Fextensions-chromium%2Fdeveloper-guide%2Fport-chrome-extension" target="_blank" title=""&gt;learn.microsoft.com/zh-cn/micro…&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;360：      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fopen.se.360.cn%2Fopen%2Fextension_dev%2Foverview.html" target="_blank" title=""&gt;open.se.360.cn/open/extens…&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;搜狗：      &lt;a href="https://link.juejin.cn?target=http%3A%2F%2Fie.sogou.com%2Fopen%2Fdoc%2F" target="_blank" title=""&gt;ie.sogou.com/open/doc/&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-nzw7h" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;九、发布&lt;/h1&gt;    &lt;p&gt;chrome开发者中心：      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fchrome.google.com%2Fwebstore%2Fdevconsole" target="_blank" title=""&gt;chrome.google.com/webstore/de…&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;firefix发布文档：      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Faddons.mozilla.org%2Fen-US%2Fdevelopers%2F" target="_blank" title=""&gt;addons.mozilla.org/en-US/devel…&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;edge发布文档：      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Flearn.microsoft.com%2Fzh-cn%2Fmicrosoft-edge%2Fextensions-chromium%2Fpublish%2Fcreate-dev-account" target="_blank" title=""&gt;learn.microsoft.com/zh-cn/micro…&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-LEiCt" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;十、未来&lt;/h1&gt;    &lt;ul&gt;      &lt;li&gt;content-sctipts + Mockjs 自动填充表单？&lt;/li&gt;      &lt;li&gt;UI检查，自动化测试？&lt;/li&gt;      &lt;li&gt;性能、依赖检测，Lighthouse?&lt;/li&gt;      &lt;li&gt;账号管理&amp;amp;一键登录？&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;可以在评论区说说你们用到的场景~~&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://link.juejin.cn?target=" name="user-content-BFIzU" title=""&gt;&lt;/a&gt;&lt;/p&gt;    &lt;h1&gt;参考&lt;/h1&gt;    &lt;p&gt;      &lt;a href="https://juejin.cn/post/7114959554709815326" target="_blank" title=""&gt;chrome 插件开发指南(字节跳动技术团队)&lt;/a&gt;      &lt;br /&gt;      &lt;a href="https://juejin.cn/post/7071945934183071775" target="_blank" title=""&gt;Chrome Extension 扩展程序迁移至 Manifest V3&lt;/a&gt;      &lt;br /&gt;      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fblog.csdn.net%2FLuoshengyang%2Farticle%2Fdetails%2F52465364" target="_blank" title=""&gt;Chromium扩展（Extension）的Content Script加载过程分析&lt;/a&gt;      &lt;br /&gt;      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2Fe5-4TrvWX8KBv6zkbc_HtQ" target="_blank" title=""&gt;一种开发 Chrome 扩展程序的新姿势&lt;/a&gt;      &lt;br /&gt;      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fextensions%2Fmv3%2Fmessaging%2F" target="_blank" title=""&gt;Message passing&lt;/a&gt;      &lt;br /&gt;      &lt;a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.chrome.com%2Fdocs%2Fapps%2FnativeMessaging%2F" target="_blank" title=""&gt;Native Messaging&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62714-chrome-%E6%8F%92%E4%BB%B6-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Thu, 30 Mar 2023 20:33:50 CST</pubDate>
    </item>
    <item>
      <title>OpenAI宣布为ChatGPT引入插件功能 解锁更多技能</title>
      <link>https://itindex.net/detail/62704-openai-chatgpt-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;p&gt;【TechWeb】3月24日消息，据外媒报道，当地时间周四，美国人工智能研究公司OpenAI宣布为ChatGPT引入插件功能。&lt;/p&gt; &lt;p&gt;到目前为止，ChatGPT一直受到限制，因为它只能从2021年9月之前的训练数据中提取信息。引入插件功能极大地扩展了ChatGPT的功能，并首次允许它访问来自网络的实时数据。&lt;/p&gt; &lt;p&gt;OpenAI表示，这些插件是专门为语言模型设计的工具，以安全为核心原则，帮助ChatGPT访问最新信息、运行计算或使用第三方服务。&lt;/p&gt; &lt;p&gt;据外媒报道，OpenAI的插件不只可以检索实时信息，还可以绑定API，让它“代表用户执行操作”。&lt;/p&gt; &lt;p&gt;外媒称，Expedia、FiscalNote、Instacart、KAYAK、Klarna、Milo、OpenTable、Shopify、Slack、Speak、Wolfram和Zapier等公司已经为ChatGPT开发了首批插件。插件系统开放之后，这些公司的服务就可以接入ChatGPT。&lt;/p&gt; &lt;p&gt;目前，OpenAI正在努力找出与新插件功能有关的潜在风险。该公司表示，其工程师已经进行了一些测试，以确定插件可能如何被滥用。此外，该公司还在邀请外部研究人员提供反馈。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#25991;&amp;#31456;" border="0" src="http://s1.techweb.com.cn/static/img/20180614.png"&gt;&lt;/img&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>资讯编译</category>
      <guid isPermaLink="true">https://itindex.net/detail/62704-openai-chatgpt-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Fri, 24 Mar 2023 15:43:47 CST</pubDate>
    </item>
    <item>
      <title>Elasticsearch Head 插件使用小结</title>
      <link>https://itindex.net/detail/62541-elasticsearch-head-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;strong&gt;作者：崔雄华&lt;/strong&gt;&lt;/p&gt;  &lt;h1&gt;1 Elasticsearch Head是什么&lt;/h1&gt;  &lt;p&gt;ElasticSearch head就是一款能连接ElasticSearch搜索引擎，并提供可视化的操作页面对ElasticSearch搜索引擎进行各种设置和数据检索功能的管理插件，如在head插件页面编写RESTful接口风格的请求，就可以对ElasticSearch中的数据进行增删改查、创建或者删除索引等操作。类似于使用navicat工具连接MySQL这种关系型数据库，对数据库做操作。&lt;/p&gt;  &lt;h1&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;2 本地安装&lt;/h1&gt;  &lt;p&gt;下面简单介绍下ES环境安装和Elasticsearch Head在chrome浏览器中插件安装。&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;2.1 ES安装&lt;/h2&gt;  &lt;p&gt;安装链接：   &lt;a href="https://www.elastic.co/cn/webinars/getting-started-elasticsearch?elektra=what-is-elasticsearch&amp;storm=hero-banner-cta&amp;rogue=gs-with-elasticsearch-webinar"&gt;https://www.elastic.co/cn/webinars/getting-started-elasticsearch?elektra=what-is-elasticsearch&amp;amp;storm=hero-banner-cta&amp;amp;rogue=gs-with-elasticsearch-webinar&lt;/a&gt;   &lt;br /&gt;1.双击运行&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/642b09e99aa94ff7b8f1106d338dd94b%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;2.启动成功日志&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f9d6a22b9e87407b8df0b6b47b410050%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;3.查看版本信息   &lt;br /&gt;访问地址：   &lt;a href="http://localhost:9200/"&gt;http://localhost:9200/&lt;/a&gt; 出现如下信息：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df97bf7dd4904331b774abdc36def4ee%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;2.2 head插件安装&lt;/h2&gt;  &lt;p&gt;安装链接：   &lt;a href="https://chrome.google.com/webstore/detail/multi-elasticsearch-head/cpmmilfkofbeimbmgiclohpodggeheim?hl=zh-CN"&gt;https://chrome.google.com/webstore/detail/multi-elasticsearch-head/cpmmilfkofbeimbmgiclohpodggeheim?hl=zh-CN&lt;/a&gt;   &lt;br /&gt;git地址：   &lt;a href="https://github.com/mobz/elasticsearch-head"&gt;https://github.com/mobz/elasticsearch-head&lt;/a&gt;   &lt;br /&gt;1.打开head后效果&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9e1b02af5cd747059df1a338285caf64%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3eed1e7dda504a3f908b1845331ad506%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;重要信息，集群健康值。Elasticsearch 中其实有专门的衡量索引健康状况的标志，分为三个等级：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;green，绿色。这代表所有的主分片和副本分片都已分配。你的集群是 100% 可用的。&lt;/li&gt;   &lt;li&gt;yellow，黄色。所有的主分片已经分片了，但至少还有一个副本是缺失的。&lt;/li&gt;   &lt;li&gt;red，红色。至少一个主分片以及它的全部副本都在缺失中。&lt;/li&gt;&lt;/ul&gt;  &lt;h1&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;3 基本查询&lt;/h1&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;3.1 检索关键字&lt;/h2&gt;  &lt;p&gt;1.must子句   &lt;br /&gt;文档必须匹配must所有子句查询&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7d63be82de5a44048422a469b5a906fa%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;2.should子句   &lt;br /&gt;文档应该匹配should子句查询的至少一个&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/819d42f029df4ae0ae3a3c076f4e34e1%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;3.must_not子句   &lt;br /&gt;文档不能匹配该查询条件，相当于“！=”&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/188d2f1dabf84b78ab3a6d1df81c624a%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;3.2 检索条件&lt;/h2&gt;  &lt;ul&gt;   &lt;li&gt;match：分词匹配&lt;/li&gt;   &lt;li&gt;term：表示精确匹配&lt;/li&gt;   &lt;li&gt;wildcard：通配符匹配&lt;/li&gt;   &lt;li&gt;prefix：前缀匹配&lt;/li&gt;   &lt;li&gt;range：区间查询&lt;/li&gt;   &lt;li&gt;query_string：允许在单个查询字符串中指定AND&lt;/li&gt;   &lt;li&gt;text：文本&lt;/li&gt;   &lt;li&gt;missing： 无值（类似于sql中IS NULL）&lt;/li&gt;&lt;/ul&gt;  &lt;h1&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;4 复合查询&lt;/h1&gt;  &lt;p&gt;ES以RESTful接口风格的请求，使用json进行复杂的查询。请求格式：   &lt;a href="http://ip:port/%E7%B4%A2%E5%BC%95/%E7%B1%BB%E5%9E%8B/%E6%96%87%E6%A1%A3Id"&gt;http://ip:port/索引/类型/文档Id&lt;/a&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;4.1 查询数据（GET）&lt;/h2&gt;  &lt;p&gt;user/user/BmH494EB0DXGzMoya1Bu&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6e525bc1eaaa4cf7b9fb850f050ce7be%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;查询官方文档：   &lt;a href="https://www.elastic.co/guide/cn/elasticsearch/guide/current/query-dsl-intro.html"&gt;https://www.elastic.co/guide/cn/elasticsearch/guide/current/query-dsl-intro.html&lt;/a&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;4.2 插入数据（PUT、POST）&lt;/h2&gt;  &lt;p&gt;PUT方法需要指明id&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3a674b05b323469fa5b492612f5c0682%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;POST方法自动生成id&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/981591177c544d0cb6781943db4699ce%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb445e8970e2445594e9554c7772a5d0%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;4.3 更新数据（PUT）&lt;/h2&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4524c3e614a04738822e28829d7f37fa%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;4.4 删除数据（DELETE）&lt;/h2&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4353ec03a8f94bdba70aedf8a5d15165%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;4.5 给索引添加字段&lt;/h2&gt;  &lt;p&gt;user/user/_mapping也可以添加成功&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2ebadab5f71f4923aff9c5128cfa7874%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h1&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;5 具体实践&lt;/h1&gt;  &lt;p&gt;纯配ECLP运单数据除了落mysql数据库同时也会存储ES，商家工作台导出、运单列表功能就是查询ES数据。   &lt;br /&gt;例如下面就是根据运单号查询运单数据：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b066ea0d10bc43669ea2f0c4fb01cc44%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;工作中需要在运单扩展表上增加字段，除了mysql数据表需要增加字段外，ES也要增加字段。lwb_main就是运单数据索引，给lwb_main索引增加字段执行语句如下：lwb_main/_mapping/lwb_main?pretty，pretty主要做美化作用，也可不要。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d473f3450cb4e84a4c31f837b98a0d9%7Etplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h1&gt;   &lt;a href="https://itindex.net/relian"&gt;&lt;/a&gt;6 总结&lt;/h1&gt;  &lt;p&gt;Elasticsearch Head插件直接在chrome浏览器安装后就可以使用，非常方便，对于初学者大有益处，使用head插件可以快速实现ES索引数据的增删改查、创建或者删除索引等操作。&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/62541-elasticsearch-head-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Tue, 13 Dec 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>看了我常用的IDEA插件，同事也开始悄悄安装了...</title>
      <link>https://itindex.net/detail/62413-idea-%E6%8F%92%E4%BB%B6-%E5%90%8C%E4%BA%8B</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;IDEA是程序员用的最多的开发工具，很多程序员想把它打造成一站式开发工具，于是安装了各种各样的插件。通过插件在IDEA中完成各种操作，无需安装其他软件，确实很方便！今天给大家分享下我平时常用的IDEA插件，个个是精品！&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;SpringBoot实战电商项目mall（50k+star）地址：  &lt;a href="https://github.com/macrozheng/mall"&gt;https://github.com/macrozheng/mall&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;Key Promoter X&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;Key Promoter X 是一款帮助你快速学习IDEA快捷键的插件，当你在IDEA中用鼠标点击某些功能时，它会自动提示你使用该功能的快捷键。它能让你更轻松地摆脱使用鼠标功能，从而只使用键盘来开发，这大概是刚开始使用IDEA的程序员最需要的插件了。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6655cd614683409ea342e81e89423bc0~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当我们使用鼠标完成某些工作时，Key Promoter X会提示对应的快捷键，方便我们更快地掌握IDEA的快捷键。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e63e42285b7f4b498bfa9e8a8d16f7c9~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Lombok&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;Lombok目前已经是开发Java应用的标配了，不仅SpringBoot默认支持它，连IDEA也内置了Lombok插件，无需安装即可使用。Lombok是一款Java代码功能增强库，通过Lombok的注解，你可以不用再写getter、setter、equals等方法，Lombok将在编译时为你自动生成。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4d309bd1d5624ede9e3804a80bc775b3~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;举个例子，当我们给一个类添加@Getter和@Setter注解后；&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;/**
 * 修改订单费用信息参数
 * Created by macro on 2018/10/29.
 */
@Getter
@Setter
public class OmsMoneyInfoParam {
    @ApiModelProperty(&amp;quot;订单ID&amp;quot;)
    private Long orderId;
    @ApiModelProperty(&amp;quot;运费金额&amp;quot;)
    private BigDecimal freightAmount;
    @ApiModelProperty(&amp;quot;管理员后台调整订单所使用的折扣金额&amp;quot;)
    private BigDecimal discountAmount;
    @ApiModelProperty(&amp;quot;订单状态：0-&amp;gt;待付款；1-&amp;gt;待发货；2-&amp;gt;已发货；3-&amp;gt;已完成；4-&amp;gt;已关闭；5-&amp;gt;无效订单&amp;quot;)
    private Integer status;
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Lombok就会为我们自动生成所有属性的Getter和Setter方法，无需我们再手写，具体使用可以参考  &lt;a href="https://juejin.cn/post/6911476307528253453"&gt;Lombok的使用&lt;/a&gt; 。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/204b8f5ecdcf42459e6c16ce7a4ada62~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;MyBatisX&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;MybatisX是一款基于IDEA的快速开发插件，由MyBatis-Plus团队开发维护，提示很全功能也很强大。支持xml和Mapper接口之间的跳转，自带图形化的代码生成器，可以通过类似JPA的方式，直接根据方法名称生成SQL实现。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/63da2bc0a007473695f17775f67ffaf6~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我们点击Mapper接口方法左侧的图标可以直接跳转到xml中对应的SQL实现，在xml点击左侧图标也可以直接跳转到Mapper接口中对应的方法。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c3504e44d86f4f95a546872f7540097b~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当我们创建符合JPA规范的方法时，能直接生成SQL实现无需手写，MyBatisX的功能很强大，详细使用可以参考  &lt;a href="https://juejin.cn/post/7137856634075742244"&gt;MybatisX插件的使用&lt;/a&gt; 。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/799667ba9ba64dcc9f753ad920a17a67~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;RestfulFastRequest&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;RestfulFastRequest号称是IDEA版本的Postman，它是一个功能强大的Restful API工具包插件，可以根据已有的方法快速生成接口调试用例。它有一个漂亮的界面来完成请求、检查服务器响应、存储你的API请求和导出API请求，该插件能帮助你在IDEA内更快更高效地调试API！&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/43adfcc32a9c4b4cb5646795e0b7b653~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;下面是使用RestfulFastRequest调试API接口的一张效果图，用起来还是非常方便的，具体使用可以参考  &lt;a href="https://juejin.cn/post/7098511464708702239"&gt;RestfulFastRequest插件的使用&lt;/a&gt; 。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d6796caa683742e7931dcc2e6fa9b0b5~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;PlantUML&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;PlantUML是一款开源的UML图绘制工具，支持通过文本来生成图形，使用起来非常高效。可以支持时序图、类图、对象图、活动图、思维导图等图形的绘制。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/982e24eeed6f4d6d925e77a923325133~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;下面使用PlantUML来绘制一张流程图，可以实时预览，速度也很快，具体使用可以参考  &lt;a href="https://juejin.cn/post/7017988314053492767"&gt;PlantUML插件的使用&lt;/a&gt; 。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0367fa769127448fb784936797b1f54d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;SequenceDiagram&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;SequenceDiagram是一款能根据代码生成时序图的插件，还支持在时序图上直接导航到对应代码以及导出为图片或PlantUML文件。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/43f874943e9049d49c3a9f866ee6eccf~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;下面是一张使用SequenceDiagram制作的时序图，还是非常不错的，具体使用可以参考  &lt;a href="https://juejin.cn/post/7134877521182457869"&gt;SequenceDiagram插件的使用&lt;/a&gt; 。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e7f607089a224bf9b41be435760c949d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;GsonFormatPlus&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;一款能根据JSON字符串自动生成实体类的插件，支持Lombok。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6f40143654a345faaf337b8c23bf3d26~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;选择类名，右键生成，输入JSON字符串即可快速生成对应实体类。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53c9583768224486939db7e672a24d22~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Json Parser&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;一款简单小巧的JSON格式化插件，还在使用在线工具格式化JSON？试试这款IDEA插件吧！&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0ab933016db34a27989c67f48fd6e519~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;直接打开右侧面板，输入JSON字符串即可快速格式化，支持折叠显示。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/924c46a185e248b1ad33a7b5b0442083~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;String Manipulation&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;一款专业处理字符串的插件，支持各种格式代码命名方式的切换、支持各种语言的转义和反转义、支持字符加密、支持多个字符的排序、对齐、过滤等。总之功能很强大，有需要字符串操作时，可以试试它。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9cb4b3e5c0ef429e9646e52b1e91b468~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;选中需要处理的字符串，右键打开菜单即可开始使用。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/81d84735c91a4647992378dfe8ac8578~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;MapStruct support&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;MapStruct是一款基于Java注解的对象属性映射工具，使用的时候我们只要在接口中定义好对象属性映射规则，它就能自动生成映射实现类，不使用反射，性能优秀。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cd1c75447577403fa66c88fed99bbe35~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当我们使用它的IDEA插件时，他能自动提示映射对象所包含的属性，并且在点击属性时能跳转到对应属性，具体使用可以参考  &lt;a href="https://juejin.cn/post/7026151729997561869"&gt;MapStruct的使用&lt;/a&gt; 。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5dad28dec6324f51a375ca274d07523a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Alibaba Java Coding Guidelines&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;阿里巴巴《Java 开发手册》配套插件，可以实时检测代码中不符合手册规约的地方，助你码出高效，码出质量。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc8fa40d12d44299a1ee6d0c3153073e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;比如说手册里有这么一条：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/55a9cfba6e8e42f3bfd07743af01c945~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当我们违反手册规约时，该插件会自动检测并进行提示。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/17e4cd216e6b4b7fba6710e4600a117b~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;同时提供了一键检测所有代码规约情况和切换语言的功能。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec6f408f77b1427ca573479efc90e233~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如果你想修改某条规约的检测规则的话，可以通过设置的  &lt;code&gt;Editor-&amp;gt;Inspections&lt;/code&gt;进行修改。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/80bc58282f1f4e38a0a71461bf10c24e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Alibaba Cloud Toolkit&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;CloudToolkit是阿里出品的一款IDEA插件，通过它我们可以更方便地实现自动化部署，其内置的终端工具和文件上传功能，即使用来管理服务器也非常方便！这款IDEA插件不仅功能强大，而且完全免费！&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/607cff4842ee4a52a79f7172e7330122~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;配置好服务器后，通过它可以一件打包上传到服务器，然后自动执行指定的脚本。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c18f37f3022946adb8ecfa32cea6784b~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其内置了一个终端工具，提示还挺全的，如果你想在IDEA里管理Linux服务器，不妨可以试试，具体使用可以参考  &lt;a href="https://juejin.cn/post/7114097885267886116"&gt;CloudToolkit插件的使用&lt;/a&gt; 。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/168d3de379554dfb876a4c4b088494ab~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;arthas idea&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;基于IDEA开发的Arthas命令生成插件，支持Arthas官方常用的命令，比如 watch、trace、ognl static、ognl bean method、field、monitor、stack 、tt等命令。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c848d877450b4cfd8561ea59265618ef~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;直接打开右键菜单，选择Arthas命令即可快速生成命令，具体使用可以参考  &lt;a href="https://juejin.cn/post/7103706246586302495"&gt;Arthas使用教程&lt;/a&gt; 。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9dd6ce015bae4fb2a59b0a977299d7d6~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Docker&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;IDEA官方提供的Docker插件，已内置，支持远程Docker环境的镜像和容器管理，同时支持使用Docker Compose实现批量部署。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6005a3bcdbba463bb2f7a0d5bee87e52~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通过它能自动打包应用的镜像，jar包会直接上传到远程服务器并打包成镜像，具体使用可以参考  &lt;a href="https://juejin.cn/post/7111500936547139614"&gt;IDEA官方Docker插件的使用&lt;/a&gt; 。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/22529e9f4c6440819fe8581af789ff1f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Maven Helper&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;解决Maven依赖冲突的好帮手，可以快速查找项目中的依赖冲突，并予以解决！&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/23181d17c27d4fc1a7c8646a7e61e265~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我们可以通过  &lt;code&gt;pom.xml&lt;/code&gt;文件底部的  &lt;code&gt;依赖分析&lt;/code&gt;标签页查看当前项目中的所有依赖。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/573e13ebf1ce4b25842f89ea57a90c16~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通过  &lt;code&gt;冲突&lt;/code&gt;按钮我们可以筛选出所有冲突的依赖，当前项目  &lt;code&gt;guava&lt;/code&gt;依赖有冲突，目前使用的是  &lt;code&gt;18.0&lt;/code&gt;版本。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4a9cc07bc684c3fb209a6c7ec171421~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;选中有冲突的依赖，点击  &lt;code&gt;Exclude&lt;/code&gt;按钮可以直接排除该依赖。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c554927b837416ea6c85eff9c498317~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;同时  &lt;code&gt;pom.xml&lt;/code&gt;中也会对该依赖添加  &lt;code&gt;&amp;lt;exclusion&amp;gt;&lt;/code&gt;标签，是不是很方便啊！&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6e08df5f319d4d4daac182b729aa3a49~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Grep Console&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;一款帮你分析控制台日志的插件，可以对不同级别的日志进行不同颜色的高亮显示，还可以用来按关键字搜索日志内容。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6966a0ed03f49f2be2c505f84c99647~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当项目打印日志的时候，可以发现不同日志级别的日志会以不同颜色来显示。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/db8d57350491416daee64e3ebd7ed835~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如果你需要修改配色方案的话，可以通过  &lt;code&gt;Tools&lt;/code&gt;打开该插件的配置菜单。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/230355a64d2f45788515cccce2a8634d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;然后通过配置菜单修改配色方案。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa80401de02d406d9650fe8f00b77411~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;可以通过在控制台右键并使用  &lt;code&gt;Grep&lt;/code&gt;按钮来调出日志分析的窗口。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1850a891c02a4d7b991c15a2701e40ad~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;然后直接通过关键字来搜索即可。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a924b44f07ab45c5a9d1aa27b6805d3a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Markdown&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;IDEA官方出品的一款Markdown插件，支持编辑Markdown文件并进行预览，对于习惯了使用IDEA的小伙伴还是非常方便的。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e112ca5dbcdc4a22976b6d05768c8225~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;使用它来编辑Markdown文件最方便的地方在于，可以直接使用IDEA提供的各种快捷键，无需适应一套新的快捷键。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be76d31b75664b0ea2033d4edf59450e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Translation&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;一款翻译插件，支持Google、有道、阿里、百度翻译，对我们看源码时翻译注释很有帮助！&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4cc82747801f423eaaed6f82f45047a6~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;直接选中需要翻译的内容，点击右键即可找到翻译按钮；&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73a589e4671440b6a0dd7a10853078d4~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;直接使用  &lt;code&gt;翻译文档&lt;/code&gt;可以将整个文档都进行翻译；&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a2a312099d77458495b58023c6c0e7e1~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;还可以通过右上角的翻译按钮直接翻译指定内容。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9d26c1b2efb240879cd943e40fb0add8~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Statistic&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;一款代码统计工具，可以用来统计当前项目中代码的行数和大小。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f3486d0b03764b40bbb1e5d129ac7ec8~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我们可以通过顶部菜单中的  &lt;code&gt;View-&amp;gt;Tool Windows-&amp;gt;Statistic&lt;/code&gt;按钮开启该功能。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6182fd97839946ef90f9930c4d98dc0c~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;此时就可以看到我们项目代码的统计情况了，比如我的开源项目  &lt;code&gt;mall&lt;/code&gt;中  &lt;code&gt;java&lt;/code&gt;代码大小为  &lt;code&gt;2818kB&lt;/code&gt;，行数为  &lt;code&gt;85645&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e1abfc4a64304a8c979dddaa03546ff7~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Vue.js&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;Vue.js支持插件，写过前端的朋友肯定用过，可以根据模板创建   &lt;code&gt;.vue&lt;/code&gt;文件，也可以对Vue相关代码进行智能提示。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0242dbab06334848a28dfb4970bca750~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;启用该插件后，可以根据模板新建  &lt;code&gt;.vue&lt;/code&gt;文件。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/08ef0c7bdb384dc0b6859e62ab5ef623~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当我们在标签中写入以  &lt;code&gt;v-&lt;/code&gt;开头的代码时，会提示Vue中的相关指令。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/654329f8c0194618af3badae81eaef51~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;以上是我常用的20款IDEA插件，涵盖了大部分应用场景，平时开发的时候基本上也够用了。不过IDEA插件虽然能增强它的功能，给我们提供一站式的开发体验，但是也不要安装过多，太多了容易卡！&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62413-idea-%E6%8F%92%E4%BB%B6-%E5%90%8C%E4%BA%8B</guid>
      <pubDate>Tue, 06 Sep 2022 09:40:34 CST</pubDate>
    </item>
    <item>
      <title>Kubernetes容器网络及Flannel插件详解</title>
      <link>https://itindex.net/detail/62284-kubernetes-%E5%AE%B9%E5%99%A8-%E7%BD%91%E7%BB%9C</link>
      <description>&lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;theme: smartblue
highlight: a11y-dark&lt;/h2&gt;
 &lt;p&gt;持续创作，加速成长！这是我参与「掘金日新计划 · 6 月更文挑战」的第2天，  &lt;a href="https://juejin.cn/post/7099702781094674468" title="https://juejin.cn/post/7099702781094674468"&gt;点击查看活动详情&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;1.1.容器网络基础&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5b23c2657e3f4f7e87fbb7829352b11d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Kubernetes是一个开源容器调度编排引擎，管理大规模容器化应用，采用典型的Master-Worker主从分布式技术架构，由集中式管理节点（Master Node）,分布式的工作节点（Worker Node）组成。向下屏蔽底层差异化的分布式基础设施，以应用为中心构建云计算的基础操作系统能力（即云原生操作系统），面向用户提供云原生时代的云计算的新界面。&lt;/p&gt;
 &lt;p&gt;其中，Kubernetes网络至关重要，如果管理节点是控制大脑，运行节点是执行载体，那么网络就是将整个容器集群打通形成一个整体的神经网络；与Docker网络相比，Kubernetes网络最大的特点就是让容器组（Pod）拥有自己的身份证，即独立IP，实现在任何节点上的Pod都可以互相直接通信，而不需要任何的NAT地址转换；在不做限制时，Pod可以访问任何网络，并且拥有独立的网络栈，集群内部可见地址与外部可见地址保持一致。&lt;/p&gt;
 &lt;p&gt;在容器网络的具体实现上，Kubernetes通过开放的CNI标准，以插件化方式引入多种容器网络实现，从而支持各种差异化的场景的需求；当前社区比较常见的网络插件主要有Flannel、Calico、Cilium、OVN等，每个插件有不同的模式，需要按照实际的场景来选择使用。按照POD通信方式，有同主机的容器通信与跨主机的容器通信两大类型。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8fec66a8d5254e18bb9d7b687d5fc66c~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;（一）同主机的容器通信&lt;/p&gt;
 &lt;p&gt;在kubernetes集群的节点上，会创建一个veth(virtual ethernet）虚拟设备，同时将veth一端插入到容器网络的命名空间中，一端连接到主机上的网桥（linux bridge）。这样在同一主机上的POD通过veth实现IP地址相互通信。网桥也会分配一个IP地址，充当从POD到不同节点的出口流量网关。&lt;/p&gt;
 &lt;p&gt;（二）跨主机的容器通信&lt;/p&gt;
 &lt;p&gt;在不同主机上运行的容器POD通过IP地址相互通信，需要通过网络插件实现，按照依赖底层的技术大致可以分为Overlay模式，路由模式，Underlay模式三大类：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45cd72a58e604edbbc283c7c0c07d6da~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;①　Overlay模式是在二层或三层网络之上再构建起来一个独立的网络，这个网络通常会有自己独立的IP地址空间、交换或者路由的实现。VXLAN协议是目前最流行的Overlay网络隧道协议之一，显著优势就是灵活，对底层网络没有侵入性。&lt;/p&gt;
 &lt;p&gt;②　路由模式放弃了跨主机容器在L2的连通性，而专注于通过路由协议提供容器在L3的通信方案；路由模式更易于集成到现在的数据中心的基础设施之上，便捷地连接容器和主机，并在报文过滤和隔离方面有着更好的扩展能力及更精细的控制模型。&lt;/p&gt;
 &lt;p&gt;③　Underlay模式是借助驱动程序将宿主机的底层网络接口直接暴露给容器使用的一种网络构建技术，较为常见的解决方案有MAC VLAN、IP VLAN和直接路由等。&lt;/p&gt;
 &lt;h2&gt;1.2.Flannel网络插件&lt;/h2&gt;
 &lt;p&gt;Flannel是由go语言开发，是一种基于overlay网络的跨主机容器网络插件。Flannel插件为集群中所有节点重新规划IP地址的分配规则，使得不同节点上的容器能够在同一个子网内，且IP地址不重复，实现不同节点上的容器通过内网IP直接通信。Flannel目前支持udp、vxlan、host-gw、aws-vpc、gce和alloc路由等多种灵活模式。但Flannel缺少必要的安全隔离，Qos等能力，适合常见简单，安全隔离要求较低的场景。以下是Flannel三种模式比较：&lt;/p&gt;
 &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;模式&lt;/td&gt;   &lt;td&gt;底层网络要求&lt;/td&gt;   &lt;td&gt;实现模式&lt;/td&gt;   &lt;td&gt;封包/解包&lt;/td&gt;   &lt;td&gt;overlay网络&lt;/td&gt;   &lt;td&gt;转发效率&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;Flannel UDP&lt;/td&gt;   &lt;td&gt;三层网络&lt;/td&gt;   &lt;td&gt;overlay&lt;/td&gt;   &lt;td&gt;用户态&lt;/td&gt;   &lt;td&gt;三层&lt;/td&gt;   &lt;td&gt;低&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;Flannel VXLAN&lt;/td&gt;   &lt;td&gt;三层网络&lt;/td&gt;   &lt;td&gt;overlay&lt;/td&gt;   &lt;td&gt;内核态&lt;/td&gt;   &lt;td&gt;二层&lt;/td&gt;   &lt;td&gt;中&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;Flannel host-gw&lt;/td&gt;   &lt;td&gt;二层网络&lt;/td&gt;   &lt;td&gt;路由&lt;/td&gt;   &lt;td&gt;无&lt;/td&gt;   &lt;td&gt;三层&lt;/td&gt;   &lt;td&gt;高&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
 &lt;p&gt;在Flannel VXLAN模式时，插件会在kubernetes集群的每个节点上创建vxlan设备与路由表。发往不同主机上容器数据包都会通过vxlan设备，封装成UDP数据发往目的地；在目的主机上，封装的UDP数据包会被解压并路由到目标POD。但是具体到POD如何分配IP，以及整个容器生命周期过程中CNI插件与其他组件怎么交互配合，是一个相当复杂的过程，本文通过Flannel插件，以及容器运行时Containerd，详细的说明容器网络运行的全过程。&lt;/p&gt;
 &lt;h2&gt;1.3.Kubelet、Container Runtime和CNI插件&lt;/h2&gt;
 &lt;h3&gt;1.3.1.Pod IP地址分配机制&lt;/h3&gt;
 &lt;p&gt;当节点首次向集群注册时，nodeipam作为选项传递给kube-controller-manager的--controllers参数，控制器就会从集群CIDR（集群网络的IP范围）中为每个节点分配一个专用子网（podCIDR）。由于节点子网podCIDR保证不会重复，所以在为节点POD分配的IP地址也确保了唯一；如果需要更改节点的podCIDR，可以通过取消注册节点再重新注册实现。使用以下命令列出节点的podCIDR：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ kubectl get no-o json | jq &amp;apos;.spec.podCIDR&amp;apos;10.244.0.0/24
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;1.3.2.Pod启动时网络配置过程&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/107c987ffbee442490bf818bc408ca1a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当一个POD被调度到节点时，会有很多初始化操作，以下是网络配置相关的配置及初始化过程详细步骤：&lt;/p&gt;
 &lt;p&gt;①　POD被调度到容器集群的某个节点上&lt;/p&gt;
 &lt;p&gt;②　节点的kubelet通过调用CRI插件来创建POD&lt;/p&gt;
 &lt;p&gt;③　CRI插件创建POD Sandbox ID与POD网络命名空间&lt;/p&gt;
 &lt;p&gt;④　CRI插件通过POD网络命名空间和POD Sandbox ID来调用CNI插件&lt;/p&gt;
 &lt;p&gt;⑤　CNI插件配置POD网络，调用顺序从Flannel CNI插件，Bridge CNI 插件到主机IPAM CNI 插件，最后返回POD IP地址。&lt;/p&gt;
 &lt;p&gt;⑥　创建Pause容器，并将其添加第三步创建的POD的网络命名空间&lt;/p&gt;
 &lt;p&gt;⑦　Kubelet调用CRI插件拉取应用容器镜像&lt;/p&gt;
 &lt;p&gt;⑧　容器运行时containerd拉取应用容器镜像&lt;/p&gt;
 &lt;p&gt;⑨　Kubelet调用CRI插件来启动应用容器&lt;/p&gt;
 &lt;p&gt;⑩　CRI插件调用容器运行时containerd来启动和配置在pod cgroup和namespaces中的应用容器。&lt;/p&gt;
 &lt;h3&gt;1.3.3.CRI插件与CNI插件的交互&lt;/h3&gt;
 &lt;p&gt;在POD初始化过程中，CRI插件会调用CNI插件来完成对POD网络的配置。CNI网络插件都会在kubernetes节点上安装配置代理，并附带CNI配置，然后CRI插件通过该配置来确定要调用那个CNI插件。CNI配置文件的位置默认值为/etc/cni/net.d/，可以自定义配置。同时在每个节点上都会有CNI插件，CNI插件位置默认值为/opt/cni/bin，也可以自定义配置。在containerd作为容器运行时，CNI配置和CNI插件二进制文件的路径可以在containerd配置的[plugins.&amp;quot;io.containerd.grpc.v1.cri&amp;quot;.cni]中指定。&lt;/p&gt;
 &lt;p&gt;对于Flannel插件，Flanneld作为Flannel守护进程，通常安装在 kubernetes集群的节点上，而install-cni作为init容器，在每个节点上创建 CNI配置文件-/etc/cni/net.d/10-flannel.conflist。Flanneld创建vxlan设备，从API-SERVER获取并监听POD网络元数据。在创建POD时，为整个集群中的所有POD分配路由，通过路由实现POD IP地址相互通信。CRI插件与CNI插件之间的交互如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0657d4cf79e7430fae65693e550dd3a2~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;①　Kubelet通过CRI插件调用容器运行时&lt;/p&gt;
 &lt;p&gt;②　CRI插件通过CNI配置文件调用CNI插件，为POD创建网络命名空间，网络命名空间在/var/run/netns/文件下&lt;/p&gt;
 &lt;p&gt;③　Flannel CNI插件配置和调用Bridge CNI plugin&lt;/p&gt;
 &lt;p&gt;④　Bridge CNI插件在主机上创建cniO网桥，并创建了veth对，一端插入容器网络命名空间，另一端连接到cniO网桥。然后调用已配置的IPAM 插件。&lt;/p&gt;
 &lt;p&gt;⑤　host-local IPAM插件返回容器的IP地址和网关（cniO桥作为容器的网关），将IP地址分配给POD，桥接插件将网关IP地址分配给cniO桥接。所有分配的IP地址都存储在本地磁盘上的/var/lib/cni/networks/cni0/目录下。&lt;/p&gt;
 &lt;h3&gt;1.3.4.CNI插件间的交互&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6c852def69049009f41eab9083e56c0~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;CNI插件配置POD网络，调用顺序从Flannel CNI插件，Bridge CNI 插件到主机IPAM CNI 插件，最后返回POD IP地址。详情如下：&lt;/p&gt;
 &lt;p&gt;①　Flannel CNI插件：Containerd CRI插件使用CNI配置文件- /etc/cni/net.d/10-flannel.conflist调用Flannel CNI插件：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ cat /etc/cni/net.d/10-flannel.conflist{  &amp;quot;name&amp;quot;: &amp;quot;cni0&amp;quot;,  &amp;quot;plugins&amp;quot;: [    {      &amp;quot;type&amp;quot;: &amp;quot;flannel&amp;quot;,      &amp;quot;delegate&amp;quot;: {       &amp;quot;ipMasq&amp;quot;: false,        &amp;quot;hairpinMode&amp;quot;: true,        &amp;quot;isDefaultGateway&amp;quot;: true      }    }  ]}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;当Flanneld启动时，会从API-SERVER获取podCIDR和其他与网络相关的详细信息，并存储在 -/run/flannel/subnet.env文件中：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;FLANNEL_NETWORK=10.244.0.0/16 
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450 
FLANNEL_IPMASQ=false
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Flannel CNI插件使用/run/flannel/subnet.env中的信息来配置和调用网桥CNI插件。&lt;/p&gt;
 &lt;p&gt;②　Bridge CNI插件：Flannel CNI插件调用Bridge CNI插件，配置如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;cni0&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;bridge&amp;quot;,
  &amp;quot;mtu&amp;quot;: 1450,
  &amp;quot;ipMasq&amp;quot;: false,
  &amp;quot;isGateway&amp;quot;: true,
  &amp;quot;ipam&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;host-local&amp;quot;,
    &amp;quot;subnet&amp;quot;: &amp;quot;10.244.0.0/24&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;首次调用Bridge CNI插件时，会创建一个linux网桥，并在配置文件中指定“名称”：“cni0”。然后为每一个POD创建veth对，veth对一端位于容器的网络命名空间中，另一端连接到主机网络的网桥上，使用 Bridge CNI插件，主机上的所有容器都连接到主机网络的网桥上。&lt;/p&gt;
 &lt;p&gt;配置veth对后，Bridge插件调用主机本地IPAM CNI插件，可以在CNI config CRI插件中配置使用具体IPAM 插件。&lt;/p&gt;
 &lt;p&gt;③　主机本地 IPAM CNI 插件：Bridge CNI插件使用以下配置调用主机本地 IPAM CNI 插件：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;cni0&amp;quot;,
  &amp;quot;ipam&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;host-local&amp;quot;,
    &amp;quot;subnet&amp;quot;: &amp;quot;10.244.0.0/24&amp;quot;,
    &amp;quot;dataDir&amp;quot;: &amp;quot;/var/lib/cni/networks&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Host-local IPAM（IP地址管理）插件从子网返回容器的IP地址，并将分配的IP本地存储在主机上dataDir下指定的目录下- /var/lib/cni/networks/。/var/lib/cni/networks/文件包含分配 IP 的容器 ID。&lt;/p&gt;
 &lt;p&gt;调用时，主机本地IPAM 插件返回以下结果：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;{
  &amp;quot;ip4&amp;quot;: {
    &amp;quot;ip&amp;quot;: &amp;quot;10.244.4.2&amp;quot;,
    &amp;quot;gateway&amp;quot;: &amp;quot;10.244.4.3&amp;quot;
  },
  &amp;quot;dns&amp;quot;: {}
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;1.4.总结&lt;/h2&gt;
 &lt;p&gt;总体来说，容器网络是Kubernetes最复杂部分，同时也是设计精华所在，通过CNI标准开放，根据实际需求实现不同的CNI来满足差异化的需求，尤其是与底层基础云能力深度集成的场景，每个云厂商都有实现各自高效的网络插件。整个容器网络大致从以下三个方面理解：&lt;/p&gt;
 &lt;p&gt;首先，在整体设计上，让POD有唯一的IP地址，通过为每个节点分配一个子网，从而保证节点为POD分配的IP地址，在整个集群内部不会重复。&lt;/p&gt;
 &lt;p&gt;其次，在容器网络是实现上，通过CNI将容器网络标准与具体实现解耦分类，通过插件引入不同的容器网络插件。&lt;/p&gt;
 &lt;p&gt;最后，在容器POD创建时，通过Kubelet，CRI插件，以及CNI插件相互配合，实现容器POD网络配置及初始化。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62284-kubernetes-%E5%AE%B9%E5%99%A8-%E7%BD%91%E7%BB%9C</guid>
      <pubDate>Mon, 30 May 2022 15:07:54 CST</pubDate>
    </item>
    <item>
      <title>Spring Boot 如何热加载jar实现动态插件？</title>
      <link>https://itindex.net/detail/61897-spring-boot-%E5%8A%A0%E8%BD%BD</link>
      <description>&lt;h2&gt;  &lt;a href="https://zlt2000.gitee.io/#&amp;#19968;&amp;#12289;&amp;#32972;&amp;#26223;" title="&amp;#19968;&amp;#12289;&amp;#32972;&amp;#26223;"&gt;&lt;/a&gt;一、背景&lt;/h2&gt; &lt;p&gt;动态插件化编程是一件很酷的事情，能实现业务功能的   &lt;strong&gt;解耦&lt;/strong&gt; 便于维护，另外也可以提升   &lt;strong&gt;可扩展性&lt;/strong&gt; 随时可以在不停服务器的情况下扩展功能，也具有非常好的   &lt;strong&gt;开放性&lt;/strong&gt; 除了自己的研发人员可以开发功能之外，也能接纳第三方开发商按照规范开发的插件。&lt;/p&gt; &lt;p&gt;常见的动态插件的实现方式有   &lt;code&gt;SPI&lt;/code&gt;、  &lt;code&gt;OSGI&lt;/code&gt; 等方案，由于脱离了 Spring IOC 的管理在插件中无法注入主程序的 Bean 对象，例如主程序中已经集成了 Redis 但是在插件中无法使用。&lt;/p&gt; &lt;p&gt;本文主要介绍在 Spring Boot 工程中热加载 jar 包并注册成为 Bean 对象的一种实现思路，在动态扩展功能的同时支持在插件中注入主程序的 Bean 实现功能更强大的插件。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;h2&gt;  &lt;a href="https://zlt2000.gitee.io/#&amp;#20108;&amp;#12289;&amp;#28909;&amp;#21152;&amp;#36733;-jar-&amp;#21253;" title="&amp;#20108;&amp;#12289;&amp;#28909;&amp;#21152;&amp;#36733; jar &amp;#21253;"&gt;&lt;/a&gt;二、热加载 jar 包&lt;/h2&gt; &lt;p&gt;通过指定的链接或者路径动态加载 jar 包，可以使用   &lt;code&gt;URLClassLoader&lt;/code&gt; 的   &lt;code&gt;addURL&lt;/code&gt; 方法来实现，样例代码如下：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;ClassLoaderUtil 类&lt;/strong&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;public class ClassLoaderUtil {     &lt;br /&gt;    public static ClassLoader getClassLoader(String url) {     &lt;br /&gt;        try {     &lt;br /&gt;            Method method = URLClassLoader.class.getDeclaredMethod(&amp;quot;addURL&amp;quot;, URL.class);     &lt;br /&gt;            if (!method.isAccessible()) {     &lt;br /&gt;                method.setAccessible(true);     &lt;br /&gt;            }     &lt;br /&gt;            URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());     &lt;br /&gt;            method.invoke(classLoader, new URL(url));     &lt;br /&gt;            return classLoader;     &lt;br /&gt;        } catch (Exception e) {     &lt;br /&gt;            log.error(&amp;quot;getClassLoader-error&amp;quot;, e);     &lt;br /&gt;            return null;     &lt;br /&gt;        }     &lt;br /&gt;    }     &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;其中在创建   &lt;code&gt;URLClassLoader&lt;/code&gt; 时，指定当前系统的 ClassLoader 为父类加载器    &lt;code&gt;ClassLoader.getSystemClassLoader()&lt;/code&gt; 这步比较关键，用于打通主程序与插件之间的 ClassLoader ，解决把插件注册进 IOC 时的各种 ClassNotFoundException 问题。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;h2&gt;  &lt;a href="https://zlt2000.gitee.io/#&amp;#19977;&amp;#12289;&amp;#21160;&amp;#24577;&amp;#27880;&amp;#20876;-Bean" title="&amp;#19977;&amp;#12289;&amp;#21160;&amp;#24577;&amp;#27880;&amp;#20876; Bean"&gt;&lt;/a&gt;三、动态注册 Bean&lt;/h2&gt; &lt;p&gt;将插件 jar 中加载的实现类注册到 Spring 的 IOC 中，同时也会将 IOC 中已有的 Bean 注入进插件中；分别在程序启动时和运行时两种场景下的实现方式。&lt;/p&gt; &lt;h3&gt;  &lt;a href="https://zlt2000.gitee.io/#3-1-&amp;#21551;&amp;#21160;&amp;#26102;&amp;#27880;&amp;#20876;-Bean" title="3.1. &amp;#21551;&amp;#21160;&amp;#26102;&amp;#27880;&amp;#20876; Bean"&gt;&lt;/a&gt;3.1. 启动时注册 Bean&lt;/h3&gt; &lt;p&gt;使用   &lt;code&gt;ImportBeanDefinitionRegistrar&lt;/code&gt;  实现在 Spring Boot 启动时动态注册插件的 Bean，样例代码如下：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;PluginImportBeanDefinitionRegistrar 类&lt;/strong&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;public class PluginImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {     &lt;br /&gt;    private final String targetUrl = &amp;quot;file:/D:/SpringBootPluginTest/plugins/plugin-impl-0.0.1-SNAPSHOT.jar&amp;quot;;     &lt;br /&gt;    private final String pluginClass = &amp;quot;com.plugin.impl.PluginImpl&amp;quot;;     &lt;br /&gt;     &lt;br /&gt;    @SneakyThrows     &lt;br /&gt;    @Override     &lt;br /&gt;    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {     &lt;br /&gt;        ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);     &lt;br /&gt;        Class&amp;lt;?&amp;gt; clazz = classLoader.loadClass(pluginClass);     &lt;br /&gt;        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);     &lt;br /&gt;        BeanDefinition beanDefinition = builder.getBeanDefinition();     &lt;br /&gt;        registry.registerBeanDefinition(clazz.getName(), beanDefinition);     &lt;br /&gt;    }     &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt; &lt;/p&gt; &lt;h3&gt;  &lt;a href="https://zlt2000.gitee.io/#3-2-&amp;#36816;&amp;#34892;&amp;#26102;&amp;#27880;&amp;#20876;-Bean" title="3.2. &amp;#36816;&amp;#34892;&amp;#26102;&amp;#27880;&amp;#20876; Bean"&gt;&lt;/a&gt;3.2. 运行时注册 Bean&lt;/h3&gt; &lt;p&gt;程序运行时动态注册插件的 Bean 通过使用   &lt;code&gt;ApplicationContext&lt;/code&gt; 对象来实现，样例代码如下：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;@GetMapping(&amp;quot;/reload&amp;quot;)     &lt;br /&gt;public Object reload() throws ClassNotFoundException {     &lt;br /&gt;        ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);     &lt;br /&gt;        Class&amp;lt;?&amp;gt; clazz = classLoader.loadClass(pluginClass);     &lt;br /&gt;        springUtil.registerBean(clazz.getName(), clazz);     &lt;br /&gt;        PluginInterface plugin = (PluginInterface)springUtil.getBean(clazz.getName());     &lt;br /&gt;        return plugin.sayHello(&amp;quot;test reload&amp;quot;);     &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;strong&gt;SpringUtil 类&lt;/strong&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;19     &lt;br /&gt;20     &lt;br /&gt;21     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;@Component     &lt;br /&gt;public class SpringUtil implements ApplicationContextAware {     &lt;br /&gt;    private DefaultListableBeanFactory defaultListableBeanFactory;     &lt;br /&gt;    private ApplicationContext applicationContext;     &lt;br /&gt;     &lt;br /&gt;    @Override     &lt;br /&gt;    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {     &lt;br /&gt;        this.applicationContext = applicationContext;     &lt;br /&gt;        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;     &lt;br /&gt;        this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();     &lt;br /&gt;    }     &lt;br /&gt;     &lt;br /&gt;    public void registerBean(String beanName, Class&amp;lt;?&amp;gt; clazz) {     &lt;br /&gt;        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);     &lt;br /&gt;        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());     &lt;br /&gt;    }     &lt;br /&gt;     &lt;br /&gt;    public Object getBean(String name) {     &lt;br /&gt;        return applicationContext.getBean(name);     &lt;br /&gt;    }     &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt; &lt;/p&gt; &lt;h2&gt;  &lt;a href="https://zlt2000.gitee.io/#&amp;#22235;&amp;#12289;&amp;#24635;&amp;#32467;" title="&amp;#22235;&amp;#12289;&amp;#24635;&amp;#32467;"&gt;&lt;/a&gt;四、总结&lt;/h2&gt; &lt;p&gt;本文介绍的插件化实现思路通过   &lt;strong&gt;共用 ClassLoader&lt;/strong&gt; 和   &lt;strong&gt;动态注册 Bean&lt;/strong&gt; 的方式，打通了插件与主程序之间的类加载器和 Spring 容器，使得可以非常方便的实现插件与插件之间和插件与主程序之间的   &lt;strong&gt;类交互&lt;/strong&gt;，例如在插件中注入主程序的 Redis、DataSource、调用远程 Dubbo 接口等等。&lt;/p&gt; &lt;p&gt;但是由于没有对插件之间的   &lt;code&gt;ClassLoader&lt;/code&gt; 进行   &lt;strong&gt;隔离&lt;/strong&gt; 也可能会存在如类冲突、版本冲突等问题；并且由于 ClassLoader 中的 Class 对象无法销毁，所以除非修改类名或者类路径，不然插件中已加载到 ClassLoader 的类是没办法动态修改的。&lt;/p&gt; &lt;p&gt;所以本方案比较适合插件数据量不会太多、具有较好的开发规范、插件经过测试后才能上线或发布的场景。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;h2&gt;  &lt;a href="https://zlt2000.gitee.io/#&amp;#20116;&amp;#12289;&amp;#23436;&amp;#25972;-demo" title="&amp;#20116;&amp;#12289;&amp;#23436;&amp;#25972; demo"&gt;&lt;/a&gt;五、完整 demo&lt;/h2&gt; &lt;p&gt;  &lt;a href="https://github.com/zlt2000/springs-boot-plugin-test" rel="external nofollow noopener noreferrer" target="_blank"&gt;https://github.com/zlt2000/springs-boot-plugin-test&lt;/a&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>分布式 Spring Boot Java SpringBoot 插件化</category>
      <guid isPermaLink="true">https://itindex.net/detail/61897-spring-boot-%E5%8A%A0%E8%BD%BD</guid>
      <pubDate>Sun, 17 Oct 2021 10:21:06 CST</pubDate>
    </item>
    <item>
      <title>Spring Boot 如何热加载 jar 实现动态插件？</title>
      <link>https://itindex.net/detail/61831-spring-boot-%E5%8A%A0%E8%BD%BD</link>
      <description>&lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;一、背景  &lt;br /&gt;&lt;/h2&gt; &lt;p&gt;动态插件化编程是一件很酷的事情，能实现业务功能的  &lt;strong&gt;「解耦」&lt;/strong&gt;便于维护，另外也可以提升  &lt;strong&gt;「可扩展性」&lt;/strong&gt;随时可以在不停服务器的情况下扩展功能，也具有非常好的  &lt;strong&gt;「开放性」&lt;/strong&gt;除了自己的研发人员可以开发功能之外，也能接纳第三方开发商按照规范开发的插件。&lt;/p&gt; &lt;p&gt;常见的动态插件的实现方式有  &lt;code&gt;SPI&lt;/code&gt;、  &lt;code&gt;OSGI&lt;/code&gt;等方案，由于脱离了 Spring IOC 的管理在插件中无法注入主程序的 Bean 对象，例如主程序中已经集成了 Redis 但是在插件中无法使用。&lt;/p&gt; &lt;p&gt;本文主要介绍在 Spring Boot 工程中热加载 jar 包并注册成为 Bean 对象的一种实现思路，在动态扩展功能的同时支持在插件中注入主程序的 Bean 实现功能更强大的插件。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;h2&gt;二、热加载 jar 包&lt;/h2&gt; &lt;p&gt;通过指定的链接或者路径动态加载 jar 包，可以使用  &lt;code&gt;URLClassLoader&lt;/code&gt;的  &lt;code&gt;addURL&lt;/code&gt;方法来实现，样例代码如下：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;「ClassLoaderUtil 类」&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class ClassLoaderUtil {   &lt;br /&gt;    public static ClassLoader getClassLoader(String url) {   &lt;br /&gt;        try {   &lt;br /&gt;            Method method = URLClassLoader.class.getDeclaredMethod(&amp;quot;addURL&amp;quot;, URL.class);   &lt;br /&gt;            if (!method.isAccessible()) {   &lt;br /&gt;                method.setAccessible(true);   &lt;br /&gt;            }   &lt;br /&gt;            URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());   &lt;br /&gt;            method.invoke(classLoader, new URL(url));   &lt;br /&gt;            return classLoader;   &lt;br /&gt;        } catch (Exception e) {   &lt;br /&gt;            log.error(&amp;quot;getClassLoader-error&amp;quot;, e);   &lt;br /&gt;            return null;   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;其中在创建  &lt;code&gt;URLClassLoader&lt;/code&gt;时，指定当前系统的 ClassLoader 为父类加载器    &lt;code&gt;ClassLoader.getSystemClassLoader()&lt;/code&gt;这步比较关键，用于打通主程序与插件之间的 ClassLoader ，解决把插件注册进 IOC 时的各种 ClassNotFoundException 问题。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;h2&gt;三、动态注册 Bean&lt;/h2&gt; &lt;p&gt;将插件 jar 中加载的实现类注册到 Spring 的 IOC 中，同时也会将 IOC 中已有的 Bean 注入进插件中；分别在程序启动时和运行时两种场景下的实现方式。&lt;/p&gt; &lt;h3&gt;3.1. 启动时注册&lt;/h3&gt; &lt;p&gt;使用  &lt;code&gt;ImportBeanDefinitionRegistrar&lt;/code&gt; 实现在 Spring Boot 启动时动态注册插件的 Bean，样例代码如下：  &lt;strong&gt;「PluginImportBeanDefinitionRegistrar 类」&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class PluginImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {   &lt;br /&gt;    private final String targetUrl = &amp;quot;file:/D:/SpringBootPluginTest/plugins/plugin-impl-0.0.1-SNAPSHOT.jar&amp;quot;;   &lt;br /&gt;    private final String pluginClass = &amp;quot;com.plugin.impl.PluginImpl&amp;quot;;   &lt;br /&gt;   &lt;br /&gt;    @SneakyThrows   &lt;br /&gt;    @Override   &lt;br /&gt;    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {   &lt;br /&gt;        ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);   &lt;br /&gt;        Class&amp;lt;?&amp;gt; clazz = classLoader.loadClass(pluginClass);   &lt;br /&gt;        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);   &lt;br /&gt;        BeanDefinition beanDefinition = builder.getBeanDefinition();   &lt;br /&gt;        registry.registerBeanDefinition(clazz.getName(), beanDefinition);   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt; &lt;/p&gt; &lt;h3&gt;3.2. 运行时注册&lt;/h3&gt; &lt;p&gt;程序运行时动态注册插件的 Bean 通过使用  &lt;code&gt;ApplicationContext&lt;/code&gt;对象来实现，样例代码如下：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;@GetMapping(&amp;quot;/reload&amp;quot;)   &lt;br /&gt;public Object reload() throws ClassNotFoundException {   &lt;br /&gt;  ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);   &lt;br /&gt;  Class&amp;lt;?&amp;gt; clazz = classLoader.loadClass(pluginClass);   &lt;br /&gt;  springUtil.registerBean(clazz.getName(), clazz);   &lt;br /&gt;  PluginInterface plugin = (PluginInterface)springUtil.getBean(clazz.getName());   &lt;br /&gt;  return plugin.sayHello(&amp;quot;test reload&amp;quot;);   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;strong&gt;「SpringUtil 类」&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;@Component   &lt;br /&gt;public class SpringUtil implements ApplicationContextAware {   &lt;br /&gt;    private DefaultListableBeanFactory defaultListableBeanFactory;   &lt;br /&gt;    private ApplicationContext applicationContext;   &lt;br /&gt;   &lt;br /&gt;    @Override   &lt;br /&gt;    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {   &lt;br /&gt;        this.applicationContext = applicationContext;   &lt;br /&gt;        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;   &lt;br /&gt;        this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    public void registerBean(String beanName, Class&amp;lt;?&amp;gt; clazz) {   &lt;br /&gt;        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);   &lt;br /&gt;        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    public Object getBean(String name) {   &lt;br /&gt;        return applicationContext.getBean(name);   &lt;br /&gt;    }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt; &lt;/p&gt; &lt;h2&gt;四、总结&lt;/h2&gt; &lt;p&gt;本文介绍的插件化实现思路通过  &lt;strong&gt;「共用 ClassLoader」&lt;/strong&gt;和  &lt;strong&gt;「动态注册 Bean」&lt;/strong&gt;的方式，打通了插件与主程序之间的类加载器和 Spring 容器，使得可以非常方便的实现插件与插件之间和插件与主程序之间的  &lt;strong&gt;「类交互」&lt;/strong&gt;，例如在插件中注入主程序的 Redis、DataSource、调用远程 Dubbo 接口等等。&lt;/p&gt; &lt;p&gt;但是由于没有对插件之间的  &lt;code&gt;ClassLoader&lt;/code&gt;进行  &lt;strong&gt;「隔离」&lt;/strong&gt;也可能会存在如类冲突、版本冲突等问题；并且由于 ClassLoader 中的 Class 对象无法销毁，所以除非修改类名或者类路径，不然插件中已加载到 ClassLoader 的类是没办法动态修改的。&lt;/p&gt; &lt;p&gt;所以本方案比较适合插件数据量不会太多、具有较好的开发规范、插件经过测试后才能上线或发布的场景。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;h2&gt;五、完整 demo&lt;/h2&gt; &lt;p&gt;  &lt;strong&gt;https://github.com/zlt2000/springs-boot-plugin-test&lt;/strong&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/61831-spring-boot-%E5%8A%A0%E8%BD%BD</guid>
      <pubDate>Tue, 19 Oct 2021 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>本地elasticsearch-head插件的安装与使用</title>
      <link>https://itindex.net/detail/61722-elasticsearch-head-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;p&gt;上一篇谈到在Ubuntu16.04中安装elasticsearch，这一篇我们来谈谈如何在本地机器中安装elasticsearch-head插件，进行ES可视化管理&lt;/p&gt; &lt;h1&gt;  &lt;a href="http://www.duanmuxu.top/#&amp;#29615;&amp;#22659;&amp;#37197;&amp;#32622;" title="&amp;#29615;&amp;#22659;&amp;#37197;&amp;#32622;"&gt;&lt;/a&gt;环境配置&lt;/h1&gt; &lt;ul&gt;  &lt;li&gt;Node JS版本：10.16.0&lt;/li&gt;  &lt;li&gt;虚拟机中已经安装好elasticsearch，版本：5.6.8&lt;/li&gt;&lt;/ul&gt; &lt;h1&gt;  &lt;a href="http://www.duanmuxu.top/#&amp;#23433;&amp;#35013;elasticsearch-head" title="&amp;#23433;&amp;#35013;elasticsearch-head"&gt;&lt;/a&gt;安装elasticsearch-head&lt;/h1&gt; &lt;h2&gt;  &lt;a href="http://www.duanmuxu.top/#&amp;#23433;&amp;#35013;NodeJS" title="&amp;#23433;&amp;#35013;NodeJS"&gt;&lt;/a&gt;安装NodeJS&lt;/h2&gt; &lt;p&gt;elasticsearch-head 是在nodejs环境下运行的，因此必须提前安装好NodeJS，安装教程可参考此篇博客，够详细了：  &lt;a href="https://blog.csdn.net/zhangkaidsy/article/details/86645070?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&amp;utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1" rel="noopener" target="_blank"&gt;NodeJS 安装及环境配置&lt;/a&gt;&lt;/p&gt; &lt;p&gt;安装好后，在cmd命令行中键入&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;node -v     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;得到如下版本信息，便安装成功&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;C:\Users\Administrator&amp;gt;node -v     &lt;br /&gt;v10.16.0     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;  &lt;a href="http://www.duanmuxu.top/#&amp;#23433;&amp;#35013;grunt" title="&amp;#23433;&amp;#35013;grunt"&gt;&lt;/a&gt;安装grunt&lt;/h2&gt; &lt;p&gt;运行elasticsearch-head需要借助grunt命令，因此需要安装grunt。打开命令行窗口进入nodejs 安装目录，运行以下命令&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;npm install -g grunt  -cli     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;如果没有报错就安装成功&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;PS D:\nodejs&amp;gt; npm install -g grunt-cli     &lt;br /&gt;D:\nodejs\node_global\grunt -&amp;gt; D:\nodejs\node_global\node_modules\grunt-cli\bin\grunt     &lt;br /&gt;+ grunt-cli@1.3.2     &lt;br /&gt;added 150 packages from 121 contributors in 23.241s     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;  &lt;a href="http://www.duanmuxu.top/#&amp;#19979;&amp;#36733;elasticsearch-head" title="&amp;#19979;&amp;#36733;elasticsearch-head"&gt;&lt;/a&gt;下载elasticsearch-head&lt;/h2&gt; &lt;p&gt;进入网址：   &lt;a href="https://github.com/mobz/elasticsearch-head" rel="noopener" target="_blank"&gt;https://github.com/mobz/elasticsearch-head&lt;/a&gt; 下载整个项目到本地目录下并进行解压，我将解压后的目录放在了D盘&lt;/p&gt;                 &lt;div&gt;                      &lt;div&gt;&lt;/div&gt;                      &lt;img alt="&amp;#19979;&amp;#36733;ES-head" src="https://img-blog.csdnimg.cn/20200410013027371.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjU4NjM3Mw==,size_16,color_FFFFFF,t_70#pic_center" title=""&gt;&lt;/img&gt;                &lt;/div&gt;                 &lt;div&gt;下载ES-head&lt;/div&gt;                             &lt;div&gt;                      &lt;div&gt;&lt;/div&gt;                      &lt;img alt="&amp;#35299;&amp;#21387;&amp;#21518;&amp;#30340;head&amp;#25991;&amp;#20214;" src="https://img-blog.csdnimg.cn/20200410013057434.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjU4NjM3Mw==,size_16,color_FFFFFF,t_70#pic_center" title=""&gt;&lt;/img&gt;                &lt;/div&gt;                 &lt;div&gt;解压后的head文件&lt;/div&gt;             &lt;p&gt;Gruntfile.js 是个配置文件，里面可以配elasticsearch-head在本地的端口&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://www.duanmuxu.top/#npm&amp;#23433;&amp;#35013;elasticsearch-head" title="npm&amp;#23433;&amp;#35013;elasticsearch-head"&gt;&lt;/a&gt;npm安装elasticsearch-head&lt;/h2&gt; &lt;p&gt;使用命令行进入elasticsearch-head 目录，执行下述命令&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;npm install     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;进行npm安装，如果没有报错则安装成功&lt;/p&gt;                 &lt;div&gt;                      &lt;div&gt;&lt;/div&gt;                      &lt;img alt="npm&amp;#23433;&amp;#35013;" src="https://img-blog.csdnimg.cn/202004100131289.png#pic_center" title=""&gt;&lt;/img&gt;                &lt;/div&gt;                 &lt;div&gt;npm安装&lt;/div&gt;             &lt;p&gt;如果提示&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;npm ERR! code ELIFECYCLE     &lt;br /&gt;npm ERR! errno 1     &lt;br /&gt;npm ERR! phantomjs-prebuilt@2.1.16 install: `node install.js`     &lt;br /&gt;npm ERR! Exit status 1     &lt;br /&gt;npm ERR!     &lt;br /&gt;npm ERR! Failed at the phantomjs-prebuilt@2.1.16 install script.     &lt;br /&gt;npm ERR! This is probably not a problem with npm. There is likely additional logging output above.     &lt;br /&gt;     &lt;br /&gt;npm ERR! A complete log of this run can be found in:     &lt;br /&gt;npm ERR!     D:\nodejs\node_cache\_logs\2020-04-09T15_58_31_720Z-debug.log     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;这种错误，则先执行以下命令&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;npm install phantomjs-prebuilt@2.1.14 --ignore-scripts     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;安装成功后再执行 npm install，就会发现没有报错了&lt;/p&gt; &lt;h1&gt;  &lt;a href="http://www.duanmuxu.top/#&amp;#20462;&amp;#25913;&amp;#34394;&amp;#25311;&amp;#26426;&amp;#20013;&amp;#30340;elasticsearch&amp;#37197;&amp;#32622;" title="&amp;#20462;&amp;#25913;&amp;#34394;&amp;#25311;&amp;#26426;&amp;#20013;&amp;#30340;elasticsearch&amp;#37197;&amp;#32622;"&gt;&lt;/a&gt;修改虚拟机中的elasticsearch配置&lt;/h1&gt; &lt;p&gt;先将运行中的ES停掉，如果有的话，然后进入编辑虚拟机中的/usr/local/elasticsearch-5.6.8/conf/elasticsearch.yml 文件，添加下述两行代码&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;http.cors.enabled: true     &lt;br /&gt;http.cors.allow-origin: &amp;quot;*&amp;quot;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;ul&gt;  &lt;li&gt;http.cors.enabled：表示是否支持跨域，默认为false&lt;/li&gt;  &lt;li&gt;http.cors.allow-origin：当设置允许跨域，默认为*,表示支持所有域名，如果我们只是允许某些网站能访问，那么可以使用正则表达式。比如只允许本地地址。 /https?:\/\/localhost(:[0-9]+)?/&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;编辑完后保存并退出，重新运行虚拟机中的ES&lt;/p&gt; &lt;h1&gt;  &lt;a href="http://www.duanmuxu.top/#&amp;#36816;&amp;#34892;elasticsearch-head" title="&amp;#36816;&amp;#34892;elasticsearch-head"&gt;&lt;/a&gt;运行elasticsearch-head&lt;/h1&gt; &lt;p&gt;上述安装和配置都完成后，在本地主机命令行中键入 grunt server 运行elasticsearch-head，得到如下提示则运行成功&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;PS D:\elasticsearch-head-master&amp;gt; grunt server     &lt;br /&gt;Running &amp;quot;connect:server&amp;quot; (connect) task     &lt;br /&gt;Waiting forever...     &lt;br /&gt;Started connect web server on http://localhost:9100     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;此时就可以在浏览器中输入   &lt;a href="http://localhost:9100" rel="noopener" target="_blank"&gt;http://localhost:9100&lt;/a&gt; 打开elasticsearch-head  &lt;br /&gt;然后在连接栏中输入 虚拟机IP:9200 进行连接，便能连接上虚拟机中的ES&lt;/p&gt;                 &lt;div&gt;                      &lt;div&gt;&lt;/div&gt;                      &lt;img alt="&amp;#25171;&amp;#24320;ES-head" src="https://img-blog.csdnimg.cn/20200410013319692.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjU4NjM3Mw==,size_16,color_FFFFFF,t_70#pic_center" title=""&gt;&lt;/img&gt;                &lt;/div&gt;                 &lt;div&gt;打开ES-head&lt;/div&gt;                             &lt;div&gt;                      &lt;div&gt;&lt;/div&gt;                      &lt;img alt="&amp;#36830;&amp;#25509;&amp;#34394;&amp;#25311;&amp;#26426;&amp;#20013;&amp;#30340;ES" src="https://img-blog.csdnimg.cn/20200410013438935.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjU4NjM3Mw==,size_16,color_FFFFFF,t_70#pic_center" title=""&gt;&lt;/img&gt;                &lt;/div&gt;                 &lt;div&gt;连接虚拟机中的ES&lt;/div&gt;             &lt;p&gt;完结撒花。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>ElasticSearch Linux ElasticSearch</category>
      <guid isPermaLink="true">https://itindex.net/detail/61722-elasticsearch-head-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Fri, 10 Apr 2020 01:36:47 CST</pubDate>
    </item>
    <item>
      <title>k8s 重置，更换网络插件_云深不知处的技术博客_51CTO博客</title>
      <link>https://itindex.net/detail/61649-k8s-%E7%BD%91%E7%BB%9C-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;div&gt;    &lt;h1&gt;一，重置k8s&lt;/h1&gt;    &lt;p&gt;1，主节点：      &lt;br /&gt;kubectl delete node node01 node02 master      &lt;br /&gt;kubeadm reset      &lt;br /&gt;rm -rf  .kube /var/log/containers /var/log/pods      &lt;br /&gt;2，从节点      &lt;br /&gt;kubeadm reset      &lt;br /&gt;rm -rf  .kube /var/log/containers /var/log/pods&lt;/p&gt;    &lt;h1&gt;二，更换网络插件&lt;/h1&gt;    &lt;p&gt;卸载flannel：      &lt;br /&gt;在master节点删除flannel      &lt;br /&gt;kubectl delete -f kube-flannel.yml      &lt;br /&gt;重启kubelet      &lt;br /&gt;sytemctl restart kubelet      &lt;br /&gt;安装calico：      &lt;br /&gt;wget      &lt;a href="https://docs.projectcalico.org/manifests/calico.yaml" rel="nofollow"&gt;https://docs.projectcalico.org/manifests/calico.yaml&lt;/a&gt;      &lt;br /&gt;kubectl apply -f calico.yaml      &lt;br /&gt;安装完毕后使用docker images 查看容器镜像可以看见如下以calico打头的镜像      &lt;br /&gt;      &lt;img alt="k8s&amp;#23398;&amp;#20064;&amp;#31508;&amp;#35760;&amp;#20043;&amp;#23433;&amp;#35013;&amp;#20108;&amp;#8212;&amp;#8212;&amp;#37325;&amp;#32622;&amp;#65292;&amp;#26356;&amp;#25442;&amp;#32593;&amp;#32476;&amp;#25554;&amp;#20214;" src="https://s4.51cto.com/images/blog/202006/15/e13dc20cbf128e944eb46b8b8725c101.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk="&gt;&lt;/img&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61649-k8s-%E7%BD%91%E7%BB%9C-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Wed, 28 Jul 2021 19:27:17 CST</pubDate>
    </item>
    <item>
      <title>K8s网络插件flannel与calico - 小雨淅淅o0 - 博客园</title>
      <link>https://itindex.net/detail/61547-k8s-%E7%BD%91%E7%BB%9C-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;div&gt;    &lt;p&gt;       &lt;strong&gt;Kubernetes的网络通信问题:&lt;/strong&gt;      &lt;br /&gt;　　1. 容器间通信： 即同一个Pod内多个容器间通信，通常使用loopback来实现。      &lt;br /&gt;　　2. Pod间通信： K8s要求,Pod和Pod之间通信必须使用Pod-IP 直接访问另一个Pod-IP      &lt;br /&gt;　　3. Pod与Service通信： 即PodIP去访问ClusterIP，当然，clusterIP实际上是IPVS 或 iptables规则的虚拟IP，是没有TCP/IP协议栈支持的。但不影响Pod访问它.      &lt;br /&gt;　　4. Service与集群外部Client的通信，即K8s中Pod提供的服务必须能被互联网上的用户所访问到。      &lt;br /&gt;      &lt;br /&gt;需要注意的是，k8s集群初始化时的service网段，pod网段，网络插件的网段，以及真实服务器的网段，都不能相同，如果相同就会出各种各样奇怪的问题，而且这些问题在集群做好之后是不方便改的，改会导致更多的问题，所以，就在搭建前将其规划好。      &lt;br /&gt;      &lt;br /&gt;CNI(容器网络接口)：      &lt;br /&gt;　　这是K8s中提供的一种通用网络标准规范，因为k8s本身不提供网络解决方案。      &lt;br /&gt;　　目前比较知名的网络解决方案有:      &lt;br /&gt;　　　　flannel      &lt;br /&gt;　　　　calico      &lt;br /&gt;　　　　canel      &lt;br /&gt;　　　　kube-router      &lt;br /&gt;　　　　.......      &lt;br /&gt;等等，目前比较常用的时flannel和calico，flannel的功能比较简单，不具备复杂网络的配置能力，calico是比较出色的网络管理插件，单具备复杂网络配置能力的同时，往往意味着本身的配置比较复杂，所以相对而言，比较小而简单的集群使用flannel，考虑到日后扩容，未来网络可能需要加入更多设备，配置更多策略，则使用calico更好      &lt;br /&gt;所有的网络解决方案，它们的共通性：      &lt;br /&gt;　　1. 虚拟网桥      &lt;br /&gt;　　2. 多路复用：MacVLAN      &lt;br /&gt;　　3. 硬件交换：SR-IOV（单根-I/O虚拟网络）：它是一种物理网卡的硬件虚拟化技术，它通过输出VF(虚拟功能)来将网卡虚拟为多个虚拟子接口，每个VF绑定给一个VM后，该VM就可以直接操纵该物理网卡。      &lt;br /&gt;      &lt;br /&gt;kubelet来调CNI插件时，会到 /etc/cni/net.d/目录下去找插件的配置文件，并读取它，来加载该插件,并让该网络插件来为Pod提供网络服务。      &lt;br /&gt;      &lt;br /&gt;flannel网络插件要怎么部署？      &lt;br /&gt;　1. flannel部署到那个节点上？      &lt;br /&gt;　　因为kubelet是用来管理Pod的，而Pod运行需要网络，因此凡是部署kubelet的节点，都需要部署flannel来提供网络，因为kubelet正是通过调用flannel来实现为Pod配置网络的(如:添加网络，配置网络，激活网络等)。      &lt;br /&gt;      &lt;br /&gt;　2. flannel自身要如何部署？      &lt;br /&gt;　　1》它支持直接运行为宿主机上的一个守护进程。      &lt;br /&gt;　　2》它也支持运行为一个Pod      &lt;br /&gt;　　对于运行为一个Pod这种方式：就必须将flannel配置为共享当前宿主机的网络名称空间的Pod，若flannel作为控制器控制的Pod来运行的话，它的控制器必须是DaemonSet，在每一个节点上都控制它仅能运行一个Pod副本，而且该副本必须直接共享宿主机的网络名称空间，因为只有这样，此Pod才能设置宿主机的网络名称空间，因为flannel要在当前宿主机的网络名称空间中创建CNI虚拟接口，还要将其他Pod的另一半veth桥接到虚拟网桥上，若不共享宿主机的网络名称空间，这是没法做到的。      &lt;br /&gt;      &lt;br /&gt;3. flannel的工作方式有3种:      &lt;br /&gt;　　1) VxLAN:      &lt;br /&gt;　　　而VxLAN有两种工作方式:      &lt;br /&gt;　　　　a.  VxLAN:　这是原生的VxLAN，即直接封装VxLAN首部，UDP首部，IP，MAC首部这种的。      &lt;br /&gt;　　　　b.  DirectRouting: 这种是混合自适应的方式, 即它会自动判断，若当前是相同二层网络      &lt;br /&gt;　　　　   (即：不垮路由器,二层广播可直达)，则直接使用Host-GW方式工作，若发现目标是需要跨网段      &lt;br /&gt;　　　　   (即：跨路由器)则自动转变为使用VxLAN的方式。      &lt;br /&gt;　　2)  host-GW:  这种方式是宿主机内Pod通过虚拟网桥互联，然后将宿主机的物理网卡作为网关，当需要访问其它Node上的Pod时，只需要将报文发给宿主机的物理网卡，由宿主机通过查询本地路由表，来做路由转发，实现跨主机的Pod通信，这种模式带来的问题时，当k8s集群非常大时，会导致宿主机上的路由表变得非常巨大，而且这种方式，要求所有Node必须在同一个二层网络中，否则将无法转发路由，这也很容易理解，因为如果Node之间是跨路由的，那中间的路由器就必须知道Pod网络的存在，它才能实现路由转发，但实际上，宿主机是无法将Pod网络通告给中间的路由器，因此它也就无法转发理由。      &lt;br /&gt;　　3) UDP: 这种方式性能最差的方式，这源于早期flannel刚出现时，Linux内核还不支持VxLAN，即没有VxLAN核心模块，因此flannel采用了这种方式，来实现隧道封装，其效率可想而知，因此也给很多人一种印象，flannel的性能很差，其实说的是这种工作模式，若flannel工作在host-GW模式下，其效率是非常高的，因为几乎没有网络开销。      &lt;br /&gt;      &lt;br /&gt;4. flannel的网络配置参数：      &lt;br /&gt;　　1) Network: flannel使用的CIDR格式的网络地址，主要用于为Pod配置网络功能。      &lt;br /&gt;　　　如:  10.10.0.0/16  ---&amp;gt;      &lt;br /&gt;　　　　master:  10.10.0.0/24      &lt;br /&gt;　　　　node01: 10.10.1.0/24      &lt;br /&gt;　　　　.....      &lt;br /&gt;　　　　node255: 10.10.255.0/24      &lt;br /&gt;      &lt;br /&gt;　　2) SubnetLen: 把Network切分为子网供各节点使用时，使用多长的掩码来切分子网，默认是24位.      &lt;br /&gt;　　3) SubnetMin: 若需要预留一部分IP时，可设置最小从那里开始分配IP，如：10.10.0.10/24 ，这样就预留出了10个IP      &lt;br /&gt;　　4) SubnetMax: 这是控制最多分配多个IP，如: 10.10.0.100/24  这样在给Pod分配IP时，最大分配到10.10.0.100了。      &lt;br /&gt;　　5) Backend: 指定后端使用的协议类型，就是上面提到的：vxlan( 原始vxlan，directrouter)，host-gw, udp&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;flannel的配置：&lt;/strong&gt;      &lt;br /&gt;　　.....      &lt;br /&gt;　　net-conf.json: |      &lt;br /&gt;　　　　{      &lt;br /&gt;　　　　　&amp;quot;Network&amp;quot;:  &amp;quot;10.10.0.0/16&amp;quot;,      &lt;br /&gt;　　　　　&amp;quot;Backend&amp;quot;:  {      &lt;br /&gt;　　　　　&amp;quot;Type&amp;quot;:   &amp;quot;vxlan&amp;quot;, 　　            #当然，若你很确定自己的集群以后也不可能跨网段，你完全可以直接设置为 host-gw.      &lt;br /&gt;　　　　　&amp;quot;Directrouting&amp;quot;:  true  #默认是false，修改为true就是可以让VxLAN自适应是使用VxLAN还是使用host-gw了。      &lt;br /&gt;　　　　　}      &lt;br /&gt;　　　　}      &lt;br /&gt;      &lt;br /&gt;#在配置flannel时，一定要注意，不要在半道上，去修改，也就是说要在你部署k8s集群后，就直接规划好，而不要在k8s集群已经运行起来了，你再去修改，虽然可能也不会出问题，但一旦出问题，你就！！&lt;/p&gt;    &lt;p&gt;　　      &lt;img alt="" src="https://img2018.cnblogs.com/blog/922925/201908/922925-20190802172545350-1788912835.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;  #在配置好，flannel后，一定要测试，创建新Pod，看看新Pod是否能从flannel哪里获得IP地址，是否能通信。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Calico：&lt;/strong&gt;      &lt;br /&gt;　　Calico是一种非常复杂的网络组件，它需要自己的etcd数据库集群来存储自己通过BGP协议获取的路由等各种所需要持久保存的网络数据信息，因此在部署Calico时，早期是需要单独为Calico部署etcd集群的，因为在k8s中，访问etcd集群只有APIServer可以对etcd进行读写，其它所有组件都必须通过APIServer作为入口，将请求发给APIServer，由APIServer来从etcd获取必要信息来返回给请求者，但Caclico需要自己写，因此就有两种部署Calico网络插件的方式，一种是部署两套etcd，另一种就是Calico不直接写，而是通过APIServer做为代理，来存储自己需要存储的数据。通常第二种使用的较多，这样可降低系统复杂度。      &lt;br /&gt;　　当然由于Calico本身很复杂，但由于很多k8s系统可能存在的问题是，早期由于各种原因使用了flannel来作为网络插件，但后期发现需要使用网络策略的需求，怎么办？      &lt;br /&gt;　　目前比较成熟的解决方案是：flannel + Calico, 即使用flannel来提供简单的网络管理功能，而使用Calico提供的网络策略功能。      &lt;br /&gt;      &lt;br /&gt;      &lt;br /&gt;Calico网络策略：&lt;/p&gt;    &lt;p&gt;　　      &lt;img alt="" src="https://img2018.cnblogs.com/blog/922925/201908/922925-20190802174641622-981307379.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;  Egress：是出站的流量，即自己是源，远端为服务端，因此我自己的源IP可确定，但端口不可预知， 目标的端口和IP都是确定的，因此to 和 ports都是指目标的IP和端口。      &lt;br /&gt;  Ingress：是入站的流量，即自己为目标，而远端是客户端，因此要做控制，就只能对自己的端口 和 客户端的地址 做控制。      &lt;br /&gt;我们通过Ingress 和 Egress定义的网络策略是对一个Pod生效 还是 对一组Pod生效？      &lt;br /&gt;这个就要通过podSelector来实现了。      &lt;br /&gt;而且在定义网络策略时，可以很灵活，如：入站都拒绝，仅允许出站的； 或 仅允许指定入站的，出站都允许等等。      &lt;br /&gt;另外，在定义网络策略时，也可定义 在同一名称空间中的Pod都可以自由通信，但跨名称空间就都拒绝。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;网络策略的生效顺序：&lt;/strong&gt;      &lt;br /&gt;　　越具体的规则越靠前，越靠前，越优先匹配&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;网络策略的定义：&lt;/strong&gt;      &lt;br /&gt;　　kubectl  explain  networkpolicy      &lt;br /&gt;　　spec:      &lt;br /&gt;　　　egress:  &amp;lt;[]Object&amp;gt;  :定义出站规则      &lt;br /&gt;　　　ingress: &amp;lt;[]Object&amp;gt;: 定义入站规则      &lt;br /&gt;　　　podSelector： 如论是入站还是出站，这些规则要应用到那些Pod上。      &lt;br /&gt;　　　policyType：[Ingress|Egress| Ingress,Egress]   :      &lt;br /&gt;　　　  它用于定义若同时定义了egress和ingress，到底那个生效？若仅给了ingress，则仅ingress生效，若设置为Ingress,Egress则两个都生效。      &lt;br /&gt;　　      注意：policyType在使用时,若不指定，则当前你定义了egress就egress生效，若egress,ingress都定义了,则两个都生效！！      &lt;br /&gt;　　　　还有，若你定义了egress， 但policyType: ingress, egress  ； egress定义了，但ingress没有定义,这种要会怎样？      &lt;br /&gt;　　　　其实，这时ingress的默认规则会生效，即：若ingress的默认规则为拒绝，则会拒绝所有入站请求，若为允许，则会允许所有入站请求，      &lt;br /&gt;　　　　所以，若你只想定义egress规则，就明确写egress ！！      &lt;br /&gt;      &lt;br /&gt;　　egress：&amp;lt;[]Object&amp;gt;      &lt;br /&gt;　　    ports: &amp;lt;[]Object&amp;gt;  :因为ports是有端口号 和 协议类型的，因此它也是对象列表      &lt;br /&gt;　　    　port :      &lt;br /&gt;　　　　protocol: 这两个就是用来定义目标端口和协议的。      &lt;br /&gt;　　　to :&amp;lt;[]Object&amp;gt;      &lt;br /&gt;　　　    podSelector: &amp;lt;Object&amp;gt; : 在控制Pod通信时，可控制源和目标都是一组Pod，然后控制这两组Pod之间的访问。      &lt;br /&gt;　　　   ipBlock：&amp;lt;[]Object&amp;gt; : 指定一个Ip地址块，只要在这个IP范围内的，都受到策略的控制，而不区分是Pod还是Service。      &lt;br /&gt;　　　   namespaceSelector： 这是控制对指定名称空间内的全部Pod 或 部分Pod做访问控制。      &lt;br /&gt;      &lt;br /&gt;　　Ingress：      &lt;br /&gt;　　    from： 这个from指访问者访问的IP      &lt;br /&gt;　　    ports:  也是访问者访问的Port&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;a title=""&gt;          &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;      &lt;pre&gt;#定义网络策略：
vim   networkpolicy-demo.yaml
apiVersion:  networking.k8s.io/v1  
    #注意：虽然kubectl  explain networkpolicy中显示为 extensions/v1beta1 ，但你要注意看说明部分.
kind:  NetworkPolicy
metadata:
     name:  deny-all-ingressnamespace:  dev
spec:
     podSelector:  {}  #这里写空的含义是，选择指定名称空间中所有Pod
     policyTypes:-Ingress        #这里指定要控制Ingress(进来的流量)，但又没有指定规则，就表示全部拒绝，只有明确定义的,才是允许的。
                       #egress: 出去的流量不控制，其默认规则就是允许，因为不关心，所以爱咋咋地的意思。
    
#写一个简单的自主式Pod的定义:
vim  pod1.yaml
    apiVersion:  v1
    kind: Pod
    metadata: 
      name:  pod1
    spec:
      containers:-name:  myapp
        image:  harbor.zcf.com/k8s/myapp:v1

#创建dev名称空间，并应用规则
kubectl apply-f networkpolicy-demo.yaml -n dev

# kubectl describe-n dev networkpolicies
    Name:         deny-all-ingress
    Namespace:    dev
   ........................
    Spec:
      PodSelector:&amp;lt;none&amp;gt; (Allowing the specific traffic to all podsinthisnamespace)
      Allowing ingress traffic:&amp;lt;none&amp;gt; (Selected pods are isolatedforingress connectivity)
      Allowing egress traffic:&amp;lt;none&amp;gt; (Selected pods are isolatedforegress connectivity)


#查看dev名称空间中的网络规则:
kubectlgetnetworkpolicy -n dev     
 或   
 kubectlgetnetpol  -n  dev

#然后在dev 和 prod 两个名称空间中分别创建pod
kubectl  apply-f  pod1.yaml   -n   dev
kubectl  apply-f  pod1.yaml   -n   prod

#接着测试访问这两个名称空间中的pod
kubectlgetpod  -n  dev   -o   wide
    
    #测试访问:
        curl   http://POD_IPkubectlgetpod  -n  prod   -o   wide

    #测试访问:
        curl   http://POD_IP#通过以上测试，可以看到，dev名称空间中的pod无法被访问，而prod名称空间中的pod则可被访问。&lt;/pre&gt;      &lt;div&gt;        &lt;a title=""&gt;          &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;    &lt;div&gt;      &lt;div&gt;        &lt;a title=""&gt;          &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;      &lt;pre&gt;#测试放行所有dev的ingress入站请求。
# vim  networkpolicy-demo.yaml
    apiVersion:  networking.k8s.io/v1   
    kind:  NetworkPolicy
    metadata:
       name: allow-all-ingressnamespace:  dev
    spec:
       podSelector:  {}
       ingress:-{}      #这就表示允许所有，因为定义了规则，但规则是空的，即允许所有。
       policyTypes:-Ingress

#接着测试，和上面测试一样，也是访问dev 和 prod两个名称空间中的pod，若能访问，则成功。
# kubectl describe-n dev netpol
    Name:         deny-all-ingress
    Namespace:    dev
    .....................
    Spec:
      PodSelector:&amp;lt;none&amp;gt; (Allowing the specific traffic to all podsinthisnamespace)
      Allowing ingress traffic:
        To Port:&amp;lt;any&amp;gt;(traffic allowed to all ports)
        From:&amp;lt;any&amp;gt;(traffic not restricted by source)
      Allowing egress traffic:&amp;lt;none&amp;gt; (Selected pods are isolatedforegress connectivity)
      Policy Types: Ingress&lt;/pre&gt;      &lt;div&gt;        &lt;a title=""&gt;          &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;  #测试定义一个仅允许访问dev名称空间中，pod标签 app=myapp 的一组pod的80端口&lt;/p&gt;    &lt;p&gt;　　      &lt;img alt="" src="https://img2018.cnblogs.com/blog/922925/201908/922925-20190802175542541-907033446.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;a title=""&gt;          &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;      &lt;pre&gt;#先给pod1打上app=myapp的标签
#kubectl label pod pod1 app=myapp -n dev
    
vim  allow-dev-80.yaml
 apiVersion: networking.k8s.io/v1
 kind: NetworkPolicy
 metadata:
    name: allow-myapp-ingress
 spec:
   podSelector:
      matchLabels:
        app: myapp
     ingress:-from:-ipBlock:
            cidr:10.10.0.0/16except:-10.10.1.2/32ports:-protocol:  TCP
         port:88-protocol:  TCP
         port:443#查看定义的ingress规则
kubectlgetnetpol  -n  dev

#然后测试访问 dev 名称空间中的pod
curl  http://Pod_IPcurl  http://Pod_IP:443curl   http://Pod_IP:88&lt;/pre&gt;      &lt;div&gt;        &lt;a title=""&gt;          &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2018.cnblogs.com/blog/922925/201908/922925-20190802175810651-579552423.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2018.cnblogs.com/blog/922925/201908/922925-20190802175826689-478710017.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;a title=""&gt;          &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;      &lt;pre&gt;上图测试：1. 先给dev名称空间打上标签
  kubectl   labelnamespacedev   ns=dev2. 编写网络策略配置清单
  vim  allow-ns-dev.yaml
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-ns-dev
    spec:
      podSelector: {}
      ingress:-from:-namespaceSelector:
              matchLabels:
                ns: dev
      egress:-to:-namespaceSelector:
             matchLabels:
               ns: dev



#要控制egress，也是如此，只是将ingress替换为egress即可，然后在做测试。
另外，关于网络策略，建议：
 名称空间内：
     拒绝所有出站，入站流量
     仅放行出站目标为当前名称空间内各Pod间通信，因为网络策略控制的颗粒度是Pod级别的，不是名称空间级别。
     具体的网络策略，要根据实际需求，来定义ingress 和 egress规则。&lt;/pre&gt;      &lt;div&gt;        &lt;a title=""&gt;          &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt; &lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61547-k8s-%E7%BD%91%E7%BB%9C-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Fri, 18 Jun 2021 17:13:32 CST</pubDate>
    </item>
    <item>
      <title>PostgreSQL 时序数据库插件 timescaleDB 部署实践(含例子 纽约TAXI数据透视分析) - PostGIS + timescaleDB =&gt; PG时空数据库 - Digoal.Zhou’s Blog</title>
      <link>https://itindex.net/detail/61289-postgresql-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;h2&gt;背景&lt;/h2&gt;  &lt;p&gt;现实社会中，很多业务产生的数据具有时序数据属性（在时间维度上顺序写入，同时包括大量时间区间查询统计的需求）。&lt;/p&gt;  &lt;p&gt;例如业务的FEED数据，物联网产生的时序数据（如气象传感器、车辆轨迹、等），金融行业的实时数据等等。&lt;/p&gt;  &lt;p&gt;PostgreSQL的UDF和BRIN（块级索引）很适合时序数据的处理。具体有以下的两个例子&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://billtian.github.io/digoal.blog/2018/01/201711/20171102_02.md"&gt;《PostgreSQL 按需切片的实现(TimescaleDB插件自动切片功能的plpgsql schemaless实现)》&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://billtian.github.io/digoal.blog/2018/01/201704/20170417_01.md"&gt;《PostgreSQL 时序最佳实践 - 证券交易系统数据库设计 - 阿里云RDS PostgreSQL最佳实践》&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img alt="pic" src="https://billtian.github.io/digoal.blog/2018/01/29/20180129_01_pic_002.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;实际上PostgreSQL生态中，衍生了一个时序插件：timescaleDB。专门用于处理时序数据。（timescale的改进，包括SQL优化器的改进(支持merge append，时间片聚合非常高效)，rotate接口，自动分片等）&lt;/p&gt;  &lt;p&gt;同时timescaleDB也非常受投资者的关注，已获5000万美金的投资，也间接说明时序数据库在未来是非常受用户欢迎的。&lt;/p&gt;  &lt;h2&gt;timescaleDB的优势&lt;/h2&gt;  &lt;p&gt;首先，timescaleDB是自动切片的，对用户无感知，在数据量非常庞大的时候，写入性能不衰减。（主要指IOPS较低的磁盘，如果IOPS较好的磁盘PG在写入大量数据后性能也是OK的。）&lt;/p&gt;  &lt;p&gt;    &lt;img alt="pic" src="https://billtian.github.io/digoal.blog/2018/01/29/20180129_01_pic_001.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;其次，timescale改进了SQL优化器，增加了merge append的执行节点，同时在对小时间片进行group by时，可以不用HASH或GROUP整个数据范围，而是分片计算，使得效率非常高。&lt;/p&gt;  &lt;p&gt;最后，timescale增加了一些API，使得用户在时序数据的写入、维护、查询都非常的高效、同时易于维护。&lt;/p&gt;  &lt;p&gt;API如下&lt;/p&gt;  &lt;p&gt;http://docs.timescale.com/v0.8/api&lt;/p&gt;  &lt;h2&gt;部署timescaleDB&lt;/h2&gt;  &lt;p&gt;以CentOS 7.x x64为例。&lt;/p&gt;  &lt;p&gt;1、首先要安装好PostgreSQL&lt;/p&gt;  &lt;p&gt;参考    &lt;a href="https://billtian.github.io/digoal.blog/2018/01/201611/20161121_01.md"&gt;《PostgreSQL on Linux 最佳部署手册》&lt;/a&gt;&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;export USE_NAMED_POSIX_SEMAPHORES=1  
LIBS=-lpthread CFLAGS=&amp;quot;-O3&amp;quot; ./configure --prefix=/home/digoal/pgsql10 --with-segsize=8 --with-wal-segsize=256  
LIBS=-lpthread CFLAGS=&amp;quot;-O3&amp;quot; make world -j 64  
LIBS=-lpthread CFLAGS=&amp;quot;-O3&amp;quot; make install-world&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;2、其次需要安装cmake3&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;epel  
  
yum install -y cmake3  
  
ln -s /usr/bin/cmake3 /usr/bin/cmake&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;3、编译timescaleDB&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;git clone https://github.com/timescale/timescaledb/  
  
cd timescaledb  
git checkout release-0.8.0  
  
或  
  
wget https://github.com/timescale/timescaledb/archive/0.8.0.tar.gz  
  
  
  
export PATH=/home/digoal/pgsql10/bin:$PATH  
export LD_LIBRARY_PATH=/home/digoal/pgsql10/lib:$LD_LIBRARY_PATH  
  
# Bootstrap the build system  
./bootstrap  
  
cd ./build &amp;amp;&amp;amp; make  
  
make install  
  
  
[  2%] Built target sqlupdatefile  
[  4%] Built target sqlfile  
[100%] Built target timescaledb  
Install the project...  
-- Install configuration: &amp;quot;Release&amp;quot;  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb.control  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.8.0.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.7.1--0.8.0.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.1.0--0.2.0.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.2.0--0.3.0.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.3.0--0.4.0.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.4.0--0.4.1.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.4.1--0.4.2.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.4.2--0.5.0.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.5.0--0.6.0.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.6.0--0.6.1.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.6.1--0.7.0.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.6.1--0.7.1.sql  
-- Installing: /home/dege.zzz/pgsql10/share/extension/timescaledb--0.7.0--0.7.1.sql  
-- Installing: /home/dege.zzz/pgsql10/lib/timescaledb.so&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;4、配置postgresql.conf，在数据库启动时自动加载timescale lib库。&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;vi $PGDATA/postgresql.conf  
shared_preload_libraries = &amp;apos;timescaledb&amp;apos;  
  
pg_ctl restart -m fast&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;5、对需要使用timescaledb的数据库，创建插件.&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;psql  
psql (10.1)  
Type &amp;quot;help&amp;quot; for help.  
  
postgres=# create extension timescaledb ;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;6、timescaledb的相关参数&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;timescaledb.constraint_aware_append     
timescaledb.disable_optimizations       
timescaledb.optimize_non_hypertables    
timescaledb.restoring    
  
postgres=# show timescaledb.constraint_aware_append ;  
 timescaledb.constraint_aware_append   
-------------------------------------  
 on  
(1 row)  
  
postgres=# show timescaledb.disable_optimizations ;  
 timescaledb.disable_optimizations   
-----------------------------------  
 off  
(1 row)  
  
postgres=# show timescaledb.optimize_non_hypertables ;  
 timescaledb.optimize_non_hypertables   
--------------------------------------  
 off  
(1 row)  
  
postgres=# show timescaledb.restoring ;  
 timescaledb.restoring   
-----------------------  
 off  
(1 row)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h2&gt;timescaleDB使用例子1 - 纽约TAXI数据透视分析&lt;/h2&gt;  &lt;p&gt;第一个例子是real-life New York City taxicab data ，&lt;/p&gt;  &lt;p&gt;http://docs.timescale.com/v0.8/tutorials/tutorial-hello-nyc&lt;/p&gt;  &lt;p&gt;数据为真实的数据，来自&lt;/p&gt;  &lt;p&gt;http://www.nyc.gov/html/tlc/html/about/trip_record_data.shtml&lt;/p&gt;  &lt;p&gt;1、下载样本数据&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;wget https://timescaledata.blob.core.windows.net/datasets/nyc_data.tar.gz&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;2、解压&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;tar -zxvf nyc_data.tar.gz&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;3、建表，其中包括将普通表转换为时序存储表的API create_hypertable 的使用。&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;psql -f nyc_data.sql&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;截取一些nyc_data.sql的内容如下：&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;cat nyc_data.sql  
  
-- 打车数据: 包括时长、计费、路程、上车、下车经纬度、时间、人数等等。  
  
CREATE TABLE &amp;quot;rides&amp;quot;(  
    vendor_id TEXT,  
    pickup_datetime TIMESTAMP WITHOUT TIME ZONE NOT NULL,  
    dropoff_datetime TIMESTAMP WITHOUT TIME ZONE NOT NULL,  
    passenger_count NUMERIC,  
    trip_distance NUMERIC,  
    pickup_longitude  NUMERIC,  
    pickup_latitude   NUMERIC,  
    rate_code         INTEGER,  
    dropoff_longitude NUMERIC,  
    dropoff_latitude  NUMERIC,  
    payment_type INTEGER,  
    fare_amount NUMERIC,  
    extra NUMERIC,  
    mta_tax NUMERIC,  
    tip_amount NUMERIC,  
    tolls_amount NUMERIC,  
    improvement_surcharge NUMERIC,  
    total_amount NUMERIC  
);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;这句话，将rides转换为时序表存储&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;SELECT create_hypertable(&amp;apos;rides&amp;apos;, &amp;apos;pickup_datetime&amp;apos;, &amp;apos;payment_type&amp;apos;, 2, create_default_indexes=&amp;gt;FALSE);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;创建索引&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;CREATE INDEX ON rides (vendor_id, pickup_datetime desc);  
CREATE INDEX ON rides (pickup_datetime desc, vendor_id);  
CREATE INDEX ON rides (rate_code, pickup_datetime DESC);  
CREATE INDEX ON rides (passenger_count, pickup_datetime desc);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;4、导入测试数据&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;psql -c &amp;quot;\COPY rides FROM nyc_data_rides.csv CSV&amp;quot;  
COPY 10906858&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;5、对已转换为时序存储表的rides执行一些测试SQL，性能比PostgreSQL普通表要好。&lt;/p&gt;  &lt;p&gt;每天同车超过2人的交易，平均计费多少？&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;-- Average fare amount of rides with 2+ passengers by day  
  
SELECT date_trunc(&amp;apos;day&amp;apos;, pickup_datetime) as day, avg(fare_amount)  
  FROM rides  
  WHERE passenger_count &amp;gt; 1 AND pickup_datetime &amp;lt; &amp;apos;2016-01-08&amp;apos;  
  GROUP BY day ORDER BY day;  
  
        day         |         avg  
--------------------+---------------------  
2016-01-01 00:00:00 | 13.3990821679715529  
2016-01-02 00:00:00 | 13.0224687415181399  
2016-01-03 00:00:00 | 13.5382068607068607  
2016-01-04 00:00:00 | 12.9618895561740149  
2016-01-05 00:00:00 | 12.6614611935518309  
2016-01-06 00:00:00 | 12.5775245695086098  
2016-01-07 00:00:00 | 12.5868802584437019  
(7 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;6、某些查询的性能甚至超过20倍&lt;/p&gt;  &lt;p&gt;每天有多少笔交易。&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;-- Total number of rides by day for first 5 days  
  
SELECT date_trunc(&amp;apos;day&amp;apos;, pickup_datetime) as day, COUNT(*) FROM rides  
  GROUP BY day ORDER BY day  
  LIMIT 5;  
  
        day         | count  
--------------------+--------  
2016-01-01 00:00:00 | 345037  
2016-01-02 00:00:00 | 312831  
2016-01-03 00:00:00 | 302878  
2016-01-04 00:00:00 | 316171  
2016-01-05 00:00:00 | 343251  
(5 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;timescale增加了merge append的执行优化，因此在时间片上按小粒度聚合，效率非常高，数据量越大，性能提升的效果越明显。&lt;/p&gt;  &lt;p&gt;For example, TimescaleDB introduces a time-based “merge append” optimization to minimize the number of    &lt;br /&gt;groups which must be processed to execute the following (given its knowledge that time is already ordered).&lt;/p&gt;  &lt;p&gt;For our 100M row table, this results in query latency that is 396x faster than PostgreSQL (82ms vs. 32566ms).&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;SELECT date_trunc(&amp;apos;minute&amp;apos;, time) AS minute, max(usage_user)  
  FROM cpu  
  WHERE time &amp;lt; &amp;apos;2017-01-01&amp;apos;  
  GROUP BY minute  
  ORDER BY minute DESC  
  LIMIT 5;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;7、执行一些timescaleDB特有的功能函数，例如time_bucket，这里同样会用到timescaleDB内置的一些加速算法。&lt;/p&gt;  &lt;p&gt;每5分钟间隔为一个BUCKET，输出每个间隔产生了多少笔订单。&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;-- Number of rides by 5 minute intervals  
--   (using the TimescaleDB &amp;quot;time_bucket&amp;quot; function)  
  
SELECT time_bucket(&amp;apos;5 minute&amp;apos;, pickup_datetime) as five_min, count(*)  
  FROM rides  
  WHERE pickup_datetime &amp;lt; &amp;apos;2016-01-01 02:00&amp;apos;  
  GROUP BY five_min ORDER BY five_min;  
  
      five_min       | count  
---------------------+-------  
 2016-01-01 00:00:00 |   703  
 2016-01-01 00:05:00 |  1482  
 2016-01-01 00:10:00 |  1959  
 2016-01-01 00:15:00 |  2200  
 2016-01-01 00:20:00 |  2285  
 2016-01-01 00:25:00 |  2291  
 2016-01-01 00:30:00 |  2349  
 2016-01-01 00:35:00 |  2328  
 2016-01-01 00:40:00 |  2440  
 2016-01-01 00:45:00 |  2372  
 2016-01-01 00:50:00 |  2388  
 2016-01-01 00:55:00 |  2473  
 2016-01-01 01:00:00 |  2395  
 2016-01-01 01:05:00 |  2510  
 2016-01-01 01:10:00 |  2412  
 2016-01-01 01:15:00 |  2482  
 2016-01-01 01:20:00 |  2428  
 2016-01-01 01:25:00 |  2433  
 2016-01-01 01:30:00 |  2337  
 2016-01-01 01:35:00 |  2366  
 2016-01-01 01:40:00 |  2325  
 2016-01-01 01:45:00 |  2257  
 2016-01-01 01:50:00 |  2316  
 2016-01-01 01:55:00 |  2250  
(24 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;8、执行一些统计分析SQL&lt;/p&gt;  &lt;p&gt;每个城市的打车交易量。&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;-- Join rides with rates to get more information on rate_code  
  
SELECT rates.description, COUNT(vendor_id) as num_trips FROM rides  
  JOIN rates on rides.rate_code = rates.rate_code  
  WHERE pickup_datetime &amp;lt; &amp;apos;2016-01-08&amp;apos;  
  GROUP BY rates.description ORDER BY rates.description;  
  
      description      | num_trips  
-----------------------+-----------  
 JFK                   |     54832  
 Nassau or Westchester |       967  
 Newark                |      4126  
 group ride            |        17  
 negotiated fare       |      7193  
 standard rate         |   2266401  
(6 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;某些城市2016年1月的打车统计（最长、短距离、平均人数、时长等）&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;-- Analysis of all JFK and EWR rides in Jan 2016  
  
SELECT rates.description, COUNT(vendor_id) as num_trips,  
    AVG(dropoff_datetime - pickup_datetime) as avg_trip_duration, AVG(total_amount) as avg_total,  
    AVG(tip_amount) as avg_tip, MIN(trip_distance) as min_distance, AVG(trip_distance) as avg_distance, MAX(trip_distance) as max_distance,  
    AVG(passenger_count) as avg_passengers  
  FROM rides  
  JOIN rates on rides.rate_code = rates.rate_code  
  WHERE rides.rate_code in (2,3) AND pickup_datetime &amp;lt; &amp;apos;2016-02-01&amp;apos;  
  GROUP BY rates.description ORDER BY rates.description;  
  
 description | num_trips | avg_trip_duration |      avg_total      |      avg_tip       | min_distance |    avg_distance     | max_distance |   avg_passengers  
-------------+-----------+-------------------+---------------------+--------------------+--------------+---------------------+--------------+--------------------  
 JFK         |    225019 | 00:45:46.822517   | 64.3278115181384683 | 7.3334228220728027 |         0.00 | 17.2602816651038357 |       221.00 | 1.7333869584346211  
 Newark      |     16822 | 00:35:16.157472   | 86.4633688027582927 | 9.5461657353465700 |         0.00 | 16.2706122934252764 |       177.23 | 1.7435501129473309  
(2 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;9、数据自动分片与执行计划&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;postgres=# \d+ rides  
                                                     Table &amp;quot;public.rides&amp;quot;  
        Column         |            Type             | Collation | Nullable | Default | Storage  | Stats target | Description   
-----------------------+-----------------------------+-----------+----------+---------+----------+--------------+-------------  
 vendor_id             | text                        |           |          |         | extended |              |   
 pickup_datetime       | timestamp without time zone |           | not null |         | plain    |              |   
 dropoff_datetime      | timestamp without time zone |           | not null |         | plain    |              |   
 passenger_count       | numeric                     |           |          |         | main     |              |   
 trip_distance         | numeric                     |           |          |         | main     |              |   
 pickup_longitude      | numeric                     |           |          |         | main     |              |   
 pickup_latitude       | numeric                     |           |          |         | main     |              |   
 rate_code             | integer                     |           |          |         | plain    |              |   
 dropoff_longitude     | numeric                     |           |          |         | main     |              |   
 dropoff_latitude      | numeric                     |           |          |         | main     |              |   
 payment_type          | integer                     |           |          |         | plain    |              |   
 fare_amount           | numeric                     |           |          |         | main     |              |   
 extra                 | numeric                     |           |          |         | main     |              |   
 mta_tax               | numeric                     |           |          |         | main     |              |   
 tip_amount            | numeric                     |           |          |         | main     |              |   
 tolls_amount          | numeric                     |           |          |         | main     |              |   
 improvement_surcharge | numeric                     |           |          |         | main     |              |   
 total_amount          | numeric                     |           |          |         | main     |              |   
Indexes:  
    &amp;quot;rides_passenger_count_pickup_datetime_idx&amp;quot; btree (passenger_count, pickup_datetime DESC)  
    &amp;quot;rides_pickup_datetime_vendor_id_idx&amp;quot; btree (pickup_datetime DESC, vendor_id)  
    &amp;quot;rides_rate_code_pickup_datetime_idx&amp;quot; btree (rate_code, pickup_datetime DESC)  
    &amp;quot;rides_vendor_id_pickup_datetime_idx&amp;quot; btree (vendor_id, pickup_datetime DESC)  
Child tables: _timescaledb_internal._hyper_1_1_chunk,  
              _timescaledb_internal._hyper_1_2_chunk,  
              _timescaledb_internal._hyper_1_3_chunk,  
              _timescaledb_internal._hyper_1_4_chunk  
  
其中一个分片的约束如下  
Check constraints:  
    &amp;quot;constraint_1&amp;quot; CHECK (pickup_datetime &amp;gt;= &amp;apos;2015-12-31 00:00:00&amp;apos;::timestamp without time zone AND pickup_datetime &amp;lt; &amp;apos;2016-01-30 00:00:00&amp;apos;::timestamp without time zone)  
    &amp;quot;constraint_2&amp;quot; CHECK (_timescaledb_internal.get_partition_hash(payment_type) &amp;gt;= 1073741823)  
Inherits: rides&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;-- Peek behind the scenes  
  
postgres=#  select count(*) from rides;  
  count     
----------  
 10906858  
(1 row)  
  
Time: 376.247 ms  
postgres=# explain select count(*) from rides;  
                                                 QUERY PLAN                                                   
------------------------------------------------------------------------------------------------------------  
 Finalize Aggregate  (cost=254662.23..254662.24 rows=1 width=8)  
   -&amp;gt;  Gather  (cost=254661.71..254662.22 rows=5 width=8)  
         Workers Planned: 5  
         -&amp;gt;  Partial Aggregate  (cost=253661.71..253661.72 rows=1 width=8)  
               -&amp;gt;  Append  (cost=0.00..247468.57 rows=2477258 width=0)  
                     -&amp;gt;  Parallel Seq Scan on rides  (cost=0.00..0.00 rows=1 width=0)  
                     -&amp;gt;  Parallel Seq Scan on _hyper_1_1_chunk  (cost=0.00..77989.57 rows=863657 width=0)  
                     -&amp;gt;  Parallel Seq Scan on _hyper_1_2_chunk  (cost=0.00..150399.01 rows=1331101 width=0)  
                     -&amp;gt;  Parallel Seq Scan on _hyper_1_3_chunk  (cost=0.00..6549.75 rows=112675 width=0)  
                     -&amp;gt;  Parallel Seq Scan on _hyper_1_4_chunk  (cost=0.00..12530.24 rows=169824 width=0)  
(10 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;10、也可以直接查分片&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;postgres=# select count(*) from  _timescaledb_internal._hyper_1_1_chunk;  
  count    
---------  
 3454961  
(1 row)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h3&gt;分片对用户完全透明&lt;/h3&gt;  &lt;p&gt;分片元数据：&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;postgres=# \dn  
         List of schemas  
         Name          |  Owner     
-----------------------+----------  
 _timescaledb_cache    | postgres  
 _timescaledb_catalog  | postgres  
 _timescaledb_internal | postgres  
 public                | postgres  
(4 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h2&gt;timescaleDB + PostGIS 双剑合璧 - 时空数据库&lt;/h2&gt;  &lt;p&gt;结合时序数据库timescaleDB插件，空间数据库PostGIS插件。PostgreSQL可以很好的处理空间数据。&lt;/p&gt;  &lt;p&gt;1、创建空间数据库PostGIS创建&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;create extension postgis;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;2、添加空间类型字段&lt;/p&gt;  &lt;p&gt;http://postgis.net/docs/manual-2.4/AddGeometryColumn.html&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;postgres=# SELECT AddGeometryColumn (&amp;apos;public&amp;apos;,&amp;apos;rides&amp;apos;,&amp;apos;pickup_geom&amp;apos;,2163,&amp;apos;POINT&amp;apos;,2);  
                   addgeometrycolumn                      
--------------------------------------------------------  
 public.rides.pickup_geom  SRID:2163 TYPE:POINT DIMS:2   
(1 row)  
  
postgres=# SELECT AddGeometryColumn (&amp;apos;public&amp;apos;,&amp;apos;rides&amp;apos;,&amp;apos;dropoff_geom&amp;apos;,2163,&amp;apos;POINT&amp;apos;,2);  
                    addgeometrycolumn                      
---------------------------------------------------------  
 public.rides.dropoff_geom  SRID:2163 TYPE:POINT DIMS:2   
(1 row)  
  
postgres=#   
postgres=# \d+ rides  
                                                     Table &amp;quot;public.rides&amp;quot;  
        Column         |            Type             | Collation | Nullable | Default | Storage  | Stats target | Description   
-----------------------+-----------------------------+-----------+----------+---------+----------+--------------+-------------  
 vendor_id             | text                        |           |          |         | extended |              |   
 pickup_datetime       | timestamp without time zone |           | not null |         | plain    |              |   
 dropoff_datetime      | timestamp without time zone |           | not null |         | plain    |              |   
 passenger_count       | numeric                     |           |          |         | main     |              |   
 trip_distance         | numeric                     |           |          |         | main     |              |   
 pickup_longitude      | numeric                     |           |          |         | main     |              |   
 pickup_latitude       | numeric                     |           |          |         | main     |              |   
 rate_code             | integer                     |           |          |         | plain    |              |   
 dropoff_longitude     | numeric                     |           |          |         | main     |              |   
 dropoff_latitude      | numeric                     |           |          |         | main     |              |   
 payment_type          | integer                     |           |          |         | plain    |              |   
 fare_amount           | numeric                     |           |          |         | main     |              |   
 extra                 | numeric                     |           |          |         | main     |              |   
 mta_tax               | numeric                     |           |          |         | main     |              |   
 tip_amount            | numeric                     |           |          |         | main     |              |   
 tolls_amount          | numeric                     |           |          |         | main     |              |   
 improvement_surcharge | numeric                     |           |          |         | main     |              |   
 total_amount          | numeric                     |           |          |         | main     |              |   
 pickup_geom           | geometry(Point,2163)        |           |          |         | main     |              |   
 dropoff_geom          | geometry(Point,2163)        |           |          |         | main     |              |   
Indexes:  
    &amp;quot;rides_passenger_count_pickup_datetime_idx&amp;quot; btree (passenger_count, pickup_datetime DESC)  
    &amp;quot;rides_pickup_datetime_vendor_id_idx&amp;quot; btree (pickup_datetime DESC, vendor_id)  
    &amp;quot;rides_rate_code_pickup_datetime_idx&amp;quot; btree (rate_code, pickup_datetime DESC)  
    &amp;quot;rides_vendor_id_pickup_datetime_idx&amp;quot; btree (vendor_id, pickup_datetime DESC)  
Child tables: _timescaledb_internal._hyper_1_1_chunk,  
              _timescaledb_internal._hyper_1_2_chunk,  
              _timescaledb_internal._hyper_1_3_chunk,  
              _timescaledb_internal._hyper_1_4_chunk&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;3、将数据更新到geometry字段（实际存储为两个自动，分别表示经度和纬度。实际上不更新也没关系，因为PG支持表达式索引，完全可以使用这两个字段，创建表达式空间索引）。&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;-- Generate the geometry points and write to table  
--   (Note: These calculations might take a few mins)  
  
UPDATE rides SET pickup_geom = ST_Transform(ST_SetSRID(ST_MakePoint(pickup_longitude,pickup_latitude),4326),2163);  
UPDATE rides SET dropoff_geom = ST_Transform(ST_SetSRID(ST_MakePoint(dropoff_longitude,dropoff_latitude),4326),2163);  
  
  
vacuum full rides;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;4、时空分析举例。&lt;/p&gt;  &lt;p&gt;在(lat, long) (40.7589,-73.9851)附近400米范围内，每30分钟有多少辆车被叫（以上车位置来计算）。&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;-- Number of rides on New Years Eve originating within  
--   400m of Times Square, by 30 min buckets  
--   Note: Times Square is at (lat, long) (40.7589,-73.9851)  
  
SELECT time_bucket(&amp;apos;30 minutes&amp;apos;, pickup_datetime) AS thirty_min, COUNT(*) AS near_times_sq  
  FROM rides  
  WHERE ST_Distance(pickup_geom, ST_Transform(ST_SetSRID(ST_MakePoint(-73.9851,40.7589),4326),2163)) &amp;lt; 400  
    AND pickup_datetime &amp;lt; &amp;apos;2016-01-01 14:00&amp;apos;  
  GROUP BY thirty_min ORDER BY thirty_min;  
  
     thirty_min      | near_times_sq  
---------------------+--------------  
 2016-01-01 00:00:00 |      74  
 2016-01-01 00:30:00 |     102  
 2016-01-01 01:00:00 |     120  
 2016-01-01 01:30:00 |      98  
 2016-01-01 02:00:00 |     112  
 2016-01-01 02:30:00 |     109  
 2016-01-01 03:00:00 |     163  
 2016-01-01 03:30:00 |     181  
 2016-01-01 04:00:00 |     214  
 2016-01-01 04:30:00 |     185  
 2016-01-01 05:00:00 |     158  
 2016-01-01 05:30:00 |     113  
 2016-01-01 06:00:00 |     102  
 2016-01-01 06:30:00 |      91  
 2016-01-01 07:00:00 |      88  
 2016-01-01 07:30:00 |      58  
 2016-01-01 08:00:00 |      72  
 2016-01-01 08:30:00 |      94  
 2016-01-01 09:00:00 |     115  
 2016-01-01 09:30:00 |     118  
 2016-01-01 10:00:00 |     135  
 2016-01-01 10:30:00 |     160  
 2016-01-01 11:00:00 |     212  
 2016-01-01 11:30:00 |     229  
 2016-01-01 12:00:00 |     244  
 2016-01-01 12:30:00 |     230  
 2016-01-01 13:00:00 |     235  
 2016-01-01 13:30:00 |     238&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h2&gt;实例2 - 传感器数据、天气数据&lt;/h2&gt;  &lt;p&gt;http://docs.timescale.com/v0.8/tutorials/other-sample-datasets&lt;/p&gt;  &lt;p&gt;不再赘述。&lt;/p&gt;  &lt;h2&gt;timescaleDB 常用API&lt;/h2&gt;  &lt;p&gt;http://docs.timescale.com/v0.8/api&lt;/p&gt;  &lt;h3&gt;1、创建时序表&lt;/h3&gt;  &lt;p&gt;create_hypertable()&lt;/p&gt;  &lt;p&gt;Required Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;main_table&lt;/td&gt;      &lt;td&gt;Identifier of table to convert to hypertable&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;time_column_name&lt;/td&gt;      &lt;td&gt;Name of the column containing time values&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;Optional Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;partitioning_column&lt;/td&gt;      &lt;td&gt;Name of an additional column to partition by. If provided, number_partitions must be set.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;number_partitions&lt;/td&gt;      &lt;td&gt;Number of hash partitions to use for partitioning_column when this optional argument is supplied. Must be &amp;gt; 0.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;chunk_time_interval&lt;/td&gt;      &lt;td&gt;Interval in event time that each chunk covers. Must be &amp;gt; 0. Default is 1 month.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;create_default_indexes&lt;/td&gt;      &lt;td&gt;Boolean whether to create default indexes on time/partitioning columns. Default is TRUE.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;if_not_exists&lt;/td&gt;      &lt;td&gt;Boolean whether to print warning if table already converted to hypertable or raise exception. Default is FALSE.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;partitioning_func&lt;/td&gt;      &lt;td&gt;The function to use for calculating a value’s partition.&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h3&gt;2、添加多级分片字段&lt;/h3&gt;  &lt;p&gt;支持hash和interval分片&lt;/p&gt;  &lt;p&gt;add_dimension()&lt;/p&gt;  &lt;p&gt;Required Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;main_table&lt;/td&gt;      &lt;td&gt;Identifier of hypertable to add the dimension to.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;column_name&lt;/td&gt;      &lt;td&gt;Name of the column to partition by.&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;Optional Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;number_partitions&lt;/td&gt;      &lt;td&gt;Number of hash partitions to use on column_name. Must be &amp;gt; 0.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;interval_length&lt;/td&gt;      &lt;td&gt;Interval that each chunk covers. Must be &amp;gt; 0.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;partitioning_func&lt;/td&gt;      &lt;td&gt;The function to use for calculating a value’s partition (see create_hypertable instructions).&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h3&gt;3、删除分片&lt;/h3&gt;  &lt;p&gt;删除指定 时间点、多久 之前的分片&lt;/p&gt;  &lt;p&gt;drop_chunks()&lt;/p&gt;  &lt;p&gt;Required Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;older_than&lt;/td&gt;      &lt;td&gt;Timestamp of cut-off point for data to be dropped, i.e., anything older than this should be removed.&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;Optional Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;table_name&lt;/td&gt;      &lt;td&gt;Hypertable name from which to drop chunks. If not supplied, all hypertables are affected.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;schema_name&lt;/td&gt;      &lt;td&gt;Schema name of the hypertable from which to drop chunks. Defaults to public.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;cascade&lt;/td&gt;      &lt;td&gt;Boolean on whether to CASCADE the drop on chunks, therefore removing dependent objects on chunks to be removed. Defaults to FALSE.&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h3&gt;4、设置分片时间区间&lt;/h3&gt;  &lt;p&gt;set_chunk_time_interval()&lt;/p&gt;  &lt;p&gt;Required Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;main_table&lt;/td&gt;      &lt;td&gt;Identifier of hypertable to update interval for.&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;chunk_time_interval&lt;/td&gt;      &lt;td&gt;Interval in event time that each new chunk covers. Must be &amp;gt; 0.&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h3&gt;5、分析函数 - 第一条&lt;/h3&gt;  &lt;p&gt;first()&lt;/p&gt;  &lt;p&gt;Required Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;value&lt;/td&gt;      &lt;td&gt;The value to return (anyelement)&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;time&lt;/td&gt;      &lt;td&gt;The timestamp to use for comparison (TIMESTAMP/TIMESTAMPTZ or integer type)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;例如，查找所有传感器的最早上传的温度值。&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;SELECT device_id, first(temp, time)  
  FROM metrics  
  GROUP BY device_id;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;使用递归亦可实现：&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://billtian.github.io/digoal.blog/2018/01/201705/20170519_01.md"&gt;《PostgrSQL 递归SQL的几个应用 - 极客与正常人的思维》&lt;/a&gt;&lt;/p&gt;  &lt;h3&gt;6、分析函数 - 最后一条&lt;/h3&gt;  &lt;p&gt;last()&lt;/p&gt;  &lt;p&gt;Required Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;value&lt;/td&gt;      &lt;td&gt;The value to return (anyelement)&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;time&lt;/td&gt;      &lt;td&gt;The timestamp to use for comparison (TIMESTAMP/TIMESTAMPTZ or integer type)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;例如，查找每5分钟时间区间内，每个传感器的最新温度值&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;SELECT device_id, time_bucket(&amp;apos;5 minutes&amp;apos;, time) as interval,  
  last(temp, time)  
  FROM metrics  
  WHERE time &amp;gt; now () - interval &amp;apos;1 day&amp;apos;  
  GROUP BY device_id, interval  
  ORDER BY interval DESC;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;使用递归亦可实现：&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://billtian.github.io/digoal.blog/2018/01/201705/20170519_01.md"&gt;《PostgrSQL 递归SQL的几个应用 - 极客与正常人的思维》&lt;/a&gt;&lt;/p&gt;  &lt;h3&gt;7、分析函数 - 柱状图&lt;/h3&gt;  &lt;p&gt;histogram()&lt;/p&gt;  &lt;p&gt;Required Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;value&lt;/td&gt;      &lt;td&gt;A set of values to partition into a histogram&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;min&lt;/td&gt;      &lt;td&gt;The histogram’s lower bound used in bucketing&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;max&lt;/td&gt;      &lt;td&gt;The histogram’s upper bound used in bucketing&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;nbuckets&lt;/td&gt;      &lt;td&gt;The integer value for the number of histogram buckets (partitions)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;例如，&lt;/p&gt;  &lt;p&gt;电池电量20到60，均分为5个BUCKET区间，返回5+2个值的数组（表示每个bucket区间的记录数），头尾分为别为边界外的记录数有多少。&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;SELECT device_id, histogram(battery_level, 20, 60, 5)  
  FROM readings  
  GROUP BY device_id  
  LIMIT 10;  
  
 device_id  |          histogram  
------------+------------------------------  
 demo000000 | {0,0,0,7,215,206,572}  
 demo000001 | {0,12,173,112,99,145,459}  
 demo000002 | {0,0,187,167,68,229,349}  
 demo000003 | {197,209,127,221,106,112,28}  
 demo000004 | {0,0,0,0,0,39,961}  
 demo000005 | {12,225,171,122,233,80,157}  
 demo000006 | {0,78,176,170,8,40,528}  
 demo000007 | {0,0,0,126,239,245,390}  
 demo000008 | {0,0,311,345,116,228,0}  
 demo000009 | {295,92,105,50,8,8,442}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h3&gt;8、分析函数 - 时间区间&lt;/h3&gt;  &lt;p&gt;类似date_trunc，但是更强大，可以用任意interval进行时间截断。方便用户使用。&lt;/p&gt;  &lt;p&gt;time_bucket()&lt;/p&gt;  &lt;p&gt;Required Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;bucket_width&lt;/td&gt;      &lt;td&gt;A PostgreSQL time interval for how long each bucket is (interval)&lt;/td&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;time&lt;/td&gt;      &lt;td&gt;The timestamp to bucket (timestamp/timestamptz/date)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;Optional Arguments&lt;/p&gt;  &lt;table&gt;    &lt;tr&gt;      &lt;th&gt;Name&lt;/th&gt;      &lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;offset&lt;/td&gt;      &lt;td&gt;The time interval to offset all buckets by (interval)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h3&gt;9、数据概貌查看函数 - 时序表概貌&lt;/h3&gt;  &lt;p&gt;hypertable_relation_size_pretty()&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;SELECT * FROM hypertable_relation_size_pretty(&amp;apos;conditions&amp;apos;);  
  
 table_size | index_size | toast_size | total_size  
------------+------------+------------+------------  
 1171 MB    | 1608 MB    | 176 kB     | 2779 MB&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h3&gt;10、数据概貌查看函数 - 分片大小&lt;/h3&gt;  &lt;p&gt;chunk_relation_size_pretty()&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;SELECT * FROM chunk_relation_size_pretty(&amp;apos;conditions&amp;apos;);  
  
                chunk_table                 | table_size | index_size | total_size  
---------------------------------------------+------------+------------+------------  
 &amp;quot;_timescaledb_internal&amp;quot;.&amp;quot;_hyper_1_1_chunk&amp;quot;  | 28 MB      | 36 MB      | 64 MB  
 &amp;quot;_timescaledb_internal&amp;quot;.&amp;quot;_hyper_1_2_chunk&amp;quot;  | 57 MB      | 78 MB      | 134 MB  
 ...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h3&gt;11、数据概貌查看函数 - 索引大小&lt;/h3&gt;  &lt;p&gt;indexes_relation_size_pretty()&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;SELECT * FROM indexes_relation_size_pretty(&amp;apos;conditions&amp;apos;);  
  
             index_name_              | total_size  
--------------------------------------+------------  
 public.conditions_device_id_time_idx | 1143 MB  
 public.conditions_time_idx           | 465 MB&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h3&gt;12、导出时序元数据&lt;/h3&gt;  &lt;p&gt;https://raw.githubusercontent.com/timescale/timescaledb/master/scripts/dump_meta_data.sql&lt;/p&gt;  &lt;div&gt;    &lt;div&gt;      &lt;pre&gt;        &lt;code&gt;psql [your connect flags] -d your_timescale_db &amp;lt; dump_meta_data.sql &amp;gt; dumpfile.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h2&gt;小结&lt;/h2&gt;  &lt;p&gt;timescaleDB是一个非常好用的时序数据处理插件，隐藏了分片逻辑（对用户透明），同时提供了大量的API函数接口，以及性能优化。在时序场景使用很赞。&lt;/p&gt;  &lt;p&gt;结合PostGIS插件，PostgreSQL在时空处理这块，如虎添翼。&lt;/p&gt;  &lt;h2&gt;参考&lt;/h2&gt;  &lt;p&gt;    &lt;a href="https://billtian.github.io/digoal.blog/2018/01/201711/20171102_02.md"&gt;《PostgreSQL 按需切片的实现(TimescaleDB插件自动切片功能的plpgsql schemaless实现)》&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://billtian.github.io/digoal.blog/2018/01/201704/20170409_05.md"&gt;《时序数据库有哪些特点? TimescaleDB时序数据库介绍》&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://billtian.github.io/digoal.blog/2018/01/201611/20161121_01.md"&gt;《PostgreSQL on Linux 最佳部署手册》&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;http://docs.timescale.com/v0.8/tutorials/tutorial-hello-nyc&lt;/p&gt;  &lt;p&gt;http://docs.timescale.com/v0.8/introduction&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://billtian.github.io/digoal.blog/2018/01/201705/20170519_01.md"&gt;《PostgrSQL 递归SQL的几个应用 - 极客与正常人的思维》&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;    &lt;a href="http://info.flagcounter.com/h9V1" rel="nofollow"&gt;      &lt;img alt="Flag Counter" border="0" src="https://s03.flagcounter.com/count/h9V1/bg_FFFFFF/txt_000000/border_CCCCCC/columns_2/maxflags_12/viewers_0/labels_0/pageviews_0/flags_0/"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;h2&gt;    &lt;a href="https://github.com/digoal/blog/blob/master/README.md" title=""&gt;digoal’s 大量PostgreSQL文章入口&lt;/a&gt;&lt;/h2&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61289-postgresql-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Tue, 30 Mar 2021 14:13:02 CST</pubDate>
    </item>
    <item>
      <title>公有云运维福利：开源监控小工具 Open-Falcon 插件 cloud-mon</title>
      <link>https://itindex.net/detail/59419-%E8%BF%90%E7%BB%B4-%E7%A6%8F%E5%88%A9-%E5%BC%80%E6%BA%90</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;本文介绍了一款基于Open-Falcon的公有云运维监控工具及使用说明。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;上篇文章回顾：&lt;/strong&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzUxMDQxMDMyNg==&amp;mid=2247485300&amp;idx=1&amp;sn=de32b40a0f48187f30051149e3cc1e2d&amp;chksm=f9022c4dce75a55bb129824594061132b3f70c6654c2e32ceab977613f212572b6d3dbed46e6&amp;scene=21#wechat_redirect" target="_blank"&gt;使用python实现简单的共享锁和排他锁&lt;/a&gt;&lt;/p&gt;  &lt;br /&gt;背景  &lt;p&gt;当你成为公有云的一名管理员，权限和安全之间的权衡便成为了始终萦绕在我们身边的一个话题。每天在我们专心解决问题，或者code的时候，一会儿有人来找你：能不能帮我们看看××× 机器所在的NAT网关带宽多大呀？目前使用量是什么情况呀？一会儿有人问：我们在做业务升级，帮我们关注下LB的流量情况呀？我们现在S3的增长趋势是什么样子的呀？&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;方案调研  &lt;p&gt;崩溃一秒钟，这种没有“含金量”的事情，怎么能浪费我们的时间呢？毕竟程序员的使命是要拯救地球的，再不济也得做一个优秀的产品。可是，问题来了。机器的监控好做，可以装Agent、Zabbix、Open-Falcon等。这些东西怎么监控呢？而且公有云有很多：什么AWS、Azure 、阿里云、金山云等，自己重新开发一个监控系统么？&lt;/p&gt;  &lt;p&gt;额，这个工作量有点大。干活当然得选最简单，高效，又合理的方案啦，哈哈。&lt;/p&gt;  &lt;p&gt;小米内部都是用Open-Falcon做监控。Open-Falcon的GitHub地址：&lt;/p&gt;  &lt;p&gt;https://github.com/open-falcon&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;Open-Falcon是基于Go开发的一款快速上手、高可用的监控系统，支持自定义Dashboard报警，可以用来监控机器各方面指标，还可以自定义插件，上报数据，获取自己想要的信息。&lt;/p&gt;  &lt;p&gt;Open-Falcon是小米早期的开源工具，星星多，社区活跃，还支持插件。恩，显示、存储、画图表、保持数据有序，都交给TA了。方案已定，写个Open-Falcon插件。   &lt;br /&gt;&lt;/p&gt;架构设计  &lt;p&gt;说了这么久，先上架构图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;需要支持的云：&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;（1）AWS&lt;/p&gt;  &lt;p&gt;（2）阿里云&lt;/p&gt;  &lt;p&gt;（3）金山云&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;监控的资源范围：&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;（1）ELB&lt;/p&gt;  &lt;p&gt;（2）EIP&lt;/p&gt;  &lt;p&gt;（3）NAT网关&lt;/p&gt;  &lt;p&gt;（4）专线&lt;/p&gt;  &lt;p&gt;每个资源都取哪些监控指标呢？当然全部取啦！控制台有啥就取啥。&lt;/p&gt;  &lt;p&gt;具体指标信息和含义请自行参照官网：&lt;/p&gt;  &lt;p&gt;金山云：&lt;/p&gt;  &lt;p&gt;https://docs.ksyun.com/documents/42&lt;/p&gt;  &lt;p&gt;阿里云：&lt;/p&gt;  &lt;p&gt;https://help.aliyun.com/product/28572.html?spm=a2c4g.750001.list.212.49707b13EZbdDR&lt;/p&gt;  &lt;p&gt;AWS：&lt;/p&gt;  &lt;p&gt;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CW_Support_For_AWS.html&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;代码思路：&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;通过读取配置文件，获取资源类型、监控指标、地区信息和账号信息；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;获取相应资源的所有列表；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;获取列表的相关监控信息；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;生成Open-Falcon识别的格式数据，推送到Open-Falcon。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;使用指北  &lt;p&gt;   &lt;strong&gt;该工具已经作为Open-Falcon的插件开源&lt;/strong&gt;，开源地址：&lt;/p&gt;  &lt;p&gt;https://github.com/open-falcon/cloud-mon&lt;/p&gt;  &lt;p&gt;具体实现代码以及参考文档可以去github查看。&lt;/p&gt;  &lt;p&gt;首先把代码clone下来，按照你们的实际情况配置文件，就可以使用啦。&lt;/p&gt;  &lt;p&gt;配置文件实例：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;cloud下面是一个list，可以写的很长很长，想配什么配什么。下面是各项参数说明：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;至此，小工具分享完啦，希望帮助大家提高效率。&lt;/p&gt;  &lt;p&gt;完&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;这么好用的工具，在看一下呗~&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/59419-%E8%BF%90%E7%BB%B4-%E7%A6%8F%E5%88%A9-%E5%BC%80%E6%BA%90</guid>
      <pubDate>Thu, 04 Apr 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>苏宁 Android App 插件化应用实践</title>
      <link>https://itindex.net/detail/58572-%E8%8B%8F%E5%AE%81-android-app</link>
      <description>&lt;div&gt;  &lt;p&gt;“&lt;/p&gt;  &lt;p&gt;从大团队并肩作战到小团队带头冲锋，高效的研发模式使得 App 本身的整体崩溃率始终维持在 0.02% 以下。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;简介&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;从大团队并肩作战到小团队带头冲锋，高效的研发模式使得 App 本身的整体崩溃率始终维持在 0.02% 以下。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;本着以用户为中心、以开发者为出发点，根据现有开源方案取长补短，苏宁易购移动开发部于 2017 年初自主研发出了新型插件化技术——APNP（Android Plugin And Play），旨在让研发更敏捷，让发布更灵活，最终满足用户对产品的极速体验、按需下载、动态更新。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;需求分析&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;技术的引入来自于实际业务场景对技术的需求，插件化亦是如此，那么到底是什么原因推动了苏宁易购 App 的插件化，又是什么原因让苏宁易购开发者走上自研插件化的道路？&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;为什么苏宁易购 App 需要插件化？&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;发布周期长，产品迭代跟不上市场需求&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;对于一个电商 App，不同的时间、地点，伴随着用户千变万化、稍纵即逝的消费需求，谁能在第一时间满足这些需求，谁就能把握住需求带来的销量。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;而传统的 App 开发模式周期过长，我们需要更敏捷的发布方案，所以我们做了插件化，这也是苏宁易购对插件化最原始的需求。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;单线研发，管理、协作成本过高&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;随着苏宁易购业务的不断拓展、项目参与人员数量的增多，单线 App 开发模式所隐藏的问题日益凸显：一面从需求分析到研发测试，需要监管的内容越来越多；另一方面从方案决策到流程审批，协作沟通越来越频繁，成本越来越高。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;因此我们需要多线、小团队的研发模式，这让我们进一步确认了插件化。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;安装包过大，运营推广效率走低&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;需求在日益增长的同时，安装包体积也在同步膨胀。面对耗时、耗流量的安装包下载，新用户体验、老用户升级的阻力也越来越大。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;为了解决这个问题，我们需要拆包、需要动态下载、需要局部更新，因此我们正式引入了插件化。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;什么选择自研而不是使用开源方案？&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;没有完美无暇的开源方案，却有层出不穷的接入问题&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;移动团队在一开始选择过几种开源方案，个别方案的可用性也比较高，但是在接入之后，测试环节总会出现些疑难杂症，修复起来相对困难，一方面是源码本身的掌握成本较高，另一方面就是开源方案本身存在的缺陷。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;要么另起炉灶，要么藕断丝连&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;现有的开源方案，要么就是对现有工程的改造较大，开发有心无力；要么就是插件工程和宿主工程相互依赖，牵一发而动全身，成本、风险都很高。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;插件方案选型&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;基于上述种种原因，我们决定自研插件化技术，于是就有了 APNP。APNP采用的插件方案是直接加载 APK 文件（APK 格式不变，内容有所修改），原因有如下几点：&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;兼容性高&lt;/p&gt;  &lt;p&gt;APK 文件的格式非常稳定，它包含一个 App 正常运行的必要资源；任何版本的 Android 系统，都应该正常解析并运行任意版本匹配的 APK 文件，无论这个 APK 文件是何时产生的；我们只要正确模仿系统加载 APK 的行为，就可以加载任意一个插件 APK 文件。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;研发隔离&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;既然是直接加载 APK 文件，所以在 APNP 的设计方案中，每个插件都是一个单独工程，这就意味着除了最后的集成测试阶段，插件对应的整个软件生命周期都是独立的，既降低了管理、协作成本，又促使插件产品的发布更加灵活。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;接入简单&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;既然是独立工程，无论现有的工程是如何运转的，开发者唯一要做的就是把插件工程从现有工程中抽离出来。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;核心手段及原理&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;如果直接加载一个原始插件的 APK 文件，绝大多数情况下是无法运行的，如 Class pre-verified 异常、ResourceNotFound 等。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;因此 APNP 在保持 APK 格式不变的情况下，对APK里面的内容做了针对性修改，核心手段如下：&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;共同 Dependency 剔除&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在研发过程中，经常需要第三方依赖 Library（Dependency），而当插件工程（以下简称 Plugin）和宿主工程（以下简称 Host）包含相同的 Dependency 时，就需要剔除 Plugin 中的 Dependency。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;这样一方面可以避免 Class pre-verified 异常，另一方面也可以减少插件包的大小，提升插件包的下载 &amp;amp; 加载速度。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;实现方案（以下都默认 IDE 为 Android Studio）：通过 Gradle 插件，在 Plugin 的 Transform 过程中，剔除与 Host 相同 Dependency 的所有资源。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;Package ID 修改&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在 Android 系统中，App 对应的 Package ID 是固定的，也就是说如果我们不人为干预，Plugin 和 Host 打包生产的 APK 文件中，所有 Resource ID 中的 Package ID 都是一样的，即（0x7f）。&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;这时如果直接加载 Plugin APK，必然会出现 Resource 类型不匹配、显示错乱等异常，所以我们修改 Plugin 的 APK 的 Package ID。（Package ID 满足 0x01 &amp;lt; PID &amp;lt; 0x7f 即可）&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;实现方案：业内关于修改 Package ID 的方案有很多（如修改 aapt 源码等），APNP 采用的方案是直接修改最后生成的 APK 文件。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;先看下一个简单 APK 的文件结构，如下图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;涉及 Package ID 修改的地方有 3 处：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;R.class（classes.dex）&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;resources.arsc&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;xml 文件&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;修改 R.class 中的 Package ID&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在 Gradle assemble 之后，会在 build/generated/source/r 文件下生成相应的 R.java 文件，如下图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;此时我们将 R 文件中的 0x7f 直接替换成目标 Package ID ，然后继续交给 Gradle 做后续操作。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;这样在最终的 classes.dex 中，里面的 R.class 文件对应的 Package ID 就是我们想要设置的 Package ID。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;修改 arsc + xml 中的 Package ID&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;不同于上面通过 Hook Gradle 过程修改中间生成的 R 文件，arsc + xml 则是直接解压最终的 APK 并修改目标文件，然后重新签名。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;首先我们需要对 APK 文件中的 arsc + xml 的文件格式有一定的认识，Android 为了充分减少 APK 自身的大小，在编译的过程中会对所有的资源进行重组 + 压缩。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;例如 values 文件下的内容会被统一收集到 arsc 文件中，而不再以文件的形式存在；而 xml 也不再是原始的 xml 文件，xml 中内容会被进一步整合 + 复用。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;而无论是 arsc 文件，还是 xml 文件，在它们内部都是   &lt;strong&gt;通过一个个固定数据结构的 ResChunk 以嵌套 + 组合的形式&lt;/strong&gt;各自存储着。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;资源信息：每段 ResChunk 的内容都以 8 字节的 ResChunk_header 开头，用于描述 ResChunk 的类型 + 长度，如下图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;也就是说 arsc + xml 中的所有内容都可以被反向解析出来，当充分了解每个 ResChunk 的数据结构以及掌握 ResChunk 中哪些内容是需要修改的 Package ID，就可以相对轻松的完成对二进制文件的修改。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt; Library Chunk 插入&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;修改完 Package ID 后，还需要在 resources.arsc 文件中插入一段 Library Chunk ，如下图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;Library Chunk 在 Android 5.0 之后才出现，对应的类型如下图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;那么为什么需要插入 Library Chunk？原因是在 Android 5.0 之后，ResTable在获取资源信息时，如果资源含有 Parent（ResTable_map_entry，如 style），会验证 Parent 的 Package ID 是否合法，也就是 Package ID 是不是已经注册过了。&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;如果 lookupResourceId 没有返回 NO_ERROR，则报错，继续跟进，如下图:&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;由于在上一环节中，我们已经把 Plugin 中的 Package ID 修改了，上图中的局部变量 packageId 肯定不会是 APP_PACKAGE_ID 。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;最后会在 mLookupTable 数组中寻找 packageId 是否有对应的值，如果没有（值为 0）则抛出异常。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;所以我们需要把自定义的 Package ID 注册到 mLookupTable 数组中，那么注册的动作是在什么时候发生的？&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在 App 启动并资源首次加载时，会调用下面的方法去解析 arsc 文件，如下图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;如果发现是 Library Chunk 会调用 addMapping，如下图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;在 addMapping 中，我们会把 Library Chunk 中的 packageId 在 mLookupTable 数组中进行注册（就是简单的赋值），注册之后我们自定义的 Package ID 才会被系统认可，从而确保含有 Parent 资源的正常解析。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt; Attr ID 替换&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;完成上面 3 步后，APK 就已经可以加载了，但是插件涉及到自定义属性的 View ，自定义属性总是无法正常赋值，这又是什么原因呢？&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;以 ConstraintLayout 为例，假如 Plugin 和 Host 的 Layout 布局中都包含 ConstraintLayout，并同时都设置了 ConstraintLayout 的自定义属性如下：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;那么在 App 运行的过程中，ConstraintLayout 会通过 android.support.constraint.R.class 获取解析自定义属性，如下图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;而 android.support.constraint.R.class 在内存中只有一份，并且会优先加载 Host，因此在实际的运行内存中，R.class 来自于 Host。&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;由于 Host 中 R.class 里面的 Package ID 都是 0x7f ，而在上面第二节的修改 Package ID 环节中，Plugin 里面的 xml 的 Package ID 已经被我们修改了（包括 xml 中所引用的 Attr ID）。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;因此当 Plugin 通过 Host 的 R.class 去解析 ConstrainLayout 时，必然会出现自定义属性无法正常解析的异常。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;所在 APNP 在修改 Plugin xml 文件 Package ID 的同时，如果发现 Attr 同样存在于 Host 中，会把 Plugin xml 文件中的 Attr ID 替换成 Host 中的 Attr ID，从而保证 Plugin 中所有的自定义属性能够正常解析（红框：0x7f01000f，0x7f010022，0x7f010026，0x7f01002b），如下图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;否则直接修改 Package ID（蓝框：0x7e010000，0x7e010001，0x7e010002）。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;实践成果&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在 APNP 实际应用之后，通过苏宁云迹平台的实时数据监控，并没有发现因 APNP 本身引起的明显异常。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;从大团队并肩作战到小团队带头冲锋，高效的研发模式也使得 App 本身的整体崩溃率始终维持在 0.02% 以下。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;相比之前的研发流程，APNP 带来的影响主要有下面几个点：&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;产品更精细、定位更明确 &lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;APNP 是产品精细划分和明确定位的技术支撑，小团队的研发模式，加快了产品的落实与升级。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;研发周期短、发布更灵活&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;插件可单独升级、可独立发布的特征，配合独立团队的专人专职，单一产品可以达到一周一版本。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;管理更轻松、研发更高效&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;同步产品线的精细划分，研发团队也趋于更小、更独立，管理也显得更加轻松；同时明确的责任分工，也促进了研发人员的工作效率。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;发展前景&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;Google 在今年的 IO 大会上推出了一种动态加载方案——App Bundles，可以让用户通过 Google Play 动态下载应用功能，而且 App Bundles 采用的方案也是直接加载 APK 文件。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;通过分析最终的 Feature Master APK 文件，你会发现 App Bundles 对 APK 做了和 APNP 相同的操作：剔除 Dependency、修改 Package ID、插入 Library Chunk、替换 Attr ID。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;这看似巧合实则必然，因为 App Bundles 和 APNP 都是直接加载 APK 文件，所以面对的问题都是一样的。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在方案上 APNP 可以说有了官方保障，而且 APNP 在后面还可以借鉴 App Bundles 的实现细节，让插件加载更加高效、安全。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;而由于 Android P 对隐藏 API 调用的限制，很多人认为插件化方案将不再可行。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;但是 Google 也同时提供了 light greylist，并且国内手机厂商肯定不允许让主流 App 无法使用的情况发生，所以我觉得在很长一段时间内，插件化依然可以正常运转。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;em&gt;作者：李呈武&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;   &lt;em&gt;简介：苏宁易购前端技术专家，资深Android 开发者，深度掌握 Android 虚拟机、插件化、Weex 等技术，熟悉移动网络的特质，对移动端的架构设计有独特的见解，一直致力于通过优秀的架构设计，减少开发成本，提升开发质量。&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;   &lt;em&gt;    &lt;em&gt;编辑：关崇、孙淑娟&lt;/em&gt;&lt;/em&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;em&gt;投稿：有投稿、寻求报道意向技术人请联络 editor@51cto.com&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;精彩文章推荐：&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MjM5ODI5Njc2MA==&amp;mid=2655817939&amp;idx=1&amp;sn=8250a92732b1323342eb89185f9b5253&amp;chksm=bd74dd048a035412a96dabfffb845bdb95200ee7ff4f2b8d4e1a2fa4a2365b13224dbc9d3f66&amp;scene=21#wechat_redirect" target="_blank"&gt;终于有人把Java内存模型说清楚了！&lt;/a&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MjM5ODI5Njc2MA==&amp;mid=2655817893&amp;idx=1&amp;sn=975de09cfb592d23eef8c26eb89391c8&amp;chksm=bd74dd728a03546461e8d6609e6f8232f686f7f83a230bbc925f0a3ed9060a7e914d11e8f3ea&amp;scene=21#wechat_redirect" target="_blank"&gt;一文深入了解：分布式系统中的缓存架构&lt;/a&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MjM5ODI5Njc2MA==&amp;mid=2655816424&amp;idx=1&amp;sn=a3ee4ee879af69f0cd3536585241748e&amp;chksm=bd74c73f8a034e29d244842788dce04d727174e3bf824c8261ce3a7a4e7787f184baf00856b2&amp;scene=21#wechat_redirect" target="_blank"&gt;新手也能看懂的监控报警系统架构设计&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/58572-%E8%8B%8F%E5%AE%81-android-app</guid>
      <pubDate>Wed, 01 Aug 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>12款堪称神器的 Chrome 插件，Max 你的工作效率！</title>
      <link>https://itindex.net/detail/57929-%E7%A5%9E%E5%99%A8-chrome-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;div&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;导语：好的工具插件是提高程序员生产力的法宝，本文介绍了 12 款Chrome 插件，非常高效实用，还附带下载地址， 果断收藏~      &lt;br /&gt;&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;文/ ABHIMANYU GHOSHAL&lt;/p&gt;&lt;/blockquote&gt;    &lt;blockquote&gt;      &lt;p&gt;译/ 蛤佛-1&lt;/p&gt;      &lt;p&gt;来源/ TNW&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;工欲善其事，必先利其器，程序员的日常工作根本就      &lt;strong&gt;离 不 开 Chrome 浏览器&lt;/strong&gt;（还不知道的请面壁思过130s）！其他功能优达菌就不啰嗦了，今天来说说 Chrome 如何提高你的生产力—— Chrome 支持      &lt;strong&gt;大量扩展插件&lt;/strong&gt;，能帮你短时间内完成更多工作，同时比一些 App 消耗更少的系统资源。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;今天介绍      &lt;strong&gt;12个 Chrome 插件&lt;/strong&gt;，不用关闭浏览器你就能：记笔记，规划日程，改善你的收件箱，Max 你的工作效率。      &lt;strong&gt;（注：这些插件在Opera 里也能使用）&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;写作与笔记类&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;无论你在准备报告，撰写文章还是听讲座，准备一款能随时上手的记笔插件，肯定有所帮助！&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;用来记笔记和待办事项的&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Google Keep&lt;/strong&gt;      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;Google Keep 快速，轻便，完全符合快速工作节奏的需求——      &lt;strong&gt;只需点击浏览器上的按钮即可快速添加笔记。&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;相对于 Evernote 和 Microsoft OneNote，我更喜欢 Keep：它不那么混乱，让我能专注于快速记下想法，而不是提供一大堆用于排版和外链的功能。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;这并不意味着 Keep 只有这一个特点：它能帮你保存页面上的图像和文字，你还能在上面作笔记。如果你打算写点什么，你可以随时打开，记下文本、列表和提醒。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;      &lt;strong&gt;Keep 的 Panel view 功能很受我的青睐，我可以开着标签页的同时，建个小窗放在旁边。&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;➤      &lt;strong&gt;Google Keep 获取链接：&lt;/strong&gt;https://chrome.google.com/webstore/detail/panel-view-for-keep/jccocffecajimkdjgfpjhlpiimcnadhb&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来头脑风暴的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Papier&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;Papier 是个超级简单的记笔记工具，      &lt;strong&gt;“随时待命”是它能胜过各种桌面应用的原因。&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;只要装上插件、打开一个新的标签页，一个即时记事本就出现在你面前。图能自动保存内容，支持基本的排版和快捷键。界面简洁明快，你能看到的就是一个空白页面，随时响应你的笔记和新想法，界面太刺眼的话还有夜晚模式可供选择。      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;因为它只是个单页的笔记本，没有文档储存功能，所以感觉更像是一个瞎记记思路的地方，而不能保存和检索单独的文档。 Papier 还提供基本的富文本格式，你可以用快捷键启动，转向基于 Web 的文本编辑器。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;虽然看起来功能轻便，但由于几乎隐形的界面，Papier 追求的就是让你把想法即时呈现出来。当你需要头脑风暴、快速记下稍纵即逝的想法的时候，就显得很有用，看着浏览器就能记笔记。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;➤      &lt;strong&gt;Papier获取链接&lt;/strong&gt;：https://www.google.com/search?q=Papier&amp;amp;oq=Papier&amp;amp;aqs=chrome..69i57j69i60&amp;amp;sourceid=chrome&amp;amp;ie=UTF-8&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;任务管理类&lt;/strong&gt;      &lt;strong&gt;&lt;/strong&gt;      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;有一大堆任务等着完成？不如在浏览器里列张表，一天下来你就能很方便的看到完成情况。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来准备短事项的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Jot&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;“新标签页”是众多插件的必争之地，Jot 就是其中又简单又好看的一种。每当你打开一个标签页时，它会显示一个漂亮的壁纸，还会让你用大字报列出今天的 To-Do list 和备注，每次打开空白标签页就能看到。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;      &lt;strong&gt;如果你不需要各种高级任务管理的功能，Jot 是你的不二之选。&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;➤      &lt;strong&gt;Jot 获取链接：&lt;/strong&gt;https://chrome.google.com/webstore/detail/jot/mnemjleajnmodijhnibpekloajfdjjja      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来规划一天的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Momentu&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;Momentum 为你的新标签页加了一些有用的小部件，可以帮你方便地规划日程、实现目标。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;上面有时钟、当地天气、To-Do list 和一个自定义的快捷链接，背景图多到你看不过来。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;界面中的所有元素都可以根据自己的喜好进行调整。最牛逼的是“focus”小部件，看到没，能在图片的 good afternoon 下面，显示你一天的主要目标。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;每月支付 2 美元的话可以得到更多的自定义功能，比如同步 Todoist 和 Wunderlist 等各种应用，“foucus” 还能学会从你的 To-Do list 里抽选任务来显示。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;➤      &lt;strong&gt;Momentum 获取链接&lt;/strong&gt;：https://chrome.google.com/webstore/detail/momentum/laookkfknpbbblfpciffpaejjkokdgca?hl=en      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来安排任务的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Todois&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;如果你常用 Chrome，但还没有一个默认的 To-Do list 管理器，不如试试Todoist 。它在浏览器工具栏里内置一个强悍的任务列表，在方寸之间提供大量功能。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;对于新手，你可以简单地添加任务或将活动选项卡变为列表里的项目。它会让你设置优先级，并为每个待办事项设定到期时间或重复规律。接下来七天你的日程安排就会优雅地出现在你面前。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;在同一个地方添加和浏览待办事项的功能，比起 Wunderlist 为每个功能都要加一个插件的方法做得要好， Todoist 还能通过 Zapier 与大量应用联动，把任务添加到 Google 日历，给 Slack 里的加星标消息设为待办事项。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;➤      &lt;strong&gt;Todoist 获取链接：&lt;/strong&gt;https://chrome.google.com/webstore/detail/todoist-to-do-list-and-ta/jldhpllghnbhlbpcmnajkpdmadaolakh?hl=en&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来掌控项目的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Taco&lt;/strong&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;如果你已经用过好几个 APP 来管理任务和项目，Taco 的新标签替换功能足以让他笑傲群雄。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;和Taco联动的应用有：Wunderlist，印象笔记，Asana，Basecamp和Trello，你可以把所有的项目放进新标签页里，然后在不用打开各个应用的前提下，就可以在 “Up Next” 表里拖拽项目来优化。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;它强在能按项目过滤任务，但也能像普通 To-Do list 一样简单地使用。我喜欢用Taco 的原因在于能让我工作摸鱼两不误。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;➤      &lt;strong&gt;Taco 获取链接：&lt;/strong&gt;https://chrome.google.com/webstore/detail/taco/aogabobfbepcehdkbfagdflinolncebh?hl=en      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;阅读与研究类&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;网络上充斥着各种有用没用的信息，没一会儿就迷失在里面。不如用这些工具充分利用时间，读读书，好好研究研究。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来扫一眼文章的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Reading Tim&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;Time 会在浏览器左上角显示要花多上时间阅读页面上的内容，这对于判断是马上扫一遍还是收藏以后再读是非常有用的。      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;如果你想研究点东西，想知道应该先读那几篇文章，这款插件能帮你迅速找到文章的主题要点。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;Reading Time 默认人均阅读速度为 228 字每分钟，但为了结果准确，你得先在Staples 做个1分钟测试，然后在插件选项里输入你的阅读速度。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;➤      &lt;strong&gt;Reading Time 获取地址：&lt;/strong&gt;https://chrome.google.com/webstore/detail/reading-time/nccohhimobidpghgpnejnbkpoichbbml?hl=en&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来稍后阅读的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Pocket&lt;/strong&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;如果你偶然发现一篇有趣的文章，但没时间看，那可以用Pocket保存以后再读。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;只要点击工具栏上的按钮或者按 Ctrl/Cmd+Shift+S，你正在浏览的页面就会被加到阅读队列中。有空的时候就可以打开 Pocket 的 web 应用和 APP，在没有其他干扰的情况下阅读。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;如果 pocket 里的东西越来越多，你也可以装个 TimeToRead ：它给每篇文章加个 tag，显示得花多少时间来阅读这篇。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;这可以帮你根据你有多少摸鱼时间来挑选阅读的内容，或者通过时间长短来排个序。有些人要读的东西越堆越多，它能帮你好好地整理一下阅读列表。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;➤      &lt;strong&gt;Pocket 获取链接：&lt;/strong&gt;https://chrome.google.com/webstore/detail/save-to-pocket/niloccemoadcdkdjlinkgdfekeahmflj?hl=en      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来速读的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Reedy&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;当你时间紧急又得迅速熟悉一篇文章的时候，你就得速读内容。Reedy 可以让你选择页面上任意一段文章段落，然后在他清爽的界面上阅读。      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;要知道，信息过载是非常现实的问题，Reedy无疑能帮你弱化信息量。当我被冗长的电子邮件和新闻稿轰炸时，Reedy能让我大致浏览一遍，决定是否得花时间跟进处理。如果你经常需要阅读大量内容，他值得一试。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;➤      &lt;strong&gt;Reedy 获取链接：&lt;/strong&gt;https://chrome.google.com/webstore/detail/reedy/ihbdojmggkmjbhfflnchljfkgdhokffj      &lt;br /&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来总结文章的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Highly&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;这款插件通过高亮文本精华的方式来梳理一篇文章。在你选好文章重点、分享给朋友后，他们只会看到你选定的部分，同时会有数字显示你为他们节约了多少时间。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;这个功能不只局限于分享：我后来发现，他能用于在文章或者播客里标记来源。我所作的高亮都被保存下来，可以随时检索，甚至在Pocket和Instapaper账户里得以显示。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;➤      &lt;strong&gt;Highly 获取链接：&lt;/strong&gt;https://chrome.google.com/webstore/detail/highly-highlighter/hjpahjhcglfdopbholajmhpamgblhjhg?hl=en&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;      &lt;strong&gt;书签和搜索类&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来重新发现需要的在线资源的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Refind&lt;/strong&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;关于书签页的工具真是太多了，你换台电脑可能就忘了浏览器里都收藏了啥。Refind的出现就是为了解决这个问题。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;他能把书签页保存在云端，你google搜到相关内容时他们也会被高亮出来，妈妈再也不用担心我一篇文章收藏个好多遍啦。      &lt;br /&gt;      &lt;br /&gt;➤ Refind 获取链接：https://chrome.google.com/webstore/detail/refind/dlapbpopbcangbnjdhajdlanbfokjaja?hl=en&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;用来追踪页面变化的&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Page Monitor&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;推荐理由：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;RSS 阅读器可以聚合你关注的博客，但如果你想收到更新的实时提醒，你就得用用 Page Monitor 了。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;这款插件需要你把想跟的页面加到列表里，然后他就会持续关注更改情况，如果有新内容放上来，他就会“叮”地一声提醒你。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;作为一名记者，我用 Page Monitor 来快速跟进科技新闻——你也可以用它来追踪股票和汇率，产品发布和销售情况等。 扩展的导出功能特别方便，我可以一次性把我看的网站地址分享给同事。&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;➤      &lt;strong&gt;Page Monitor 获取链接：&lt;/strong&gt;https://chrome.google.com/webstore/detail/page-monitor/ogeebjpdeabhncjpfhgdibjajcajepgg?hl=en&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;以上就是 12 款  能提高生产力的强悍 Chrome 插件。如果你试了一些，想必你再也不会离开他们，可能顺便还卸了几个多余的桌面 App。      &lt;strong&gt;在评论区告诉优达菌你最喜欢用的插件是什么吧！&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;— 完 —&lt;/p&gt;    &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img&gt;&lt;/img&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/57929-%E7%A5%9E%E5%99%A8-chrome-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Tue, 16 Jan 2018 08:47:32 CST</pubDate>
    </item>
    <item>
      <title>《Android插件化技术——原理篇》</title>
      <link>https://itindex.net/detail/57916-android-%E6%8F%92%E4%BB%B6-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;blockquote&gt;  &lt;p&gt;| 导语 插件化技术最早从2012年诞生至今，已经走过了5个年头。从最初只支持Activity的动态加载发展到可以完全模拟app运行时的沙箱系统，各种开源项目层出不穷，在此挑选了几个代表性的框架，总结其中的技术原理。由于本人水平有限，插件化框架又相当复杂，文中若有错误或者不准确的地方望高手指点。&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;内容概要&lt;/h2&gt; &lt;p&gt;  &lt;img src="http://mmbiz.qpic.cn/mmbiz_png/tnZGrhTk4dfxmHLtbLMzmQMjmjhjWq2XxyexxDtTdraxmrfsL39vo3iaVU1zt7nxiaLKyuSsmj1oCgOmvnbv1fmw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;一、发展历史&lt;/h2&gt; &lt;p&gt;插件化技术最初源于免安装运行apk的想法，这个免安装的apk可以理解为插件。支持插件化的app可以在运行时加载和运行插件，这样便可以将app中一些不常用的功能模块做成插件，一方面减小了安装包的大小，另一方面可以实现app功能的动态扩展。想要实现插件化，主要是解决下面三个问题：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;插件中代码的加载和与主工程的互相调用&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;插件中资源的加载和与主工程的互相访问&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;四大组件生命周期的管理&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;下面是比较出名的几个开源的插件化框架，按照出现的时间排序。研究它们的实现原理，可以大致看出插件化技术的发展，根据实现原理我把这几个框架划分成了三代。&lt;/p&gt; &lt;p&gt;  &lt;img src="http://mmbiz.qpic.cn/mmbiz_png/tnZGrhTk4dfxmHLtbLMzmQMjmjhjWq2XeJ1qLUic0kGzLIzr2n3pv5Emich2bYibQLrCic1oibAUWUEwibP1sePxyeUQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;第一代：&lt;/strong&gt;dynamic-load-apk最早使用ProxyActivity这种静态代理技术，由ProxyActivity去控制插件中PluginActivity的生命周期。该种方式缺点明显，插件中的activity必须继承PluginActivity，开发时要小心处理context。而DroidPlugin通过Hook系统服务的方式启动插件中的Activity，使得开发插件的过程和开发普通的app没有什么区别，但是由于hook过多系统服务，异常复杂且不够稳定。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;第二代：&lt;/strong&gt;为了同时达到插件开发的低侵入性（像开发普通app一样开发插件）和框架的稳定性，在实现原理上都是趋近于选择尽量少的hook，并通过在manifest中预埋一些组件实现对四大组件的插件化。另外各个框架根据其设计思想都做了不同程度的扩展，其中Small更是做成了一个跨平台，组件化的开发框架。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;第三代：&lt;/strong&gt;VirtualApp比较厉害，能够完全模拟app的运行环境，能够实现app的免安装运行和双开技术。Atlas是阿里今年开源出来的一个结合组件化和热修复技术的一个app基础框架，其广泛的应用与阿里系的各个app，其号称是一个容器化框架。&lt;/p&gt; &lt;p&gt;下面详细介绍插件化框架的原理，分别对应着实现插件化的三个核心问题。&lt;/p&gt; &lt;h2&gt;二、基本原理&lt;/h2&gt; &lt;h3&gt;2.1 类加载&lt;/h3&gt; &lt;h4&gt;外部apk中类的加载&lt;/h4&gt; &lt;p&gt;Android中常用的有两种类加载器，DexClassLoader和PathClassLoader，它们都继承于BaseDexClassLoader。&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;// DexClassLoaderpublic class DexClassLoader extends BaseDexClassLoader {    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}   &lt;br /&gt;// PathClassLoader   &lt;br /&gt;   &lt;br /&gt;public class PathClassLoader extends BaseDexClassLoader {    public PathClassLoader(String dexPath, ClassLoader parent) {        &lt;br /&gt;   super(dexPath, null, null, parent);
    }    &lt;br /&gt;       &lt;br /&gt; public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {       &lt;br /&gt;    super(dexPath, null, libraryPath, parent);
    }
}&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;区别在于调用父类构造器时，DexClassLoader多传了一个optimizedDirectory参数，这个目录必须是内部存储路径，用来缓存系统创建的Dex文件。而PathClassLoader该参数为null，只能加载内部存储目录的Dex文件。&lt;/p&gt; &lt;p&gt;所以我们可以用DexClassLoader去加载外部的apk，用法如下&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;//第一个参数为apk的文件目录//第二个参数为内部存储目录//第三个为库文件的存储目录//第四个参数为父加载器new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent)&lt;/p&gt;&lt;/pre&gt; &lt;h4&gt;双亲委托机制&lt;/h4&gt; &lt;p&gt;  &lt;strong&gt;ClassLoader调用loadClass方法加载类&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;   &lt;p&gt;protected Class&amp;lt;?&amp;gt; loadClass(String className, boolean resolve) throws ClassNotFoundException {    &lt;br /&gt;       //首先从已经加载的类中查找
        Class&amp;lt;?&amp;gt; clazz = findLoadedClass(className);       &lt;br /&gt;    if (clazz == null) {
            ClassNotFoundException suppressed = null;        &lt;br /&gt;           try {      &lt;br /&gt;                //如果没有加载过，先调用父加载器的loadClass
                clazz = parent.loadClass(className, false);
            } catch (ClassNotFoundException e) {
                suppressed = e;
            }         &lt;br /&gt;        if (clazz == null) {           &lt;br /&gt;                try {              &lt;br /&gt;          &lt;br /&gt;                  //父加载器都没有加载，则尝试加载
                    clazz = findClass(className);
                } catch (ClassNotFoundException e) {
                    e.addSuppressed(suppressed);          &lt;br /&gt;                     throw e;
                }
            }
        }       &lt;br /&gt;            return clazz;
    }&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;可以看出ClassLoader加载类时，先查看自身是否已经加载过该类，如果没有加载过会首先让父加载器去加载，如果父加载器无法加载该类时才会调用自身的findClass方法加载，该机制很大程度上避免了类的重复加载。&lt;/p&gt; &lt;h4&gt;DexClassLoader的DexPathList&lt;/h4&gt; &lt;p&gt;DexClassLoader重载了findClass方法，在加载类时会调用其内部的DexPathList去加载。DexPathList是在构造DexClassLoader时生成的，其内部包含了DexFile。如下图所示&lt;/p&gt; &lt;p&gt;  &lt;img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;DexPathList的loadClass会去遍历DexFile直到找到需要加载的类&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;public Class findClass(String name, List&amp;lt;Throwable&amp;gt; suppressed) {    &lt;br /&gt;       //循环dexElements，调用DexFile.loadClassBinaryName加载class
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;       &lt;br /&gt;        if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);      &lt;br /&gt;                     if (clazz != null) {      &lt;br /&gt;                       return clazz;
                }
            }
        }     &lt;br /&gt;      if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }        &lt;br /&gt;         return null;
    }&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;有一种热修复技术正是利用了DexClassLoader的加载机制，将需要替换的类添加到dexElements的前面，这样系统会使用先找到的修复过的类。&lt;/p&gt; &lt;h3&gt;2.2 单DexClassLoader与多DexClassLoader&lt;/h3&gt; &lt;p&gt;通过给插件apk生成相应的DexClassLoader便可以访问其中的类，这边又有两种处理方式，有单DexClassLoader和多DexClassLoader两种结构。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;多DexClassLoader&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;对于每个插件都会生成一个DexClassLoader，当加载该插件中的类时需要通过对应DexClassLoader加载。这样不同插件的类是隔离的，当不同插件引用了同一个类库的不同版本时，不会出问题。RePlugin采用的是该方案。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;单DexClassLoader&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;将插件的DexClassLoader中的pathList合并到主工程的DexClassLoader中。这样做的好处时，可以在不同的插件以及主工程间直接互相调用类和方法，并且可以将不同插件的公共模块抽出来放在一个common插件中直接供其他插件使用。Small采用的是这种方式。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;互相调用&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;插件和主工程的互相调用涉及到以下两个问题&lt;/p&gt; &lt;p&gt;插件调用主工程&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;在构造插件的ClassLoader时会传入主工程的ClassLoader作为父加载器，所以插件是可以直接可以通过类名引用主工程的类。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;主工程调用插件&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;若使用多ClassLoader机制，主工程引用插件中类需要先通过插件的ClassLoader加载该类再通过反射调用其方法。插件化框架一般会通过统一的入口去管理对各个插件中类的访问，并且做一定的限制。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;若使用单ClassLoader机制，主工程则可以直接通过类名去访问插件中的类。该方式有个弊病，若两个不同的插件工程引用了一个库的不同版本，则程序可能会出错，所以要通过一些规范去避免该情况发生。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;2.3 资源加载&lt;/h3&gt; &lt;p&gt;Android系统通过Resource对象加载资源，下面代码展示了该对象的生成过程&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;//创建AssetManager对象    &lt;br /&gt;AssetManager assets = new AssetManager();   &lt;br /&gt; //将apk路径添加到AssetManager中   &lt;br /&gt;  if (assets.addAssetPath(resDir) == 0){              
    return null;  
}   &lt;br /&gt; //创建Resource对象   &lt;br /&gt;   &lt;br /&gt;r = new Resources(assets, metrics, getConfiguration(), compInfo);&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;因此，只要将插件apk的路径加入到AssetManager中，便能够实现对插件资源的访问。&lt;/p&gt; &lt;p&gt;具体实现时，由于AssetManager并不是一个public的类，需要通过反射去创建，并且部分Rom对创建的Resource类进行了修改，所以需要考虑不同Rom的兼容性。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;资源路径的处理&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;和代码加载相似，插件和主工程的资源关系也有两种处理方式&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;合并式：addAssetPath时加入所有插件和主工程的路径&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;独立式：各个插件只添加自己apk路径&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;合并式由于AssetManager中加入了所有插件和主工程的路径，因此生成的Resource可以同时访问插件和主工程的资源。但是由于主工程和各个插件都是独立编译的，生成的资源id会存在相同的情况，在访问时会产生资源冲突。&lt;/p&gt; &lt;p&gt;独立式时，各个插件的资源是互相隔离的，不过如果想要实现资源的共享，必须拿到对应的Resource对象。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Context的处理&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;通常我们通过Context对象访问资源，光创建出Resource对象还不够，因此还需要一些额外的工作。 对资源访问的不同实现方式也需要不同的额外工作。以VirtualAPK的处理方式为例&lt;/p&gt; &lt;p&gt;第一步：创建Resource&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;if (Constants.COMBINE_RESOURCES) {   &lt;br /&gt;    //插件和主工程资源合并时需要hook住主工程的资源
    Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
    ResourcesManager.hookResources(context, resources);     &lt;br /&gt;      return resources;
} else {     &lt;br /&gt;      //插件资源独立，该resource只能访问插件自己的资源
    Resources hostResources = context.getResources();
    AssetManager assetManager = createAssetManager(context, apk);     &lt;br /&gt;        return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;第二步：hook主工程的Resource&lt;/p&gt; &lt;p&gt;对于合并式的资源访问方式，需要替换主工程的Resource，下面是具体替换的代码。&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;public static void hookResources(Context base, Resources resources) {    &lt;br /&gt;   try {
            ReflectUtil.setField(base.getClass(), base, &amp;quot;mResources&amp;quot;, resources);
            Object loadedApk = ReflectUtil.getPackageInfo(base);
            ReflectUtil.setField(loadedApk.getClass(), loadedApk, &amp;quot;mResources&amp;quot;, resources);

            Object activityThread = ReflectUtil.getActivityThread(base);
            Object resManager = ReflectUtil.getField(activityThread.getClass(), activityThread, &amp;quot;mResourcesManager&amp;quot;);          &lt;br /&gt;     if (Build.VERSION.SDK_INT &amp;lt; 24) {
                Map&amp;lt;Object, WeakReference&amp;lt;Resources&amp;gt;&amp;gt; map = (Map&amp;lt;Object, WeakReference&amp;lt;Resources&amp;gt;&amp;gt;) ReflectUtil.getField(resManager.getClass(), resManager, &amp;quot;mActiveResources&amp;quot;);
                Object key = map.keySet().iterator().next();
                map.put(key, new WeakReference&amp;lt;&amp;gt;(resources));
            } else {                // still hook Android N Resources, even though it&amp;apos;s unnecessary, then nobody will be strange.
                Map map = (Map) ReflectUtil.getFieldNoException(resManager.getClass(), resManager, &amp;quot;mResourceImpls&amp;quot;);
                Object key = map.keySet().iterator().next();
                Object resourcesImpl = ReflectUtil.getFieldNoException(Resources.class, resources, &amp;quot;mResourcesImpl&amp;quot;);
                map.put(key, new WeakReference&amp;lt;&amp;gt;(resourcesImpl));
            }
    } catch (Exception e) {
        e.printStackTrace();&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;注意下上述代码hook了几个地方，包括以下几个hook点&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;替换了主工程context中LoadedApk的mResource对象&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;将新的Resource添加到主工程ActivityThread的mResourceManager中，并且根据Android版本做了不同处理&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;第三步：关联resource和Activity&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);   &lt;br /&gt;//设置Activity的mResources属性，Activity中访问资源时都通过mResources   &lt;br /&gt;   &lt;br /&gt;ReflectUtil.setField(ContextThemeWrapper.class, activity, &amp;quot;mResources&amp;quot;, plugin.getResources());&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;上述代码是在Activity创建时被调用的（后面会介绍如何hook Activity的创建过程），在activity被构造出来后，需要替换其中的mResources为插件的Resource。由于独立式时主工程的Resource不能访问插件的资源，所以如果不做替换，会产生资源访问错误。&lt;/p&gt; &lt;p&gt;做完以上工作后，则可以在插件的Activity中放心的使用setContentView，inflater等方法加载布局了。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;资源冲突&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;合并式的资源处理方式，会引入资源冲突，原因在于不同插件中的资源id可能相同，所以解决方法就是使得不同的插件资源拥有不同的资源id。&lt;/p&gt; &lt;p&gt;资源id是由8位16进制数表示，表示为0xPPTTNNNN。PP段用来区分包空间，默认只区分了应用资源和系统资源，TT段为资源类型，NNNN段在同一个APK中从0000递增。如下表所示&lt;/p&gt; &lt;p&gt;  &lt;img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;所以思路是修改资源ID的PP段，对于不同的插件使用不同的PP段，从而区分不同插件的资源。具体实现方式有两种&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;修改aapt源码，编译期修改PP段。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;修改resources.arsc文件，该文件列出了资源id到具体资源路径的映射。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;具体实现可以分别参考Atlas框架和Small框架。推荐第二种方式，不用入侵原有的编译流程。&lt;/p&gt; &lt;h2&gt;三、四大组件支持&lt;/h2&gt; &lt;p&gt;Android开发中有一些特殊的类，是由系统创建的，并且由系统管理生命周期。如常用的四大组件，Activity，Service，BroadcastReceiver和ContentProvider。 仅仅构造出这些类的实例是没用的，还需要管理组件的生命周期。其中以Activity最为复杂，不同框架采用的方法也不尽相同。下面以Activity为例详细介绍插件化如何支持组件生命周期的管理。 大致分为两种方式：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;ProxyActivity代理&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;预埋StubActivity，hook系统启动Activity的过程&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;3.1 ProxyActivity代理&lt;/h3&gt; &lt;p&gt;ProxyActivity代理的方式最早是由dynamic-load-apk提出的，其思想很简单，在主工程中放一个ProxyActivy，启动插件中的Activity时会先启动ProxyActivity，在ProxyActivity中创建插件Activity，并同步生命周期。下图展示了启动插件Activity的过程。&lt;/p&gt; &lt;p&gt;  &lt;img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;首先需要通过统一的入口（如图中的PluginManager）启动插件Activity，其内部会将启动的插件Activity信息保存下来，并将intent替换为启动ProxyActivity的intent。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;ProxyActivity根据插件的信息拿到该插件的ClassLoader和Resource，通过反射创建PluginActivity并调用其onCreate方法。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;PluginActivty调用的setContentView被重写了，会去调用ProxyActivty的setContentView。由于ProxyActivity重写了getResource返回的是插件的Resource，所以setContentView能够访问到插件中的资源。同样findViewById也是调用ProxyActivity的。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;ProxyActivity中的其他生命周期回调函数中调用相应PluginActivity的生命周期。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;代理方式的关键总结起来有下面两点：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;ProxyActivity中需要重写getResouces，getAssets，getClassLoader方法返回插件的相应对象。生命周期函数以及和用户交互相关函数，如onResume，onStop，onBackPressedon，KeyUponWindow，FocusChanged等需要转发给插件。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;PluginActivity中所有调用context的相关的方法，如setContentView，getLayoutInflater，getSystemService等都需要调用ProxyActivity的相应方法。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;该方式有几个明显缺点：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;插件中的Activity必须继承PluginActivity，开发侵入性强。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;如果想支持Activity的singleTask，singleInstance等launchMode时，需要自己管理Activity栈，实现起来很繁琐。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;插件中需要小心处理Context，容易出错。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;如果想把之前的模块改造成插件需要很多额外的工作。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;该方式虽然能够很好的实现启动插件Activity的目的，但是由于开发式侵入性很强，dynamic-load-apk之后的插件化方案很少继续使用该方式，而是通过hook系统启动Activity的过程，让启动插件中的Activity像启动主工程的Activity一样简单。&lt;/p&gt; &lt;h3&gt;3.2 hook方式&lt;/h3&gt; &lt;p&gt;在介绍hook方式之前，先用一张图简要的介绍下系统是如何启动一个Activity的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;上图列出的是启动一个Activity的主要过程，具体步骤如下：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;Activity1调用startActivity，实际会调用Instrumentation类的execStartActivity方法，Instrumentation是系统用来监控Activity运行的一个类，Activity的整个生命周期都有它的影子。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;通过跨进程的binder调用，进入到ActivityManagerService中，其内部会处理Activity栈。之后又通过跨进程调用进入到Activity2所在的进程中。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;ApplicationThread是一个binder对象，其运行在binder线程池中，内部包含一个H类，该类继承于类Handler。ApplicationThread将启动Activity2的信息通过H对象发送给主线程。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;主线程拿到Activity2的信息后，调用Instrumentation类的newActivity方法，其内通过ClassLoader创建Activity2实例。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;下面介绍如何通过hook的方式启动插件中的Activity，需要解决以下两个问题&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;插件中的Activity没有在AndroidManifest中注册，如何绕过检测。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;如何构造Activity实例，同步生命周期&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;解决方法有很多种，以VirtualAPK为例，核心思路如下：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;先在Manifest中预埋StubActivity，启动时hook上图第1步，将Intent替换成StubActivity。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;hook第10步，通过插件的ClassLoader反射创建插件Activity\&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;之后Activity的所有生命周期回调都会通知给插件Activity&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;下面具体分析整个过程涉及到的代码：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;替换系统Instrumentation&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;VirtualAPK在初始化时会调用hookInstrumentationAndHandler，该方法hook了系统的Instrumentaiton类，由上文可知该类和Activity的启动息息相关。&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;private void hookInstrumentationAndHandler() {    &lt;br /&gt;   try {     &lt;br /&gt;         //获取Instrumentation对象
        Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);      &lt;br /&gt;              //构造自定义的VAInstrumentation
        final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);    &lt;br /&gt;                     //设置ActivityThread的mInstrumentation和mCallBack
        Object activityThread = ReflectUtil.getActivityThread(this.mContext);
        ReflectUtil.setInstrumentation(activityThread, instrumentation);
        ReflectUtil.setHandlerCallback(this.mContext, instrumentation);    &lt;br /&gt;         this.mInstrumentation = instrumentation;
    } catch (Exception e) {
        e.printStackTrace();
    }
 }&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;该段代码将主线程中的Instrumentation对象替换成了自定义的VAInstrumentation类。在启动和创建插件activity时，该类都会偷偷做一些手脚。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;hook activity启动过程&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;VAInstrumentation类重写了execStartActivity方法，图 3.2中的第一步。&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;public ActivityResult execStartActivity(
    //省略了无关参数
    Intent intent) {   &lt;br /&gt;//转换隐式intent
    mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);    &lt;br /&gt;   if (intent.getComponent() != null) {     &lt;br /&gt;      //替换intent中启动Activity为StubActivity
        this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
    }        

    //调用父类启动Activity的方法}   &lt;br /&gt;public void markIntentIfNeeded(Intent intent) {    &lt;br /&gt;   if (intent.getComponent() == null) {     &lt;br /&gt;         return;
    }

    String targetPackageName = intent.getComponent().getPackageName();
    String targetClassName = intent.getComponent().getClassName();    // search map and return specific launchmode stub activity
    if (!targetPackageName.equals(mContext.getPackageName()) &amp;amp;&amp;amp; mPluginManager.getLoadedPlugin(targetPackageName) != null) {
        intent.putExtra(Constants.KEY_IS_PLUGIN, true);
        intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
        intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
        dispatchStubActivity(intent);
    }
}&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;execStartActivity中会先去处理隐式intent，如果该隐式intent匹配到了插件中的Activity，将其转换成显式。之后通过markIntentIfNeeded将待启动的的插件Activity替换成了预先在AndroidManifest中占坑的StubActivity，并将插件Activity的信息保存到该intent中。其中有个dispatchStubActivity函数，会根据Activity的launchMode选择具体启动哪个StubActivity。VirtualAPK为了支持Activity的launchMode在主工程的AndroidManifest中对于每种启动模式的Activity都预埋了多个坑位。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;hook Activity的创建过程&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;上一步欺骗了系统，让系统以为自己启动的是一个正常的Activity。当来到图 3.2的第10步时，再将插件的Activity换回来。此时调用的是VAInstrumentation类的newActivity方法。&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;@Overridepublic Activity newActivity(ClassLoader cl, String className, Intent intent){   &lt;br /&gt;    try {
        cl.loadClass(className);
    } catch (ClassNotFoundException e) {   &lt;br /&gt;        //通过LoadedPlugin可以获取插件的ClassLoader和Resource
        LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);   &lt;br /&gt;                //获取插件的主Activity
        String targetClassName = PluginUtil.getTargetActivity(intent);   &lt;br /&gt;                if (targetClassName != null) {    &lt;br /&gt;                   //传入插件的ClassLoader构造插件Activity
            Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
            activity.setIntent(intent);   &lt;br /&gt;                    //设置插件的Resource，从而可以支持插件中资源的访问
            try {
                ReflectUtil.setField(ContextThemeWrapper.class, activity, &amp;quot;mResources&amp;quot;, plugin.getResources());
            } catch (Exception ignored) {    &lt;br /&gt;                                   // ignored.
            }     &lt;br /&gt;          return activity;
        }
    }    return mBase.newActivity(cl, className, intent);
}&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;由于AndroidManifest中预埋的StubActivity并没有具体的实现类，所以此时会发生ClassNotFoundException。之后在处理异常时取出插件Activity的信息，通过插件的ClassLoader反射构造插件的Activity。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;一些额外操作&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;插件Activity构造出来后，为了能够保证其正常运行还要做些额外的工作。VAInstrumentation类在图3.2中的第11步中也做了一些处理。&lt;/p&gt; &lt;pre&gt;  &lt;p&gt;@Overridepublic void callActivityOnCreate(Activity activity, Bundle icicle) {    &lt;br /&gt;   final Intent intent = activity.getIntent();   &lt;br /&gt;       if (PluginUtil.isIntentFromPlugin(intent)) {
        Context base = activity.getBaseContext();   &lt;br /&gt;               try {
            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
            ReflectUtil.setField(base.getClass(), base, &amp;quot;mResources&amp;quot;, plugin.getResources());
            ReflectUtil.setField(ContextWrapper.class, activity, &amp;quot;mBase&amp;quot;, plugin.getPluginContext());
            ReflectUtil.setField(Activity.class, activity, &amp;quot;mApplication&amp;quot;, plugin.getApplication());
            ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, &amp;quot;mBase&amp;quot;, plugin.getPluginContext());   &lt;br /&gt;               // set screenOrientation
            ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));    &lt;br /&gt;              if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                activity.setRequestedOrientation(activityInfo.screenOrientation);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    mBase.callActivityOnCreate(activity, icicle);
}&lt;/p&gt;&lt;/pre&gt; &lt;p&gt;这段代码主要是将Activity中的Resource，Context等对象替换成了插件的相应对象，保证插件Activity在调用涉及到Context的方法时能够正确运行。&lt;/p&gt; &lt;p&gt;经过上述步骤后，便实现了插件Activity的启动，并且该插件Activity中并不需要什么额外的处理，和常规的Activity一样。那问题来了，之后的onResume，onStop等生命周期怎么办呢？答案是所有和Activity相关的生命周期函数，系统都会调用插件中的Activity。原因在于AMS在处理Activity时，通过一个token表示具体Activity对象，而这个token正是和启动Activity时创建的对象对应的，而这个Activity被我们替换成了插件中的Activity，所以之后AMS的所有调用都会传给插件中的Activity。&lt;/p&gt; &lt;h2&gt;小结&lt;/h2&gt; &lt;p&gt;VirtualAPK通过替换了系统的Instrumentation，hook了Activity的启动和创建，省去了手动管理插件Activity生命周期的繁琐，让插件Activity像正常的Activity一样被系统管理，并且插件Activity在开发时和常规一样，即能独立运行又能作为插件被主工程调用。&lt;/p&gt; &lt;p&gt;其他插件框架在处理Activity时思想大都差不多，无非是这两种方式之一或者两者的结合。在hook时，不同的框架可能会选择不同的hook点。如360的RePlugin框架选择hook了系统的ClassLoader，即图3.2中构造Activity2的ClassLoader，在判断出待启动的Activity是插件中的时，会调用插件的ClassLoader构造相应对象。另外RePlugin为了系统稳定性，选择了尽量少的hook，因此它并没有选择hook系统的startActivity方法来替换intent，而是通过重写Activity的startActivity，因此其插件Activity是需要继承一个类似PluginActivity的基类的。不过RePlugin提供了一个Gradle插件将插件中的Activity的基类换成了PluginActivity，用户在开发插件Activity时也是没有感知的。&lt;/p&gt; &lt;h3&gt;3.3 其他组件&lt;/h3&gt; &lt;p&gt;四大组件中Activity的支持是最复杂的，其他组件的实现原理要简单很多，简要概括如下&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;Service：Service和Activity的差别在于，Activity的生命周期是由用户交互决定的，而Service的生命周期是我们通过代码主动调用的，且Service实例和manifest中注册的是一一对应的。实现Service插件化的思路是通过在manifest中预埋StubService，hook系统startService等调用替换启动的Service，之后在StubService中创建插件Service，并手动管理其生命周期。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;BroadCastReceiver：解析插件的manifest，将静态注册的广播转为动态注册。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;ContentProvider：类似于Service的方式，对插件ContentProvider的所有调用都会通过一个在manifest中占坑的ContentProvider分发。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;四、发展方向&lt;/h2&gt; &lt;p&gt;通过对插件化技术的学习，可以看出目前插件化技术的两个发展方向&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;结合组件化技术，成为一个中大型app的基础框架&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;以Small和阿里的Atlas为代表，利用了插件化技术对复杂工程的模块进行解耦，将app分成主工程和多个插件模块。主工程在运行期间动态加载相应模块的插件运行，并负责插件模块的管理工作。各个插件可以独立开发和运行，也可以依赖主工程或者其他插件。下面是基于Atlas的手淘app的框架图&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;其中的独立bundle即是一个插件，手淘中的首页，详情页，扫码，支付等都做成了单独的bundle，并且首页bundle还可以依赖于定位bundle。而主工程中则包含了各种基础功能库供各个bundle调用，并且包含了对bundle的安装，运行，版本管理，安全校验等运行期的管理工作。&lt;/p&gt; &lt;p&gt;组件化技术是利用gradle脚本实现的编译期的功能解耦，而Atlas是利用插件化技术实现了一套运行期的功能解耦，所以其也号称是动态组件化技术。&lt;/p&gt; &lt;p&gt;app沙盒系统，完全模拟app的运行环境&lt;/p&gt; &lt;p&gt;以VirtualAPP为代表，在应用层构建了一个虚拟的app运行环境，实现了免安装运行apk，应用双开等黑科技。另外作为应用开发者也需要注意我们的应用可能会运行在一个虚拟的环境下，对于支付，登录等功能要特别注意其安全性。&lt;/p&gt; &lt;p&gt;最后用VirtualAPP的作者Lody的一句话结束本篇文章，相信插件化技术还会继续发展壮大下去。&lt;/p&gt; &lt;p&gt;“插件化技术的成熟程度虽然在最近几年呈上升趋势，但是总体而言仍然处于初、中级阶段。  &lt;br /&gt;App沙盒技术的出现就是插件化发展的创新和第一阶段的产物。在未来，我相信很多插件化技  &lt;br /&gt;术会被更多的应用，如果插件化稳定到了一定的程度，甚至可以颠覆App开发的方式。”&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;参考  &lt;br /&gt;1.Android插件化：从入门到放弃(http://www.infoq.com/cn/articles/android-plug-ins-from-entry-to-give-up)&lt;/p&gt; &lt;p&gt;2.Android apk动态加载机制的研究  &lt;br /&gt;(http://blog.csdn.net/singwhatiwanna/article/details/22597587)&lt;/p&gt; &lt;p&gt;3.Android插件化原理解析系列文章  &lt;br /&gt;(http://weishu.me/2016/01/28/understand-plugin-framework-overview/)&lt;/p&gt; &lt;p&gt;4.  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&amp;mid=2650823488&amp;idx=1&amp;sn=2976c8ddc0c206149b14c527260f7766&amp;chksm=80b78fdeb7c006c8a9585db794c51e799049ec50d23c4d738c78d77454f6b0291227a00e2def&amp;mpshare=1&amp;scene=1&amp;srcid=0712oTUswGWi172UK0Azpg4i&amp;key=8652b956ca1971a47b1e263b435230c7469d30646ddbe6ce2fb781033d6eba5215c9fd7e5eaf0bd73dd5da279b32dd901261d5e55bf32997bc333ad8a059e095e2193a5baa805447fc49cd315fca4404&amp;ascene=0&amp;uin=MTI0NjM4NTEyMA%3D%3D&amp;devicetype=iMac+MacBookPro11%2C4+OSX+OSX+10.11.1+build(15B42)&amp;version=12010110&amp;nettype=WIFI&amp;fontScale=100&amp;pass_ticket=mswE9bS3QeCxTOoepaUWh9VXHxeYMosdkHkAydyR09JHQkVe%2BAJHCCnPQrRpBfQN" target="_blank"&gt;深度 | 滴滴插件化方案 VirtualApk 源码解析&lt;/a&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;5.VirtualAPK资源加载原理解析  &lt;br /&gt;(https://www.notion.so/VirtualAPK-1fce1a910c424937acde9528d2acd537)&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/57916-android-%E6%8F%92%E4%BB%B6-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Sat, 13 Jan 2018 15:40:09 CST</pubDate>
    </item>
    <item>
      <title>Android插件化方案 RePlugin/README_CN.md at dev · Qihoo360/RePlugin · GitHub</title>
      <link>https://itindex.net/detail/57574-android-%E6%8F%92%E4%BB%B6-replugin</link>
      <description>&lt;article class="markdown-body entry-content" itemprop="text"&gt;&lt;p align="center"&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki"&gt;&lt;img alt="RePlugin Logo" src="https://github.com/Qihoo360/RePlugin/wiki/img/RePlugin.png" style="max-width:100%;" width="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/blob/master/LICENSE"&gt;&lt;img alt="license" data-canonical-src="http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat" src="https://camo.githubusercontent.com/a8f6a5ad1473478a810b13e0d66a8a052ad8dc6b/687474703a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865322e302d627269676874677265656e2e7376673f7374796c653d666c6174" style="max-width:100%;"&gt;&lt;/a&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/%E5%8F%91%E8%A1%8C%E6%B3%A8%E8%AE%B0"&gt;&lt;img alt="Release Version" data-canonical-src="https://img.shields.io/badge/release-2.2.1-brightgreen.svg" src="https://camo.githubusercontent.com/e09c4ab506d7bc51f130e6f4a7abd1d0a924ea3a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f72656c656173652d322e322e312d627269676874677265656e2e737667" style="max-width:100%;"&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;&lt;a aria-hidden="true" class="anchor" href="#replugin--历经三年多考验数亿设备使用的稳定占坑类插件化方案" id="user-content-replugin--历经三年多考验数亿设备使用的稳定占坑类插件化方案"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#replugin--历经三年多考验数亿设备使用的稳定占坑类插件化方案" id="user-content-replugin--历经三年多考验数亿设备使用的稳定占坑类插件化方案"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#replugin--历经三年多考验数亿设备使用的稳定占坑类插件化方案" id="user-content-replugin--历经三年多考验数亿设备使用的稳定占坑类插件化方案"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;RePlugin —— 历经三年多考验，数亿设备使用的，稳定占坑类插件化方案&lt;/h2&gt;&lt;p&gt;RePlugin是一套完整的、稳定的、适合全面使用的，占坑类插件化方案，由360手机卫士的RePlugin Team研发，也是业内首个提出”全面插件化“（全面特性、全面兼容、全面使用）的方案。&lt;/p&gt;&lt;p&gt;其主要优势有：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;极其灵活&lt;/strong&gt;：主程序无需升级（无需在Manifest中预埋组件），即可支持新增的四大组件，甚至全新的插件&lt;/li&gt;&lt;li&gt;&lt;strong&gt;非常稳定&lt;/strong&gt;：Hook点&lt;strong&gt;仅有一处（ClassLoader），无任何Binder Hook&lt;/strong&gt;！如此可做到其&lt;strong&gt;崩溃率仅为“万分之一”，并完美兼容市面上近乎所有的Android ROM&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;特性丰富&lt;/strong&gt;：支持近乎所有在“单品”开发时的特性。&lt;strong&gt;包括静态Receiver、Task-Affinity坑位、自定义Theme、进程坑位、AppCompat、DataBinding等&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;易于集成&lt;/strong&gt;：无论插件还是主程序，&lt;strong&gt;只需“数行”就能完成接入&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;管理成熟&lt;/strong&gt;：拥有成熟稳定的“插件管理方案”，支持插件安装、升级、卸载、版本管理，甚至包括进程通讯、协议版本、安全校验等&lt;/li&gt;&lt;li&gt;&lt;strong&gt;数亿支撑&lt;/strong&gt;：有360手机卫士庞大的&lt;strong&gt;数亿&lt;/strong&gt;用户做支撑，&lt;strong&gt;三年多的残酷验证&lt;/strong&gt;，确保App用到的方案是最稳定、最适合使用的&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;截止2017年6月底，RePlugin的：&lt;/p&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align="center"&gt;特性&lt;/th&gt;&lt;th align="center"&gt;描述&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align="center"&gt;&lt;strong&gt;插件数&lt;/strong&gt;&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;103（核心57个）&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;&lt;strong&gt;插件占应用比&lt;/strong&gt;&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;高达83%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;&lt;strong&gt;年发版次数&lt;/strong&gt;&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;高达596次（工作日均2次）&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;&lt;strong&gt;崩溃率&lt;/strong&gt;&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;万分之一（0.01%），极低&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;&lt;strong&gt;时间&lt;/strong&gt;&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;2014年应用，3年验证&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;目前360公司几乎&lt;strong&gt;所有的亿级用户量的APP&lt;/strong&gt;，以及多款主流第三方APP，都采用了RePlugin方案。&lt;/p&gt;&lt;p&gt;有关RePlugin的详细介绍，请&lt;a href="https://github.com/Qihoo360/RePlugin/wiki"&gt;点击这里阅读《RePlugin 官方 WiKi》&lt;/a&gt;。&lt;/p&gt;&lt;h3&gt;&lt;a aria-hidden="true" class="anchor" href="#我们还支持以下特性" id="user-content-我们还支持以下特性"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#我们还支持以下特性" id="user-content-我们还支持以下特性"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#我们还支持以下特性" id="user-content-我们还支持以下特性"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;我们还支持以下特性&lt;/h3&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align="center"&gt;特性&lt;/th&gt;&lt;th align="center"&gt;描述&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align="center"&gt;组件&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;四大组件（含静态Receiver）&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;升级无需改主程序Manifest&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;完美支持&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;Android特性&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;支持近乎所有（包括SO库等）&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;TaskAffinity &amp;amp; 多进程&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;支持（&lt;em&gt;坑位方案&lt;/em&gt;）&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;插件类型&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;支持自带插件（&lt;em&gt;自识别&lt;/em&gt;）、外置插件&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;插件间耦合&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;支持Binder、Class Loader、资源等&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;进程间通讯&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;支持同步、异步、Binder、广播等&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;自定义Theme &amp;amp; AppComat&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;支持&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;DataBinding&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;支持&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;安全校验&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;支持&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;资源方案&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;独立资源 + Context传递（相对稳定）&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align="center"&gt;Android 版本&lt;/td&gt;&lt;td align="center"&gt;&lt;strong&gt;API Level 9+ （2.3及以上）&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h2&gt;&lt;a aria-hidden="true" class="anchor" href="#愿景" id="user-content-愿景"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#愿景" id="user-content-愿景"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#愿景" id="user-content-愿景"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;愿景&lt;/h2&gt;&lt;p&gt;让插件化能&lt;strong&gt;飞入寻常应用家&lt;/strong&gt;，做到稳定、灵活、自由，大小项目兼用。&lt;/p&gt;&lt;h2&gt;&lt;a aria-hidden="true" class="anchor" href="#replugin-架构图" id="user-content-replugin-架构图"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#replugin-架构图" id="user-content-replugin-架构图"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#replugin-架构图" id="user-content-replugin-架构图"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;RePlugin 架构图&lt;/h2&gt;&lt;p align="center"&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki"&gt;&lt;img alt="RePlugin Framework" height="600" src="https://github.com/Qihoo360/RePlugin/wiki/img/RePluginFramePic.jpeg" style="max-width:100%;"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;以360手机卫士为例：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;系统层——Android&lt;/strong&gt;：为Android Framework层。&lt;strong&gt;只有ClassLoader是Hook的&lt;/strong&gt;，而AMS、Resources等都没有做Hook，确保了其稳定性。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;框架层——RePlugin框架&lt;/strong&gt;：RePlugin框架层，&lt;strong&gt;只有RePlugin是对“上层完全公开”的&lt;/strong&gt;，其余均为Internal，或“动态编译方案”生效后的调用，对开发者而言是“无需关心”的。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;插件层——各插件&lt;/strong&gt;：“标蓝部分”是各插件，包括大部分的业务插件（如体检、清理、桌面插件等）。而其中“标黄部分”是支撑一个应用的各种基础插件，如WebView、Download、Share，甚至Protobuf都能成为基础插件。&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;&lt;a aria-hidden="true" class="anchor" href="#使用方法" id="user-content-使用方法"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#使用方法" id="user-content-使用方法"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#使用方法" id="user-content-使用方法"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;使用方法&lt;/h2&gt;&lt;p&gt;RePlugin的使用方法非常简单，大部分情况下和“单品”开发无异。&lt;/p&gt;&lt;p&gt;若您是&lt;strong&gt;第一次接触RePlugin，则&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B"&gt;请点击这里阅读《快速上手》&lt;/a&gt;&lt;/strong&gt;，跟随我们的指引，了解更多的内容。&lt;/p&gt;&lt;p&gt;若您想**了解更多有关RePlugin的玩法，则&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B"&gt;请点击这里阅读《详细教程》&lt;/a&gt;，**了解更多好玩的玩法。&lt;/p&gt;&lt;p&gt;若您想&lt;strong&gt;看下RePlugin的Sample工程，进而了解框架的具体用法，则&lt;a href="https://github.com/Qihoo360/RePlugin/blob/master/replugin-sample"&gt;请点击这里查看Sample源代码&lt;/a&gt;&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;若您在接入RePlugin中&lt;strong&gt;遇到了任何问题，则&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/FAQ"&gt;请点击这里阅读《FAQ》&lt;/a&gt;&lt;/strong&gt;，相信会有您想要的答案。&lt;/p&gt;&lt;h2&gt;&lt;a aria-hidden="true" class="anchor" href="#已接入replugin的应用" id="user-content-已接入replugin的应用"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#已接入replugin的应用" id="user-content-已接入replugin的应用"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#已接入replugin的应用" id="user-content-已接入replugin的应用"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;已接入RePlugin的应用&lt;/h2&gt;&lt;p&gt;我们诚挚期待您成为咱们RePlugin应用大家庭中的一员！&lt;/p&gt;&lt;p&gt;除了&lt;strong&gt;360集团旗下的亿级别应用&lt;/strong&gt;以外，还有一些对&lt;strong&gt;稳定要求极其严苛的“金融类”产品&lt;/strong&gt;，及第三方合作应用也接入了RePlugin（含SDK）：&lt;/p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&#xD;
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;table align="center"&gt;&lt;tbody&gt;&lt;tr align="center"&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/mobilesafe.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/mobilesafe.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/appstore.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/appstore.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/browser.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/browser.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/camera.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/camera.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/clean.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/clean.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr align="center"&gt;&lt;td&gt;&lt;b&gt;&lt;a href="https://shouji.360.cn/index.html"&gt;360 手机卫士&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="http://sj.360.cn/index.html"&gt;360 手机助手&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="http://mse.360.cn/m/index.html"&gt;360 手机浏览器&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="http://xj.huajiao.com/xji/home/pc"&gt;花椒相机&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="https://shouji.360.cn/360cleandroid/index.html"&gt;360 清理大师&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr align="center"&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/movie.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/movie.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/jieqianba.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/jieqianba.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/haitao1hao.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/haitao1hao.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/huaruntong.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/huaruntong.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/jielema.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/jielema.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr align="center"&gt;&lt;td&gt;&lt;b&gt;&lt;a href="http://www.360kan.com/appdownload"&gt;360 影视大全&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="/Qihoo360/RePlugin/blob/dev"&gt;J借钱吧&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="http://www.1haitao.com/"&gt;海淘1号&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="http://www.huaruntong.com/"&gt;华润通&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="http://www.jielem.com/"&gt;借了吗&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr align="center"&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/os.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/os.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/jietiao.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/jietiao.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/yourapps.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/yourapps.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/yourapps.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/yourapps.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/img/apps/yourapps.png" target="_blank"&gt;&lt;img height="80" src="https://github.com/Qihoo360/RePlugin/wiki/img/apps/yourapps.png" style="max-width:100%;" width="80"&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr align="center"&gt;&lt;td&gt;&lt;b&gt;&lt;a href="http://www.qiku.com/product/360os2/index.html"&gt;360OS 系统应用&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="https://www.360jie.com.cn/"&gt;360 借条&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="/Qihoo360/RePlugin/blob/dev"&gt;(即将发布)&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="/Qihoo360/RePlugin/blob/dev"&gt;(即将发布)&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;&lt;a href="/Qihoo360/RePlugin/blob/dev"&gt;(即将发布)&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这里&lt;strong&gt;衷心感谢&lt;/strong&gt;“360手机助手”，以及其它各App团队成员，帮助我们发现了很多需要改进的地方，并给予了非常积极的反馈。您们的鼓励与支持，让咱们的RePlugin能走的更远、更好！&lt;/p&gt;&lt;h2&gt;&lt;a aria-hidden="true" class="anchor" href="#已接入replugin的插件" id="user-content-已接入replugin的插件"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#已接入replugin的插件" id="user-content-已接入replugin的插件"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#已接入replugin的插件" id="user-content-已接入replugin的插件"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;已接入RePlugin的插件&lt;/h2&gt;&lt;p&gt;目前已有的插件，可以分为以下几类，供各App开发者参考：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;展示插件&lt;/strong&gt;：如&lt;strong&gt;卫士首页&lt;/strong&gt;（是的，你没看错）、体检、信息流等&lt;/li&gt;&lt;li&gt;&lt;strong&gt;业务插件&lt;/strong&gt;：如清理、骚扰拦截、悬浮窗等&lt;/li&gt;&lt;li&gt;&lt;strong&gt;合作插件&lt;/strong&gt;：如程序锁、免费WiFi、安全桌面等&lt;/li&gt;&lt;li&gt;&lt;strong&gt;后台插件&lt;/strong&gt;：如Push、服务管理、Protobuf等&lt;/li&gt;&lt;li&gt;&lt;strong&gt;基础插件&lt;/strong&gt;：如安全WebView、分享、定位等&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;截止2017年6月底，这样的插件，我们有&lt;strong&gt;103&lt;/strong&gt;个。衷心希望您能成为这个数字中的新的一员！&lt;/p&gt;&lt;h2&gt;&lt;a aria-hidden="true" class="anchor" href="#贡献自己的力量" id="user-content-贡献自己的力量"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#贡献自己的力量" id="user-content-贡献自己的力量"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#贡献自己的力量" id="user-content-贡献自己的力量"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;贡献自己的力量&lt;/h2&gt;&lt;p&gt;我们欢迎任何形式的贡献，并致以诚挚的感谢！&lt;/p&gt;&lt;p&gt;你可以贡献代码、提出问题、编写文档等。有关“贡献”相关的内容，请&lt;a href="https://github.com/Qihoo360/RePlugin/wiki/%E8%B4%A1%E7%8C%AE%E5%8A%9B%E9%87%8F"&gt;点击这里阅读《贡献力量》&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;&lt;a aria-hidden="true" class="anchor" href="#与我们联系" id="user-content-与我们联系"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#与我们联系" id="user-content-与我们联系"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#与我们联系" id="user-content-与我们联系"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;与我们联系&lt;/h2&gt;&lt;p&gt;欢迎您加入到我们的RePlugin微信群、QQ群大家庭。&lt;/p&gt;&lt;p&gt;微信群已超过上限，请进入我们的QQ群&lt;/p&gt;&lt;p&gt;QQ群：&lt;strong&gt;653205923&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;&lt;a aria-hidden="true" class="anchor" href="#license" id="user-content-license"&gt;&lt;/a&gt;&lt;svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;&lt;a aria-hidden="true" class="anchor" href="#license" id="user-content-license"&gt;&lt;/a&gt;&lt;path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" fill-rule="evenodd"&gt;&lt;a aria-hidden="true" class="anchor" href="#license" id="user-content-license"&gt;&lt;/a&gt;&lt;/path&gt;&lt;/svg&gt;License&lt;/h2&gt;&lt;p&gt;RePlugin is&lt;a href="/Qihoo360/RePlugin/blob/dev/LICENSE"&gt;Apache v2.0 licensed&lt;/a&gt;.&lt;/p&gt;&lt;/article&gt;&#xD;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/57574-android-%E6%8F%92%E4%BB%B6-replugin</guid>
      <pubDate>Thu, 19 Oct 2017 11:06:55 CST</pubDate>
    </item>
    <item>
      <title>美团App 插件化实践</title>
      <link>https://itindex.net/detail/57552-%E7%BE%8E%E5%9B%A2-app-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;h2&gt;背景&lt;/h2&gt;
 &lt;p&gt;在Android开发行业里，插件化已经不是一门新鲜的技术了，在稍大的平台型App上早已是标配。进入2017年，Atlas、Replugin、VirtualAPK相继开源，标志着插件化技术进入了成熟阶段。但纵观各大插件框架，都是基于自身App的业务来开发的，目标或多或少都有区别，所以很难有一个插件框架能一统江湖解决所有问题。最后就是绕不开的兼容性问题，Android每次版本升级都会给各个插件化框架带来不少冲击，都要费劲心思适配一番，更别提国内各个厂商对在ROM上做的定制了，正如VirtualAPK的作者任玉刚所说：完成一个插件化框架的 Demo 并不是多难的事儿，然而要开发一款完善的插件化框架却并非易事。&lt;/p&gt;
 &lt;p&gt;早在2014年美团移动技术团队就开始关注插件化技术了，并且意识到插件化架构是美团这种平台型App最好的集成形式。但由于业务增长、迭代、演化太快，受限于业务耦合和架构问题，插件化一直无法落地。到了2016年底，经过一系列的代码架构调整、技术调研，我们终于能腾出手来让插件化技术落地了。&lt;/p&gt;
 &lt;p&gt;美团平台（与点评平台一起）目前承载了美团点评所有事业群近20条业务线的业务。其中有相对成熟的业务，比如外卖、餐饮，他们对插件的要求是稳定性高，不能因为上了插件导致业务出问题；也有迭代变化很快的业务，如交通、跑腿、金融等，他们要求能快速迭代上线；此外，由于美团App采用的二进制AAR依赖方式集成已经运转了两年，各种基础设施都很成熟了，我们不希望换成插件形式的接入之后还要改变开发模式。所以，美团平台对插件的诉求主要集中在兼容性和不影响开发模式这两个点上。&lt;/p&gt;
 &lt;h2&gt;美团插件化框架的原理和特点&lt;/h2&gt;
 &lt;p&gt;插件框架的兼容性体现在多个方面，由于Android机制的问题，有些写法在插件化之前运行的很正常，但是接入插件化之后就变得不再有效。如果不解决兼容性问题，插件化的口碑和推广都会很大阻碍。兼容性不仅仅指的是对Android系统、Android碎片化的兼容，还要对已有基础库和构建工具的兼容。特别是后者，我们经常看到Github上开源的插件化框架里面有大量Crash的Issue，就是这个方面原因导致的。每个App的基础库和既有构建工具都不太一样，所以为自己的App选择合适的方案显得尤为重要。&lt;/p&gt;
 &lt;p&gt;为了保证插件的兼容性，并能无缝兼容当前AAR开发模式，美团的插件化框架方案主要做了以下几点：：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;插件的Dex加载使用类似MultiDex方案，保证对反射的兼容&lt;/li&gt;
  &lt;li&gt;替换所有的AssetManager，保证对资源访问的兼容&lt;/li&gt;
  &lt;li&gt;四大组件预埋，代理新增Activity&lt;/li&gt;
  &lt;li&gt;让构建系统来抹平AAR开发模式和插件化开发模式的差异&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;MultiDex和组件代理这里不细说，网上有很多这方面的博客可以参考。下面重点说一下美团插件化框架对资源的处理和支持AAR、插件一键切换的构建系统。&lt;/p&gt;
 &lt;h3&gt;资源处理&lt;/h3&gt;
 &lt;p&gt;了解插件化的读者都知道：如果希望访问插件的资源，需要使用AssetManager把插件的路径加入进去。但这样做是远远不够的。这是因为如果希望这个AssetManager生效，就得把它放到具体的Resources或ResourcesImpl里面，大部分插件化框架的做法是封装一个包含插件路径AssetManager的Resources，然后插件中只使用这一个Resources。&lt;/p&gt;
 &lt;p&gt;这样的做法大多数情况是有效的，但是有至少3个问题：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;如果在插件中使用了宿主Resources，如：   &lt;code&gt;getApplicationContext().getResources()&lt;/code&gt;。 这个Resources就无法访问插件的资源了&lt;/li&gt;
  &lt;li&gt;插件外的Resources 并不唯一，需要全局查找和替换&lt;/li&gt;
  &lt;li&gt;Resoureces在使用的过程中有很多中间产物，例如Theme、TypedArray等等。这些都需要清理才能正常使用&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;要完全解决这些问题，我们另辟蹊径，做了一个全局的资源处理方式：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;新建或者使用已有AssetManger，加载插件资源&lt;/li&gt;
  &lt;li&gt;查找所有的Resources/Theme，替换其中的AssetManger&lt;/li&gt;
  &lt;li&gt;清理Resources缓存，重建Theme&lt;/li&gt;
  &lt;li&gt;AssetManager的重建保护，防止丢失插件路径&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这个方案和InstantRun有点类似，但是原生InstantRun有太多的问题：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;清理顺序错误，应该先清理Applicaiton后清理Activity&lt;/li&gt;
  &lt;li&gt;Resources/Theme找不全，没有极端情况应对机制&lt;/li&gt;
  &lt;li&gt;Theme光清理不重建&lt;/li&gt;
  &lt;li&gt;完全不适配 Support包里面自己埋的“雷”   &lt;br /&gt;等等&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;举个例子Theme找不全：InstantRun会替换Theme中的AssetManager，做法是从每个Activity里面获取。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;for (Activity activity : activities) {
    ... // 省略部分代码
    Resources.Theme theme = activity.getTheme();
    try {
        try {
            Field ma = Resources.Theme.class.getDeclaredField(&amp;quot;mAssets&amp;quot;);
            ma.setAccessible(true);
            ma.set(theme, newAssetManager);
        } catch (NoSuchFieldException ignore) {
            Field themeField = Resources.Theme.class.getDeclaredField(&amp;quot;mThemeImpl&amp;quot;);
            themeField.setAccessible(true);
            Object impl = themeField.get(theme);
            Field ma = impl.getClass().getDeclaredField(&amp;quot;mAssets&amp;quot;);
            ma.setAccessible(true);
            ma.set(impl, newAssetManager);
        }
        ...
    } catch (Throwable e) {
        Log.e(LOG_TAG, &amp;quot;Failed to update existing theme for activity &amp;quot; + activity,
                e);
    }
    pruneResourceCaches(resources);
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这个思路是对的，但是远不够。例如，Google 自己的Support包里面的一个类 android.support.v7.view.ContextThemeWrapper会生成一个新的Theme保存：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class ContextThemeWrapper extends ContextWrapper {
    private int mThemeResource;
    private Resources.Theme mTheme;
    private LayoutInflater mInflater;
    ...
    private void initializeTheme() {
        final boolean first = mTheme == null;
        if (first) {
            mTheme = getResources().newTheme();
            final Resources.Theme theme = getBaseContext().getTheme();
            if (theme != null) {
                mTheme.setTo(theme);
            }
        }
        onApplyThemeResource(mTheme, mThemeResource, first);
    }
    ...
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;如果没有替换了这个ContextThemeWrapper的Theme，假如配合它使用的Reources/AssetManager是新的，就会导致Crash：  &lt;br /&gt;  &lt;code&gt;java.lang.RuntimeException: Failed to resolve attribute at index 0&lt;/code&gt;  &lt;br /&gt;这是大部分开源框架都存在的Issue。  &lt;br /&gt;为了解决这个问题，我们不仅清理所有Activity的Theme，还清理了所有View的Context。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;try {
    List&amp;lt;View&amp;gt; list = getAllChildViews(activity.getWindow().getDecorView());
    for (View v : list) {
        Context context = v.getContext();
        if (context instanceof ContextThemeWrapper
                &amp;amp;&amp;amp; context != activity
                &amp;amp;&amp;amp; !clearContextWrapperCaches.contains(context)) {
            clearContextWrapperCaches.add((ContextThemeWrapper) context);
            pruneSupportContextThemeWrapper((ContextThemeWrapper) context, newAssetManager); // 清理Theme
        }
    }
} catch (Throwable ignore) {
    Log.e(LOG_TAG, ignore.getMessage());
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;但是这些做法还是不能解决所有问题，有时候为了实现一个产品需求，Android工程师可能会采取一些非常规写法，导致变成插件之后资源加载失败。比如在一个自己的类里面保存了Theme。这种问题不可能一个个改业务代码，那能不能让插件兼容这种写法呢？  &lt;br /&gt;我们对这种行为也做了兼容：  &lt;strong&gt;修改字节码&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;了解虚拟机指令的同学都知道，如果要保存一个类变量，对应的虚拟机的指令是PUTFIELD/PUTSTATIC，以此为突破口，用ASM写一个MethodVisitor：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;static class MyMethodVisitor extends MethodVisitor {
    int stackSize = 0;

    MyMethodVisitor(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
        if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) {
            if (&amp;quot;Landroid/content/res/Resources$Theme;&amp;quot;.equals(desc)) {
                stackSize = 1;
                visitInsn(Opcodes.DUP);
                super.visitMethodInsn(Opcodes.INVOKESTATIC,
                        &amp;quot;com/meituan/hydra/runtime/Transformer&amp;quot;,
                        &amp;quot;collectTheme&amp;quot;,
                        &amp;quot;(Landroid/content/res/Resources$Theme;)V&amp;quot;,
                        false);
            }
        }
        super.visitFieldInsn(opcode, owner, name, desc);
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        super.visitMaxs(maxStack + stackSize, maxLocals);
        stackSize = 0;
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这样可以保证所有被类保存的Theme都会被收集起来，在插件安装后，统一清理、重建就行了。&lt;/p&gt;
 &lt;h3&gt;插件的构建系统&lt;/h3&gt;
 &lt;p&gt;为了实现在AAR集成方式和插件集成方式之间一键切换，并解决插件化遇到的“API陷阱”的问题，我们把大量的时间花在构建系统的建设上面，我们的构建系统除了支持常规的构建插件之外，还支持已有构建工具和未来可能存在的构建工具。  &lt;br /&gt;我们将正常构建过程分为4个阶段：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;收集依赖&lt;/li&gt;
  &lt;li&gt;处理资源&lt;/li&gt;
  &lt;li&gt;处理代码&lt;/li&gt;
  &lt;li&gt;打包签名&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;那么如何保证对已有Gradle插件的支持？最好的方式是不对这个构建过程做太多干涉，保证它们的正常、按顺序执行。所以我们的构建系统在不干扰这个顺序的基础上，把插件的构建过程插入进去，对应正常构建的4个阶段，主要做了如下工作。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;宿主解析依赖之后，分析插件的依赖，进行依赖仲裁和引用计数分析&lt;/li&gt;
  &lt;li&gt;宿主处理资源之前，处理插件资源，规避了资源访问的陷阱，生成需要Merge的资源列表给宿主，开发 美团AAPT 处理插件资源&lt;/li&gt;
  &lt;li&gt;宿主处理代码之中，规避插件API使用的陷阱，复用宿主的Proguard和Gradle插件，做到对原生构建过程的最大兼容。我们也修复了Proguard Mapping的问题，后续会有专门的博客介绍&lt;/li&gt;
  &lt;li&gt;宿主打包签名之前，构建插件APK，计算升级兼容的Hash特征，使用V2签名加快运行时的验证&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;构建系统的流程如下图  &lt;br /&gt;  &lt;img src="https://tech.meituan.com/img/android_hydra/buildflow.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;API陷阱&lt;/h4&gt;
 &lt;p&gt;我们做插件化构建系统还有另外一个非常重要的目的，就是规避“API陷阱”。下面是接入  &lt;a href="https://github.com/alibaba/atlas/blob/master/atlas-docs/guide-for-use/guide_for_bundle.md#bundle&amp;#30340;&amp;#27880;&amp;#24847;&amp;#28857;"&gt;Atlas&lt;/a&gt;所需要注意的部分问题，我们称为“API陷阱”&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;Activity通过overridePendingTransition使用的切换动画的文件要放在主APK中；&lt;/li&gt;
  &lt;li&gt;Bundle内如果有用到自定义style，那么style的parent如果也是自定义的话，parent的定义必须位于主APK中，这是由于5.0以后系统内style查找的固有逻辑导致的，容器内暂不能完全兼容&lt;/li&gt;
  &lt;li&gt;Bundle内部如果有so，则安装时so由于无法解压到APK lib目录中，对于直接通过native层使用dlopen来使用so的情况，会存在一定限制，且会影响后续so动态部署，所以目前bundle内so不建议使用dlopen的方式来使用&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;那我们是怎么做的呢？  &lt;br /&gt;我们用构建工具自动对插件资源进行处理。先把插件独有的依赖从宿主处理的依赖里面抽离，然后为宿主单独准备一份资源目录，这个目录只包括需要merge的资源。  &lt;br /&gt;那么怎么抽离呢？我们看下处理资源的task是如何获得这些资源的。代码在  &lt;code&gt;com.android.build.gradle.tasks.MergeResources$ConfigAction&lt;/code&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;ConventionMappingHelper.map(mergeResourcesTask, &amp;quot;inputResourceSets&amp;quot;,
        new Callable&amp;lt;List&amp;lt;ResourceSet&amp;gt;&amp;gt;() {
            @Override
            public List&amp;lt;ResourceSet&amp;gt; call() throws Exception {
                List&amp;lt;File&amp;gt; generatedResFolders = Lists.newArrayList(
                        scope.getRenderscriptResOutputDir(),
                        scope.getGeneratedResOutputDir());
                if (variantData.getExtraGeneratedResFolders() != null) {
                    generatedResFolders.addAll(
                            variantData.getExtraGeneratedResFolders());
                }
                if (scope.getMicroApkTask() != null &amp;amp;&amp;amp;
                        variantData.getVariantConfiguration().getBuildType()
                                .isEmbedMicroApp()) {
                    generatedResFolders.add(scope.getMicroApkResDirectory());
                }

                return variantData.getVariantConfiguration().getResourceSets(
                        generatedResFolders, includeDependencies, validateEnabled);
            }
        });
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;了解Groovy的同学都知道，设置这个inputResourceSets，其实就是重写了这个mergeResourcesTask的getInputResourceSets方法。那么我们也这可以这么做：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;ConventionMapping conventionMapping =
                (ConventionMapping) ((GroovyObject) variantData.mergeResourcesTask).getProperty(&amp;quot;conventionMapping&amp;quot;);
def srcMethod = conventionMapping._mappings.get(&amp;quot;inputResourceSets&amp;quot;);

conventionMapping.map(&amp;quot;inputResourceSets&amp;quot;, new Callable&amp;lt;List&amp;lt;ResourceSet&amp;gt;&amp;gt;() {
    @Override
    public List&amp;lt;ResourceSet&amp;gt; call() throws Exception {
        List&amp;lt;ResourceSet&amp;gt; res = srcMethod.getValue(null, null)
        ... // 处理这个res
        return res
    }
})
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;对于第一个问题：前面提到的插件为宿主提供的资源文件夹，如果是一个空的没有任何意义。我们会分析插件的AndroidManifest.xml文件，以此作为root，遍历被它引用的所有的资源，不管是文件，还是values文件夹下面的单个value，全部merge进这个文件夹。  &lt;br /&gt;但是只是AndroidManifest.xml文件是不够的，所有传给系统的文件，比如提到的“Activity通过overridePendingTransition使用的切换动画的文件”，也一并放进这个文件夹。这里需要使用ASM扫描插件的所有API调用，类似上面的Theme查找，不细展开了。&lt;/p&gt;
 &lt;p&gt;第二个问题：把插件values里面style的parent也作为检索的root，遍历merge。&lt;/p&gt;
 &lt;p&gt;第三个问题：API陷阱除了资源，还有大量的代码级别的，上面的插件so加载问题就是很典型的一个例子，正常使用System.loadLibrary(path)是不行的，但是可以把它转化成下面的写法：我们发现，如果插件dlopen来加载的so之前被加载过，就不会出现这个问题。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;private static Pattern compile = Pattern.compile(&amp;quot;dlopen failed: library \&amp;quot;lib(.+).so\&amp;quot; not found&amp;quot;);
public static void system_loadLibrary(String libname) {
    LinkedList&amp;lt;String&amp;gt; list = new LinkedList&amp;lt;&amp;gt;();
    list.add(libname);
    while (list.size() &amp;gt; 0) {
        try {
            System.loadLibrary(list.peekFirst());
            list.pop();
        } catch (UnsatisfiedLinkError error) {
            // dlopen failed: library &amp;quot;libglog_init.so&amp;quot; not found
            Matcher matcher = compile.matcher(error.getMessage());
            if (matcher.matches()) {
                String group = matcher.group(1);
                list.addFirst(group);
            } else {
                throw error;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;当然需要替换的API很多，如 getIdentifier、Notification、Glide等等，不一一列举。&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;本文主要介绍美团插件化的设计思路和一些实现。经过我们这些努力，美团平台的业务集成模式可以平滑的在AAR集成模式和插件化集成模式之间无缝切换，且上线几乎没出现兼容问题。目前在美团App最近的几个版本上，搜索、收藏、订单等重要模块都是插件形式加载的。&lt;/p&gt;
 &lt;h2&gt;作者简介&lt;/h2&gt;
 &lt;p&gt;李挺，美团点评技术专家，2014年加入美团。先后负责过多个业务项目和技术项目，致力于推动AOP和字节码技术在美团的应用。曾独立负责美团App预装项目并推动预装实现自动化。主导了美团插件化框架的设计和开发工作，目前工作重心是美团插件化框架的布道和推广。&lt;/p&gt;
 &lt;p&gt;夏伟，美团点评资深工程师，2017年加入美团。目前从事美团插件化开发，美团平台的一些底层工具优化，如AAPT、ProGuard等，专注于Hook技术、逆向研究，习惯从源码中寻找解决方案。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;美团平台客户端技术团队，负责美团平台的基础业务和移动基础设施的开发工作。基于海量用户的美团平台，支撑了美团点评多条业务线的快速发展。同时，我们也在移动开发技术方面做了一些积极的探索，在动态化、质量保障、开发模型等方面有一定积累。客户端技术团队积极采用开源技术的同时，也把我们的一些积累回馈给开源社区，希望跟业界一起推动移动开发效率、质量的提升。&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/57552-%E7%BE%8E%E5%9B%A2-app-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Thu, 12 Oct 2017 23:06:35 CST</pubDate>
    </item>
    <item>
      <title>谷歌中国开发者福利：这里有你需要的所有网站和插件</title>
      <link>https://itindex.net/detail/56602-%E8%B0%B7%E6%AD%8C%E4%B8%AD%E5%9B%BD-%E5%BC%80%E5%8F%91-%E7%A6%8F%E5%88%A9</link>
      <description>&lt;p&gt;  &lt;img src="http://static.leiphone.com/uploads/new/article/740_740/201701/5887efe9beb3d.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;在去年12月的Google开发者大会上，Google发布了几个Google Developers中国网站，它汇集了Google为开发者提供的开发技术资源，包括API 文档、开发案例、以及技术培训的视频。根据Google的官微，它涵盖了以下6个关键开发技术和平台产品信息：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;1. Android: developer.android.google.cn&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Android开发者官方网站面向应用开发者提供了Android SDK以及开发相关的各类文档。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;2. Web: developers.google.cn/web&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;学习如何利用Progressive Web Apps等Web技术来开发新一代的网站或移动应用。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3. Tensor Flow: www.tensorflow.org&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;TensorFlow是一个大规模机器学习的开源框架，提供了对多种深度神经网络的支持。它可以运行在不同的平台上，从智能手机到数据中心，并已经广泛部署到Google多项服务的生产环境中。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;4. Google Play: developer.android.com/distribute/googleplay/index.html&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;借助Google Play，开发者可以触达和服务全球190余个国家和地区的超10亿Android用户。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;5. AdMob: firebase.google.cn/docs/admob&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;使用Google的AdMob, 即可通过精准投放的应用内置广告轻松盈利。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;6. Firebase: firebase.google.cn&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Firebase 是一个移动平台，可以帮助快速开发高品质应用，扩大用户群，并赚取更多收益。Firebase由多种互补功能组成，可以自行组合和匹配这些功能以满足自己的需求。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;此外，一同发布的还有Android Wear 2.0中国开发者预览版。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;在Android Wear 2.0系统中，应用可以由Android Wear手表直接连接至互联网。因此，对于大多数应用来说，手机端的配套应用也就变得不再必要。这也意味着，多数为Android Wear 2.0开发应用的开发者将不再需要引用Google Play services客户端库。&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;根据Google的信息，目前在两个情况下开发者仍然需要引入Google Play Services客户端库来为中国市场开发应用：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;需要与手机直接进行通信的应用 。有一些用例需要Android Wear手表与已配对手机直接连接。在这种情况下，Android Wear 1.0中引入的Data Layer API仍然可以继续使用。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;使用FusedLocationProvider。最新的中国版SDK中加入了定位的支持。在用户的许可下，应用可以通过FusedLocationProvider来接收定位更新。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Android Wear 2.0开发者预览版包括最新的SDK套件，手表测试系统镜像（基于华为手表）。&lt;/p&gt; &lt;p&gt;以下是测试步骤：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;更新到 Android Studio 至 v2.1.1 以上版本&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;访问 Android Wear 2.0 开发者预览版，那里的文件下载与文档下载部分:  developer.android.google.cn/wear/preview/index.html&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;下载手表系统镜像: developer.android.google.cn/wear/preview/downloads.html &lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;在手表上测试应用&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;由于每次要查询这些资料时都需要手动调整相关网址，近日，Google又体贴的为中国开发者做了一个简单的Chrome插件，自动将google.com上的开发者网站URL替换成google.cn上的对应 URL (如果有相对应的.CN域名上的页面)，这样只要点击就能直接打开页面。&lt;/p&gt; &lt;p&gt;此扩展可以在Chrome Web Store上进行下载：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;https://chrome.google.com/webstore/detail/google-cn-devsites/lgfkdmijgggnkoocgaenihkioidaejhd&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;同时它的代码也已经在GitHub上开源了：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;https://github.com/chenzhuo914/google-cn-devsites-extension&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;至于有读者问“要如何打开这个下载页面”，Google的攻城狮也给出了回答：Desire-Passion-Belief。&lt;/p&gt; &lt;p&gt;  &lt;img src="http://static.leiphone.com/uploads/new/article/740_740/201701/5887eeaabe4d1.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;via 谷歌开发者官微（公众号：谷歌开发者）雷锋网&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>业界</category>
      <guid isPermaLink="true">https://itindex.net/detail/56602-%E8%B0%B7%E6%AD%8C%E4%B8%AD%E5%9B%BD-%E5%BC%80%E5%8F%91-%E7%A6%8F%E5%88%A9</guid>
      <pubDate>Wed, 25 Jan 2017 08:30:00 CST</pubDate>
    </item>
    <item>
      <title>工作中经常用到github上优秀、实用、轻量级、无依赖的插件和库</title>
      <link>https://itindex.net/detail/56842-%E5%B7%A5%E4%BD%9C-github-%E8%BD%BB%E9%87%8F%E7%BA%A7</link>
      <description>&lt;p&gt;  &lt;strong&gt;原文收录在我的 GitHub博客 (   &lt;a href="https://github.com/jawil/blog)"&gt;https://github.com/jawil/blog)&lt;/a&gt; ，喜欢的可以关注最新动态，大家一起多交流学习，共同进步，以学习者的身份写博客，记录点滴。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;按照格式推荐好用的插件有福利哦，说不定会送1024论坛邀请码，好自为之，你懂的，嘿嘿嘿。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997801" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;由于github的issues没有TOC菜单栏导航，所以这里方便大家查看，先安利一款Chrome浏览器的插件，感谢github用户@BBcaptain 推荐。  &lt;a href="https://chrome.google.com/webstore/detail/smart-toc/lifgeihcfpkmmlfjbailfpfhbahhibba"&gt;点击我呀，进入商店，自备梯子，如果不会翻墙，赶紧转行&lt;/a&gt;。。。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;效果预览，是不是很方便，图片较多，建议等待一会或者多刷新几下：&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997802" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Echo.js – 简单易用的图片延迟加载插件&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/toddmotto/echo/strong"&gt;https://github.com/toddmotto/...&lt;/a&gt; &lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="https://toddmotto.com/echo-js-simple-javascript-image-lazy-loading//strong"&gt;https://toddmotto.com/echo-js...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：3k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm:npm install echo-js
bower:bower install echojs&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：2KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　Echo.js 是一个独立的延迟加载图片的 JavaScript 插件。Echo.js 不依赖第三方库，压缩后不到1KB大小。 延迟加载是提高网页首屏显示速度的一种很有效的方法，当图片元素进入窗口可视区域的时候，它就会改变图像的 src 属性，从服务端加载所需的图片，这也是一个异步的过程。  &lt;br /&gt;　　  &lt;br /&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="https://jawil.github.io/demo/echo.js/"&gt;https://jawil.github.io/demo/...&lt;/a&gt;  &lt;br /&gt;Demo源码：  &lt;a href="https://github.com/jawil/jawil.github.io/tree/master/demo/echo.js"&gt;https://github.com/jawil/jawi...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997803" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Lazyr.js – 延迟加载图片（Lazy Loading）&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/callmecavs/layzr.js/strong"&gt;https://github.com/callmecavs...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://callmecavs.com/layzr.js//strong"&gt;http://callmecavs.com/layzr.j...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：5k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm:npm install layzr.js --save&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：2.75 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　Lazyr.js 是一个小的、快速的、现代的、相互间无依赖的图片延迟加载库。通过延迟加载图片，让图片出现在（或接近)）视窗才加载来提高页面打开速度。这个库通过保持最少选项并最大化速度。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;跟上面的Echo.js用法类似，喜欢的可以自行去尝试，这里就不再演示了，我一般用Echo.js。&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;better-scroll.js – 小巧，灵活的 JavaScript 模拟滚动条的插件&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/ustbhuangyi/better-scroll/strong"&gt;https://github.com/ustbhuangy...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="https://ustbhuangyi.github.io/better-scroll//strong"&gt;https://ustbhuangyi.github.io...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：300+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm install better-scroll --save-dev
import BScroll from &amp;apos;better-scroll&amp;apos;;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：24 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　better-scroll 是一个只有24.8KB的 JavaScript 模拟浏览器自带滚动条的插件，是在  &lt;strong&gt;   &lt;a href="https://github.com/cubiq/iscroll"&gt;iscroll&lt;/a&gt;&lt;/strong&gt;开源的基础上进行优化的一款插件，简单好用，轻巧高性能，功能强大，API通俗易懂，是一款优秀的scroll插件，抛弃原生滚动条，从现在做起。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="https://jawil.github.io/demo/eleme/"&gt;https://jawil.github.io/demo/...&lt;/a&gt; (PC端切换到移动模式)  &lt;br /&gt;Demo源码：  &lt;a href="https://github.com/jawil/webpack2"&gt;https://github.com/jawil/webp...&lt;/a&gt;  &lt;br /&gt;注：在ustbhuangyi的源码下改进了一下，做成多页面，技术栈：webpack2+vue.js2+sass+axios&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997804" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;better-picker – 一款轻量级IOS风格的JavaScript选择器&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/ustbhuangyi/picker/strong"&gt;https://github.com/ustbhuangy...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://ustbhuangyi.github.io/picker//strong"&gt;http://ustbhuangyi.github.io/...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：200+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm: npm install better-picker --save-dev
import Picker from &amp;apos;better-picker&amp;apos;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：46.5 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　  移动端最好用的的筛选器组件，高仿 ios 的 UIPickerView ，非常流畅的体验，原生 JS 实现，不依赖任何插件和第三方库。  &lt;br /&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://ustbhuangyi.github.io/picker/"&gt;http://ustbhuangyi.github.io/...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997805" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Sortable – 一款用于实现元素拖拽排序的功能的插件&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/RubaXa/Sortable/strong"&gt;https://github.com/RubaXa/Sor...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://rubaxa.github.io/Sortable//strong"&gt;http://rubaxa.github.io/Sorta...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：9k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Bower: bower install sortablejs --save
npm: npm install sortablejs &lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：5 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　Sortable：现代浏览器上用于实现元素拖拽排序的功能，支持 Meteor, AngularJS, React，不依赖 jQuery这玩意。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://rubaxa.github.io/Sortable/"&gt;http://rubaxa.github.io/Sorta...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997806" title=""&gt;&lt;/img&gt;  &lt;br /&gt;   &lt;/p&gt;
 &lt;h2&gt;slick – 功能异常强大的一个图片滑动切换效果库&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/kenwheeler/slick/strong"&gt;https://github.com/kenwheeler...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://kenwheeler.github.io/slick//strong"&gt;http://kenwheeler.github.io/s...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：17k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Bower: bower install slick-carousel --save
npm: npm install slick-carousel
CDNs:
https://cdnjs.com/libraries/slick-carousel
https://www.jsdelivr.com/projects/jquery.slick&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：40 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　slick 是一个功能异常强大的一个图片滑动切换效果库，接口丰富，支持各种动画和各种样式的切换滑动，唯一的缺点就是  &lt;strong&gt;基于jQuery&lt;/strong&gt;，基本废了，现在没人喜欢用jQuery，该淘汰了。。。支持 RequireJS 以及 Bower 安装。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://kenwheeler.github.io/slick/"&gt;http://kenwheeler.github.io/s...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997807" title=""&gt;&lt;/img&gt;   &lt;br /&gt;   &lt;/p&gt;
 &lt;h2&gt;swipe – 非常轻量级的一个图片滑动切换效果库&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/lyfeyaj/Swipe/strong"&gt;https://github.com/lyfeyaj/Sw...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://lyfeyaj.github.io/swipe//strong"&gt;http://lyfeyaj.github.io/swip...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：200+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Bower: bower install swipe-js  --save
npm: npm install swipe-js &lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：5 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　swipe：非常轻量级的一个图片滑动切换效果库, 性能良好, 尤其是对手机的支持, 压缩后的大小约 5kb。可以结合 jQuery、RequireJS 使用。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://lyfeyaj.github.io/swipe/"&gt;http://lyfeyaj.github.io/swipe/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997808" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Slideout.js – 触摸滑出式 Web App 导航菜单&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/mango/slideout/strong"&gt;https://github.com/mango/slid...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="https://slideout.js.org//strong"&gt;https://slideout.js.org/https://github.com/t4t5/sweet...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://t4t5.github.io/sweetalert//strong"&gt;http://t4t5.github.io/sweetal...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：15k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;bower:bower install sweetalert
npm:npm install sweetalert
&amp;lt;script src=&amp;quot;dist/sweetalert.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;dist/sweetalert.css&amp;quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：16 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　 Sweet Alert 是一个替代传统的 JavaScript Alert 的漂亮提示效果。SweetAlert 自动居中对齐在页面中央，不管您使用的是台式电脑，手机或平板电脑看起来效果都很棒。另外提供了丰富的自定义配置选择，可以灵活控制。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://t4t5.github.io/sweetalert/"&gt;http://t4t5.github.io/sweetal...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997809" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;类似插件：limonte/sweetalert2，好像这个最近还在更新，这个感觉更漂亮，大同小异，这里不多做介绍。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/limonte/sweetalert2/strong"&gt;https://github.com/limonte/sw...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：    &lt;a href="https://limonte.github.io/sweetalert2//strong"&gt;https://limonte.github.io/swe...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Awesomplete.js - 比datalist更强大更实用，零依赖的简单自动补全插件&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/leaverou/awesomplete//strong"&gt;https://github.com/leaverou/a...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://leaverou.github.io/awesomplete//strong"&gt;http://leaverou.github.io/awe...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：5k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;  &lt;br /&gt;`npm: npm install awesomplete  &lt;br /&gt;`&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;大小：5 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　Awesomplete 是一款超轻量级的，可定制的，简单的自动完成插件，零依赖，使用现代化标准构建。你可以简单地添加 awesomplete 样式，让它自动处理（你仍然可以通过指定 HTML 属性配置更多选项），您可以用几行 JS 代码，提供更多的自定义。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://leaverou.github.io/awesomplete/"&gt;http://leaverou.github.io/awe...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997810" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h2&gt;Cleave.js – 自动格式化表单输入框的文本内容&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/nosir/cleave.js//strong"&gt;https://github.com/nosir/clea...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://nosir.github.io/cleave.js//strong"&gt;http://nosir.github.io/cleave...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：6k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm:npm install --save cleave.js
bower:bower install --save cleave.js&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：11.1 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　Cleave.js 有一个简单的目的：帮助你自动格式输入的文本内容。 这个想法是提供一个简单的方法来格式化您的输入数据以增加输入字段的可读性。通过使用这个库，您不需要编写任何正则表达式来控制输入文本的格式。然而，这并不意味着取代任何验证或掩码库，你仍应在后端验证数据。它支持信用卡号码、电话号码格式（支持各个国家）、日期格式、数字格式、自定义分隔符，前缀和块模式等，提供 CommonJS/AMD 模式以及ReactJS 组件端口。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://nosir.github.io/cleave.js/"&gt;http://nosir.github.io/cleave...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。  &lt;br /&gt;输入201748自动格式化成2017-04-08，是不是很方便  &lt;br /&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997811" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;/p&gt;
 &lt;h2&gt;Immutable.js – JavaScript 不可变数据集合（Facebook出品）&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/facebook/immutable-js/strong"&gt;https://github.com/facebook/i...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://facebook.github.io/immutable-js//strong"&gt;http://facebook.github.io/imm...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：18k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm install immutable --S-D&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：60 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　不可变数据是指一旦创建就不能被修改的数据，使得应用开发更简单，允许使用函数式编程技术，比如惰性评估。Immutable JS 提供一个惰性 Sequence，允许高效的队列方法链，类似 map 和 filter ，不用创建中间代表。Immutable.js 提供持久化的列表、堆栈、Map， 以及 OrderedMap 等，最大限度地减少需要复制或缓存数据。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;script src=&amp;quot;immutable.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set(&amp;apos;b&amp;apos;, 50);
map1.get(&amp;apos;b&amp;apos;); // 2
map2.get(&amp;apos;b&amp;apos;); // 50
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;更多信息和探讨请移步,这里不多做介绍：  &lt;a href="https://www.zhihu.com/question/28016223"&gt;facebook immutable.js 意义何在，使用场景？&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Popmotion.js – 小巧，灵活的 JavaScript 运动引擎&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/Popmotion/popmotion/strong"&gt;https://github.com/Popmotion/...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="https://popmotion.io//strong"&gt;https://popmotion.io/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：3k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm install --save popmotion
import { tween } from &amp;apos;popmotion&amp;apos;;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：12 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　Popmotion 是一个只有12KB的 JavaScript 运动引擎，可以用来实现动画，物理效果和输入跟踪。原生的DOM支持：CSS，SVG，SVG路径和DOM属性的支持，开箱即用。Popmotion 网站上有很多很赞的效果，赶紧去体验一下。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://codepen.io/popmotion/pen/egwMGQ"&gt;http://codepen.io/popmotion/p...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997812" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;    &lt;/p&gt;
 &lt;h2&gt;Dynamics.js - 创建逼真的物理动画的 JS 库&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/michaelvillar/dynamics.js/strong"&gt;https://github.com/michaelvil...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://dynamicsjs.com//strong"&gt;http://dynamicsjs.com/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：6k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm: npm install dynamics.js
bower: bower install dynamics.js&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：20 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　Popmotion 是一个只有12KB的 JavaScript 运动引擎，可以用来实现动画，物理效果和输入跟踪。原生的DOM支持：CSS，SVG，SVG路径和DOM属性的支持，开箱即用。Popmotion 网站上有很多很赞的效果，赶紧去体验一下。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://dynamicsjs.com/"&gt;http://dynamicsjs.com/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997813" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Rainyday.js – 使用 JavaScript 实现雨滴效果&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/maroslaw/rainyday.js/strong"&gt;https://github.com/maroslaw/r...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://maroslaw.github.io/rainyday.js//strong"&gt;http://maroslaw.github.io/rai...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：5k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在github的dist目录下载rainyday.min.js&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;大小：10 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　Rainyday.js 背后的想法是创建一个 JavaScript 库，利用 HTML5 Canvas 渲染一个雨滴落在玻璃表面的动画。Rainyday.js 有功能可扩展的 API，例如碰撞检测和易于扩展自己的不同的动画组件的实现。它是一个使用 HTML5 特性纯 JavaScript 库，支持大部分现代浏览器。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://maroslaw.github.io/rainyday.js/demo012_1.html"&gt;http://maroslaw.github.io/rai...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997814" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Swiper – 经典的移动触摸滑块轮播插件&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/nolimits4web/Swiper/strong"&gt;https://github.com/nolimits4w...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://idangero.us/swiper//strong"&gt;http://idangero.us/swiper/https://github.com/daniel-lun...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://daniel-lundin.github.io/snabbt.js//strong"&gt;http://daniel-lundin.github.i...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：5k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;bower:bower install snabbt.js
npm:npm install snabbt.js
CDNs:
https://cdnjs.com/libraries/snabbt.js
http://www.jsdelivr.com/#!snabbt&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：16 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　Snabbt.js 是一个简约的 JavaScript 动画库。它会平移，旋转，缩放，倾斜和调整你的元素。通过矩阵乘法运算，变换等可以任何你想要的方式进行组合。最终的结果通过 CSS3 变换矩阵设置。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://daniel-lundin.github.io/snabbt.js/periodic.html"&gt;http://daniel-lundin.github.i...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997815" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;imagesLoaded – 检测网页中的图片是否加载完成&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/desandro/imagesloaded/strong"&gt;https://github.com/desandro/i...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://imagesloaded.desandro.com//strong"&gt;http://imagesloaded.desandro....&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：6k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Bower: bower install imagesloaded --save
npm: npm install imagesloaded
CDNs:
&amp;lt;script src=&amp;quot;https://unpkg.com/imagesloaded@4.1/imagesloaded.pkgd.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;https://unpkg.com/imagesloaded@4.1/imagesloaded.pkgd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：5 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　imagesLoaded 是一个用于来检测网页中的图片是否载入完成的 JavaScript 工具库。支持回调的获取图片加载的进度，还可以绑定自定义事件。可以结合 jQuery、RequireJS 使用。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://codepen.io/desandro/full/hlzaw/"&gt;http://codepen.io/desandro/fu...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997816" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Fort.js – 时尚、现代的表单填写进度提示效果&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/idriskhenchil/Fort.js/strong"&gt;https://github.com/idriskhenc...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="https://github.com/idriskhenchil/Fort.js/strong"&gt;https://github.com/idriskhenc...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：800+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;CDN:
css:
https://cdnjs.cloudflare.com/ajax/libs/Fort.js/2.0.0/fort.min.css
js:
https://cdnjs.cloudflare.com/ajax/libs/Fort.js/2.0.0/fort.min.js&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：6 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　Fort.js 是一款用于时尚、现代的表单填写进度提示效果的 JavaScript 库，你需要做的就是添加表单，剩下的任务就交给 Fort.js 算法了，使用非常简单。提供了Default、Gradient、Sections 以及 Flash 四种效果，满足开发的各种场合需要。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://idriskhenchil.github.io/default/index.html"&gt;http://idriskhenchil.github.i...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997817" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;MagicSuggest – Bootstrap 主题的多选组合框&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/nicolasbize/magicsuggest/strong"&gt;https://github.com/nicolasbiz...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://nicolasbize.com/magicsuggest//strong"&gt;http://nicolasbize.com/magics...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：1k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;github自行进行下载&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：21.8 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　MagicSuggest 是专为 Bootstrap 主题开发的多选组合框。它支持自定义呈现，数据通过 Ajax 异步获取，使用组件自动过滤。它允许空间免费项目，也有动态加载固定的建议。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://nicolasbize.com/magicsuggest/examples.html"&gt;http://nicolasbize.com/magics...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997818" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Numeral.js – 格式化和操作数字的 JavaScript 库&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/adamwdraper/Numeral-js/strong"&gt;https://github.com/adamwdrape...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://numeraljs.com//strong"&gt;http://numeraljs.com/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：4k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm: npm install numeral
CDNs:
&amp;lt;script src=&amp;quot;//cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：10 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　　Numeral.js 是一个用于格式化和操作数字的 JavaScript 库。数字可以格式化为货币，百分比，时间，甚至是小数，千位，和缩写格式，功能十分强大。支持包括中文在内的17种语言。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;var myNumeral = numeral(1000);

var value = myNumeral.value();
// 1000

var myNumeral2 = numeral(&amp;apos;1,000&amp;apos;);

var value2 = myNumeral2.value();
// 1000&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Draggabilly – 轻松实现拖放功能（Drag &amp;amp; Drop）&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/desandro/draggabilly/strong"&gt;https://github.com/desandro/d...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://draggabilly.desandro.com//strong"&gt;http://draggabilly.desandro.c...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：2k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Bower: bower install draggabilly --save
npm: npm install draggabilly
CDNs:
&amp;lt;script src=&amp;quot;https://npmcdn.com/draggabilly@2.1/dist/draggabilly.pkgd.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;https://npmcdn.com/draggabilly@2.1/dist/draggabilly.pkgd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：5 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　Draggabilly 是一个很小的 JavaScript 库，专注于拖放功能。只需要简单的设置参数就可以在你的网站用添加拖放功能。兼容 IE8+ 浏览器，支持多点触摸。可以灵活绑定事件，支持 RequireJS 以及 Bower 安装。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://draggabilly.desandro.com/"&gt;http://draggabilly.desandro.com/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997819" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;Quill – 可以灵活自定义的开源的富文本编辑器&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/quilljs/quill//strong"&gt;https://github.com/quilljs/qu...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="https://quilljs.com/strong"&gt;https://quilljs.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：12k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm: npm install quill
CDNs:
&amp;lt;!-- Main Quill library --&amp;gt;
&amp;lt;script src=&amp;quot;//cdn.quilljs.com/1.0.0/quill.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;//cdn.quilljs.com/1.0.0/quill.min.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;!-- Theme included stylesheets --&amp;gt;
&amp;lt;link href=&amp;quot;//cdn.quilljs.com/1.0.0/quill.snow.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot;&amp;gt;
&amp;lt;link href=&amp;quot;//cdn.quilljs.com/1.0.0/quill.bubble.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot;&amp;gt;

&amp;lt;!-- Core build with no theme, formatting, non-essential modules --&amp;gt;
&amp;lt;link href=&amp;quot;//cdn.quilljs.com/1.0.0/quill.core.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot;&amp;gt;
&amp;lt;script src=&amp;quot;//cdn.quilljs.com/1.0.0/quill.core.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：需求不同，大小不同&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　　Quill 的建立是为了解决现有的所见即所得（WYSIWYG）的编辑器本身就是所见即所得（指不能再扩张）的问题。如果编辑器不正是你想要的方式，这是很难或不可能对其进行自定义以满足您的需求。&lt;/p&gt;
 &lt;p&gt;　　Quill 旨在通过把自身组织成模块，并提供了强大的 API 来构建额外的模块来解决这个问题。它也并没有规定你用样式来定义编辑器皮肤。Quill 还提供了所有你希望富文本编辑器说用于的功能，包括轻量级封装，众多的格式化选项，以及广泛的跨平台支持。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="https://quilljs.com/"&gt;https://quilljs.com/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997820" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;basket.js – 基于 LocalStorage 的资源加载器&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/addyosmani/basket.js/strong"&gt;https://github.com/addyosmani...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="https://addyosmani.com/basket.js//strong"&gt;https://addyosmani.com/basket...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：2k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Bower: bower install basket.js --save
npm: npm install basket.js&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：4 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　basket.js是一款基于 LocalStorage 的资源加载器，可以用来缓存 script 和 css, 手机端使用速度快于浏览器直接缓存。  &lt;br /&gt;　  &lt;br /&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="https://addyosmani.com/basket.js/"&gt;https://addyosmani.com/basket...&lt;/a&gt;  &lt;br /&gt;更多示例请查看官方文档&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;scrollReveal.js – 使元素以非常酷帅的方式进入画布 (Viewpoint)&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/jlmakes/scrollreveal/strong"&gt;https://github.com/jlmakes/sc...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="https://scrollrevealjs.org//strong"&gt;https://scrollrevealjs.org/https://github.com/moment/mom...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://momentjs.com//strong"&gt;http://momentjs.com/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：30k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;bower install moment --save # bower
npm install moment --save   # npm
yarn add moment             # Yarn
Install-Package Moment.js   # NuGet
spm install moment --save   # spm
meteor add momentjs:moment  # meteor&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：16.6 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　 moment.js是一个轻量级的JavaScript库日期解析、验证操作,格式化日期的库。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://momentjs.com/"&gt;http://momentjs.com/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;这是一个GIF动图，不信，你看第一行的日期，时间在走。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997821" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;infinite-scroll – 一款滚动加载按需加载的轻量级插件&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;github：   &lt;a href="https://github.com/infinite-scroll/infinite-scroll/strong"&gt;https://github.com/infinite-s...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;官方网站：   &lt;a href="http://www.infinite-scroll.com//strong"&gt;http://www.infinite-scroll.co...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;star：4k+&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Install：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;github自行下载&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;大小：20 KB&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;功能介绍：&lt;/strong&gt;  &lt;br /&gt;　infinite-scroll是一款滚动加载，滚动到最下到自动加载的轻量级JavaScript插件，简单实用，按需加载提高用户体验，非常适合移动端使用，配合上面的图片懒加载如虎添翼。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo：&lt;/strong&gt;  &lt;br /&gt;效果预览地址：  &lt;a href="http://www.dazeddigital.com/"&gt;http://www.dazeddigital.com/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Demo效果预览：&lt;/strong&gt;  &lt;br /&gt;图片有点大，稍等片刻。建议上面Demo效果预览地址进行预览。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997822" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;   &lt;/p&gt;
 &lt;h2&gt;欢迎大家按照格式补充，持续更新，有什么好用的轮子赶紧滚起来吧！&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;推荐有福利，送1024论坛邀请码，嘿嘿嘿。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997823" title=""&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000008997824" title=""&gt;&lt;/img&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>javascript html5 github html</category>
      <guid isPermaLink="true">https://itindex.net/detail/56842-%E5%B7%A5%E4%BD%9C-github-%E8%BD%BB%E9%87%8F%E7%BA%A7</guid>
      <pubDate>Sun, 09 Apr 2017 11:09:41 CST</pubDate>
    </item>
    <item>
      <title>DexClassLoader 实现 Android 插件加载</title>
      <link>https://itindex.net/detail/54725-dexclassloader-android-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;h3&gt;Java 中的 ClassLoader:&lt;/h3&gt; &lt;p&gt;Java 中   &lt;code&gt;ClassLoader&lt;/code&gt; 用于动态加载   &lt;code&gt;Class&lt;/code&gt; 到 JVM，包含    &lt;code&gt;BootstrapClassLoader&lt;/code&gt;（C++ 编写，用于加载系统核心类）、  &lt;code&gt;ExtClassLoader&lt;/code&gt;（用于加载 lib/ext/ 目录的扩展 API）、  &lt;code&gt;AppClassLoader&lt;/code&gt;（加载   &lt;code&gt;CLASSPATH&lt;/code&gt; 目录下的类）。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;双亲委托机制：&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;任何自定义 ClassLoader 都必须继承     &lt;code&gt;ClassLoader&lt;/code&gt; 抽象类，并指定其 parent 加载器，默认为     &lt;code&gt;BootstrapClassLoader&lt;/code&gt;;&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;任何自定义 ClassLoader 在加载一个类之前都会先委托其 parent 去加载，只有 parent 加载失败才会自己加载；&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;     &lt;p&gt;这样既可以防止重复加载，又可以排除安全隐患（防止用户替换系统核心类）；&lt;/p&gt;
&lt;/li&gt;
    &lt;li&gt;     &lt;p&gt;所以一般只需要重写       &lt;code&gt;findClass()&lt;/code&gt;方法即可（在 parent 加载失败时调用）；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;双亲委托机制是在     &lt;code&gt;loadClass()&lt;/code&gt; 方法实现的，要想避开（自己验证安全性，比如 Tomcat 的     &lt;code&gt;WebAppClassLoader&lt;/code&gt;），必须重写     &lt;code&gt;loadClass()&lt;/code&gt; 方法；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;自定义 ClassLoader 用途：&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;在执行非置信代码前先做签名认证等；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;从网络、数据库等动态加载类；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;类的卸载：&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;只有当类的实例被回收，才会被 unload，但被     &lt;code&gt;BootstrapClassLoader&lt;/code&gt; 加载的系统类除外；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;重复加载类会报异常，只能重新定义新的 ClassLoader 再次加载；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;a&gt;&lt;/a&gt;
 &lt;h3&gt;Dalvik 的 ClassLoader:&lt;/h3&gt; &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;Android 里     &lt;code&gt;ClassLoader&lt;/code&gt; 的     &lt;code&gt;defineClass()&lt;/code&gt; 方法直接抛出     &lt;code&gt;UnsupportedOperationException&lt;/code&gt; 异常，必须借助     &lt;code&gt;DexClassLoader&lt;/code&gt; 和     &lt;code&gt;PathClassLoader&lt;/code&gt;；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;DexClassLoader&lt;/code&gt; 和     &lt;code&gt;PathClassLoader&lt;/code&gt; 都遵循双亲委托机制，因为只重写了     &lt;code&gt;findClass()&lt;/code&gt; 方法，没有重写     &lt;code&gt;loadClass()&lt;/code&gt; 方法；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;Dalvik 虚拟机识别的是     &lt;code&gt;DexFile&lt;/code&gt; 而不是     &lt;code&gt;JarFile&lt;/code&gt;；且     &lt;code&gt;DexFile.loadClass()&lt;/code&gt; 方法必须通过类加载器调用，否则无效；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;利用 DexClassLoader 实现 Android 插件加载:&lt;/h3&gt; &lt;p&gt;比如我们在主应用 HostApp 中需要调用 视频插件 VideoPlayerPlugin 中的 playVideo() 方法。&lt;/p&gt;
 &lt;h4&gt;  &lt;u&gt;给插件加入 Intent 标识&lt;/u&gt;:&lt;/h4&gt; &lt;p&gt;HostApp 要查询插件信息，只能通过   &lt;code&gt;PackageManager&lt;/code&gt;。这里我首先想到的是直接通过其   &lt;code&gt;getPackageInfo()&lt;/code&gt; 方法。但是试想，可能插件有很多个，而且包名不同。所以最好还是通过在插件中定义空的   &lt;code&gt;Activity&lt;/code&gt; 并加入   &lt;code&gt;Intent&lt;/code&gt; 标识，然后调用   &lt;code&gt;queryIntentActivities()&lt;/code&gt; 方法去查询插件信息:&lt;/p&gt;
 &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;&amp;lt;activity android:name=&amp;quot;.plugin&amp;quot;&amp;gt;     &lt;br /&gt;  &amp;lt;intent-filter&amp;gt;     &lt;br /&gt;    &amp;lt;action android:name=&amp;quot;com.rincliu.videoplayerplugin&amp;quot;/&amp;gt;     &lt;br /&gt;  &amp;lt;/intent-filter&amp;gt;     &lt;br /&gt;&amp;lt;/activity&amp;gt;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
 &lt;h4&gt;  &lt;u&gt;查询插件信息&lt;/u&gt;:&lt;/h4&gt; &lt;p&gt;首先使用   &lt;code&gt;PackageMananer&lt;/code&gt; 查询到插件的   &lt;code&gt;packageName&lt;/code&gt; 和   &lt;code&gt;ApplicationInfo&lt;/code&gt;:&lt;/p&gt;
 &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;Intent intent = new Intent(&amp;quot;com.rincliu.videoplayerplugin&amp;quot;);     &lt;br /&gt;List&amp;lt;ResolveInfo&amp;gt; plugins = getPackageManager().queryIntentActivities(intent, 0);     &lt;br /&gt;ActivityInfo act = plugins.get(0).activityInfo;     &lt;br /&gt;String packageName = act.packageName;     &lt;br /&gt;ApplicationInfo app = act.applicationInfo;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
 &lt;p&gt;上面是直接读取的第一条信息(  &lt;code&gt;plugins&lt;/code&gt; 要先判空)，如果有很多种插件，或者有好几个版本，这样就需要继续读取插件的版本号等配置信息作进一步区分：&lt;/p&gt;
 &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;Resources res = pm.getResourcesForApplication(packageName);     &lt;br /&gt;int resId = res.getIdentifier(&amp;quot;version&amp;quot;, &amp;quot;string&amp;quot;, packageName);     &lt;br /&gt;String version = res.getString(resId);     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
 &lt;h4&gt;  &lt;u&gt;使用 DexClassLoader 调用插件&lt;/u&gt;：&lt;/h4&gt; &lt;p&gt;创建   &lt;code&gt;DexClassLoader&lt;/code&gt; 对象：&lt;/p&gt;
 &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;String dexSourceDir = app.sourceDir;     &lt;br /&gt;String dexOutputDir = getApplicationInfo().dataDir;     &lt;br /&gt;String dexLibDir = app.nativeLibraryDir;     &lt;br /&gt;ClassLoader parentLoader = this.getClass().getClassLoader();     &lt;br /&gt;DexClassLoader loader = new DexClassLoader(dexSourceDir, dexOutputDir, dexLibDir, parentLoader);     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
 &lt;p&gt;使用反射调用插件中的方法：&lt;/p&gt;
 &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;try {     &lt;br /&gt;  Class&amp;lt;?&amp;gt; clazz  = loader.loadClass(packageName + &amp;quot;.VideoPlayerPlugin&amp;quot;);     &lt;br /&gt;       &lt;br /&gt;  //Object obj = clazz.newInstance();     &lt;br /&gt;  Constructor&amp;lt;?&amp;gt; localConstructor = clazz.getConstructor();     &lt;br /&gt;  Object obj = localConstructor.newInstance();     &lt;br /&gt;       &lt;br /&gt;  //Method method = ((Class&amp;lt;?&amp;gt;) obj).getMethod(&amp;quot;play&amp;quot;, String.class);     &lt;br /&gt;  Method method = clazz.getDeclaredMethod(&amp;quot;play&amp;quot;, String.class);     &lt;br /&gt;     &lt;br /&gt;  method.invoke(obj, &amp;quot;/sdcard/demo.mp4&amp;quot;);     &lt;br /&gt;} catch (Exception e) {}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/RincLiu/Roid-Snippets/tree/master/DexClassLoader" rel="external" target="_blank"&gt;完整代码&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Android Java dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/54725-dexclassloader-android-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Sun, 19 Apr 2015 12:00:00 CST</pubDate>
    </item>
    <item>
      <title>Android插件化（一）：使用改进的MultiDex动态加载assets中的apk</title>
      <link>https://itindex.net/detail/54960-android-%E6%8F%92%E4%BB%B6-multidex</link>
      <description>&lt;h1&gt;Android插件化（一）：使用改进的MultiDex动态加载assets中的apk&lt;/h1&gt;



 &lt;h2&gt;简介&lt;/h2&gt;

 &lt;p&gt;为了解决65535方法数超标的问题，Google推荐使用MultiDex来加载classes2.dex,classes3.dex等等，其基本思想就是在运行时动态修改ClassLoader，以达到动态加载类的目的。为了更好的理解MultiDex的工作原理，可以先看一下ClassLoader的工作原理[1].然后参见PathClassLoader的源码，当然，它继承自BaseDexClassLoader，主要源码都在BaseDexClassLoader中。MultiDex加载离线apk的过程如下：   &lt;br /&gt;
  &lt;img alt="MultiDex" src="http://img.blog.csdn.net/20151227104039229" title=""&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;我们可以在Application的onCreate方法或者Activity的attachBaseContext方法中开发加载。&lt;/p&gt;

 &lt;h2&gt;动态加载assets中的apk&lt;/h2&gt;

 &lt;p&gt;根据MultiDex的源码，我们可以修改其install方法，然后从assets资源中解压出所需要加载的apk文件，然后调用installSecondaryDexes方法，将其加载到当前Application的ClassLoader当中，这样，在运行的时候，就可以通过当前的ClassLoader查找到离线apk中的类了。&lt;/p&gt;



 &lt;h3&gt;[AssetsMultiDexLoader.class]&lt;/h3&gt;



 &lt;pre&gt;  &lt;code&gt;package net.mobctrl.hostapk;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.zip.ZipFile;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.util.Log;
import dalvik.system.DexFile;

/**
 * @Author Zheng Haibo
 * @Mail mochuan.zhb@alibaba-inc.com
 * @Company Alibaba Group
 * @PersonalWebsite http://www.mobctrl.net
 * @version $Id: AssetsDex.java, v 0.1 2015年12月10日 下午5:36:23 mochuan.zhb Exp $
 * @Description ClassLoader
 */
public class AssetsMultiDexLoader {

    private static final String TAG = &amp;quot;AssetsApkLoader&amp;quot;;

    private static boolean installed = false;

    private static final int MAX_SUPPORTED_SDK_VERSION = 20;

    private static final int MIN_SDK_VERSION = 4;

    private static final Set&amp;lt;String&amp;gt; installedApk = new HashSet&amp;lt;String&amp;gt;();

    private AssetsMultiDexLoader() {

    }

    /**
     * 安装Assets中的apk文件
     * 
     * @param context
     */
    public static void install(Context context) {
        Log.i(TAG, &amp;quot;install...&amp;quot;);
        if (installed) {
            return;
        }
        try {
            clearOldDexDir(context);
        } catch (Throwable t) {
            Log.w(TAG,
                    &amp;quot;Something went wrong when trying to clear old MultiDex extraction, &amp;quot;
                            + &amp;quot;continuing without cleaning.&amp;quot;, t);
        }
        AssetsManager.copyAllAssetsApk(context);
        Log.i(TAG, &amp;quot;install&amp;quot;);
        if (Build.VERSION.SDK_INT &amp;lt; MIN_SDK_VERSION) {
            throw new RuntimeException(&amp;quot;Multi dex installation failed. SDK &amp;quot;
                    + Build.VERSION.SDK_INT
                    + &amp;quot; is unsupported. Min SDK version is &amp;quot; + MIN_SDK_VERSION
                    + &amp;quot;.&amp;quot;);
        }
        try {
            ApplicationInfo applicationInfo = getApplicationInfo(context);
            if (applicationInfo == null) {
                // Looks like running on a test Context, so just return without
                // patching.
                return;
            }
            synchronized (installedApk) {
                String apkPath = applicationInfo.sourceDir;
                if (installedApk.contains(apkPath)) {
                    return;
                }
                installedApk.add(apkPath);
                if (Build.VERSION.SDK_INT &amp;gt; MAX_SUPPORTED_SDK_VERSION) {
                    Log.w(TAG,
                            &amp;quot;MultiDex is not guaranteed to work in SDK version &amp;quot;
                                    + Build.VERSION.SDK_INT
                                    + &amp;quot;: SDK version higher than &amp;quot;
                                    + MAX_SUPPORTED_SDK_VERSION
                                    + &amp;quot; should be backed by &amp;quot;
                                    + &amp;quot;runtime with built-in multidex capabilty but it&amp;apos;s not the &amp;quot;
                                    + &amp;quot;case here: java.vm.version=\&amp;quot;&amp;quot;
                                    + System.getProperty(&amp;quot;java.vm.version&amp;quot;)
                                    + &amp;quot;\&amp;quot;&amp;quot;);
                }
                /*
                 * The patched class loader is expected to be a descendant of
                 * dalvik.system.BaseDexClassLoader. We modify its
                 * dalvik.system.DexPathList pathList field to append additional
                 * DEX file entries.
                 */
                ClassLoader loader;
                try {
                    loader = context.getClassLoader();
                } catch (RuntimeException e) {
                    /*
                     * Ignore those exceptions so that we don&amp;apos;t break tests
                     * relying on Context like a android.test.mock.MockContext
                     * or a android.content.ContextWrapper with a null base
                     * Context.
                     */
                    Log.w(TAG,
                            &amp;quot;Failure while trying to obtain Context class loader. &amp;quot;
                                    + &amp;quot;Must be running in test mode. Skip patching.&amp;quot;,
                            e);
                    return;
                }
                if (loader == null) {
                    // Note, the context class loader is null when running
                    // Robolectric tests.
                    Log.e(TAG,
                            &amp;quot;Context class loader is null. Must be running in test mode. &amp;quot;
                                    + &amp;quot;Skip patching.&amp;quot;);
                    return;
                }
                // 获取dex文件列表
                File dexDir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE);
                File[] szFiles = dexDir.listFiles(new FilenameFilter() {

                    @Override
                    public boolean accept(File dir, String filename) {
                        return filename.endsWith(AssetsManager.FILE_FILTER);
                    }
                });
                List&amp;lt;File&amp;gt; files = new ArrayList&amp;lt;File&amp;gt;();
                for (File f : szFiles) {
                    Log.i(TAG, &amp;quot;load file:&amp;quot; + f.getName());
                    files.add(f);
                }
                Log.i(TAG, &amp;quot;loader before:&amp;quot; + context.getClassLoader());
                installSecondaryDexes(loader, dexDir, files);
                Log.i(TAG, &amp;quot;loader end:&amp;quot; + context.getClassLoader());
            }
        } catch (Exception e) {
            Log.e(TAG, &amp;quot;Multidex installation failure&amp;quot;, e);
            throw new RuntimeException(&amp;quot;Multi dex installation failed (&amp;quot;
                    + e.getMessage() + &amp;quot;).&amp;quot;);
        }
        installed = true;
        Log.i(TAG, &amp;quot;install done&amp;quot;);
    }

    private static ApplicationInfo getApplicationInfo(Context context)
            throws NameNotFoundException {
        PackageManager pm;
        String packageName;
        try {
            pm = context.getPackageManager();
            packageName = context.getPackageName();
        } catch (RuntimeException e) {
            /*
             * Ignore those exceptions so that we don&amp;apos;t break tests relying on
             * Context like a android.test.mock.MockContext or a
             * android.content.ContextWrapper with a null base Context.
             */
            Log.w(TAG,
                    &amp;quot;Failure while trying to obtain ApplicationInfo from Context. &amp;quot;
                            + &amp;quot;Must be running in test mode. Skip patching.&amp;quot;, e);
            return null;
        }
        if (pm == null || packageName == null) {
            // This is most likely a mock context, so just return without
            // patching.
            return null;
        }
        ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
                PackageManager.GET_META_DATA);
        return applicationInfo;
    }

    private static void installSecondaryDexes(ClassLoader loader, File dexDir,
            List&amp;lt;File&amp;gt; files) throws IllegalArgumentException,
            IllegalAccessException, NoSuchFieldException,
            InvocationTargetException, NoSuchMethodException, IOException {
        if (!files.isEmpty()) {
            if (Build.VERSION.SDK_INT &amp;gt;= 19) {
                V19.install(loader, files, dexDir);
            } else if (Build.VERSION.SDK_INT &amp;gt;= 14) {
                V14.install(loader, files, dexDir);
            } else {
                V4.install(loader, files);
            }
        }
    }

    /**
     * Locates a given field anywhere in the class inheritance hierarchy.
     *
     * @param instance
     *            an object to search the field into.
     * @param name
     *            field name
     * @return a field object
     * @throws NoSuchFieldException
     *             if the field cannot be located
     */
    private static Field findField(Object instance, String name)
            throws NoSuchFieldException {
        for (Class&amp;lt;?&amp;gt; clazz = instance.getClass(); clazz != null; clazz = clazz
                .getSuperclass()) {
            try {
                Field field = clazz.getDeclaredField(name);

                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                return field;
            } catch (NoSuchFieldException e) {
                // ignore and search next
            }
        }

        throw new NoSuchFieldException(&amp;quot;Field &amp;quot; + name + &amp;quot; not found in &amp;quot;
                + instance.getClass());
    }

    /**
     * Locates a given method anywhere in the class inheritance hierarchy.
     *
     * @param instance
     *            an object to search the method into.
     * @param name
     *            method name
     * @param parameterTypes
     *            method parameter types
     * @return a method object
     * @throws NoSuchMethodException
     *             if the method cannot be located
     */
    private static Method findMethod(Object instance, String name,
            Class&amp;lt;?&amp;gt;... parameterTypes) throws NoSuchMethodException {
        for (Class&amp;lt;?&amp;gt; clazz = instance.getClass(); clazz != null; clazz = clazz
                .getSuperclass()) {
            try {
                Method method = clazz.getDeclaredMethod(name, parameterTypes);

                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }

                return method;
            } catch (NoSuchMethodException e) {
                // ignore and search next
            }
        }

        throw new NoSuchMethodException(&amp;quot;Method &amp;quot; + name + &amp;quot; with parameters &amp;quot;
                + Arrays.asList(parameterTypes) + &amp;quot; not found in &amp;quot;
                + instance.getClass());
    }

    /**
     * Replace the value of a field containing a non null array, by a new array
     * containing the elements of the original array plus the elements of
     * extraElements.
     * 
     * @param instance
     *            the instance whose field is to be modified.
     * @param fieldName
     *            the field to modify.
     * @param extraElements
     *            elements to append at the end of the array.
     */
    private static void expandFieldArray(Object instance, String fieldName,
            Object[] extraElements) throws NoSuchFieldException,
            IllegalArgumentException, IllegalAccessException {
        Field jlrField = findField(instance, fieldName);
        Object[] original = (Object[]) jlrField.get(instance);
        Object[] combined = (Object[]) Array.newInstance(original.getClass()
                .getComponentType(), original.length + extraElements.length);
        System.arraycopy(original, 0, combined, 0, original.length);
        System.arraycopy(extraElements, 0, combined, original.length,
                extraElements.length);
        jlrField.set(instance, combined);
    }

    private static void clearOldDexDir(Context context) throws Exception {
        File dexDir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE);
        if (dexDir.isDirectory()) {
            Log.i(TAG, &amp;quot;Clearing old secondary dex dir (&amp;quot; + dexDir.getPath()
                    + &amp;quot;).&amp;quot;);
            File[] files = dexDir.listFiles();
            if (files == null) {
                Log.w(TAG, &amp;quot;Failed to list secondary dex dir content (&amp;quot;
                        + dexDir.getPath() + &amp;quot;).&amp;quot;);
                return;
            }
            for (File oldFile : files) {
                Log.i(TAG, &amp;quot;Trying to delete old file &amp;quot; + oldFile.getPath()
                        + &amp;quot; of size &amp;quot; + oldFile.length());
                if (!oldFile.delete()) {
                    Log.w(TAG, &amp;quot;Failed to delete old file &amp;quot; + oldFile.getPath());
                } else {
                    Log.i(TAG, &amp;quot;Deleted old file &amp;quot; + oldFile.getPath());
                }
            }
            if (!dexDir.delete()) {
                Log.w(TAG,
                        &amp;quot;Failed to delete secondary dex dir &amp;quot;
                                + dexDir.getPath());
            } else {
                Log.i(TAG, &amp;quot;Deleted old secondary dex dir &amp;quot; + dexDir.getPath());
            }
        }
    }

    /**
     * Installer for platform versions 19.
     */
    private static final class V19 {

        private static void install(ClassLoader loader,
                List&amp;lt;File&amp;gt; additionalClassPathEntries, File optimizedDirectory)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException,
                NoSuchMethodException {
            /*
             * The patched class loader is expected to be a descendant of
             * dalvik.system.BaseDexClassLoader. We modify its
             * dalvik.system.DexPathList pathList field to append additional DEX
             * file entries.
             */
            Field pathListField = findField(loader, &amp;quot;pathList&amp;quot;);
            Object dexPathList = pathListField.get(loader);
            ArrayList&amp;lt;IOException&amp;gt; suppressedExceptions = new ArrayList&amp;lt;IOException&amp;gt;();
            expandFieldArray(
                    dexPathList,
                    &amp;quot;dexElements&amp;quot;,
                    makeDexElements(dexPathList, new ArrayList&amp;lt;File&amp;gt;(
                            additionalClassPathEntries), optimizedDirectory,
                            suppressedExceptions));
            if (suppressedExceptions.size() &amp;gt; 0) {
                for (IOException e : suppressedExceptions) {
                    Log.w(TAG, &amp;quot;Exception in makeDexElement&amp;quot;, e);
                }
                Field suppressedExceptionsField = findField(loader,
                        &amp;quot;dexElementsSuppressedExceptions&amp;quot;);
                IOException[] dexElementsSuppressedExceptions = (IOException[]) suppressedExceptionsField
                        .get(loader);

                if (dexElementsSuppressedExceptions == null) {
                    dexElementsSuppressedExceptions = suppressedExceptions
                            .toArray(new IOException[suppressedExceptions
                                    .size()]);
                } else {
                    IOException[] combined = new IOException[suppressedExceptions
                            .size() + dexElementsSuppressedExceptions.length];
                    suppressedExceptions.toArray(combined);
                    System.arraycopy(dexElementsSuppressedExceptions, 0,
                            combined, suppressedExceptions.size(),
                            dexElementsSuppressedExceptions.length);
                    dexElementsSuppressedExceptions = combined;
                }

                suppressedExceptionsField.set(loader,
                        dexElementsSuppressedExceptions);
            }
        }

        /**
         * A wrapper around
         * {@code private static final dalvik.system.DexPathList#makeDexElements}
         * .
         */
        private static Object[] makeDexElements(Object dexPathList,
                ArrayList&amp;lt;File&amp;gt; files, File optimizedDirectory,
                ArrayList&amp;lt;IOException&amp;gt; suppressedExceptions)
                throws IllegalAccessException, InvocationTargetException,
                NoSuchMethodException {
            Method makeDexElements = findMethod(dexPathList, &amp;quot;makeDexElements&amp;quot;,
                    ArrayList.class, File.class, ArrayList.class);

            return (Object[]) makeDexElements.invoke(dexPathList, files,
                    optimizedDirectory, suppressedExceptions);
        }
    }

    /**
     * Installer for platform versions 14, 15, 16, 17 and 18.
     */
    private static final class V14 {

        private static void install(ClassLoader loader,
                List&amp;lt;File&amp;gt; additionalClassPathEntries, File optimizedDirectory)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException,
                NoSuchMethodException {
            /*
             * The patched class loader is expected to be a descendant of
             * dalvik.system.BaseDexClassLoader. We modify its
             * dalvik.system.DexPathList pathList field to append additional DEX
             * file entries.
             */
            Field pathListField = findField(loader, &amp;quot;pathList&amp;quot;);
            Object dexPathList = pathListField.get(loader);
            expandFieldArray(
                    dexPathList,
                    &amp;quot;dexElements&amp;quot;,
                    makeDexElements(dexPathList, new ArrayList&amp;lt;File&amp;gt;(
                            additionalClassPathEntries), optimizedDirectory));
        }

        /**
         * A wrapper around
         * {@code private static final dalvik.system.DexPathList#makeDexElements}
         * .
         */
        private static Object[] makeDexElements(Object dexPathList,
                ArrayList&amp;lt;File&amp;gt; files, File optimizedDirectory)
                throws IllegalAccessException, InvocationTargetException,
                NoSuchMethodException {
            Method makeDexElements = findMethod(dexPathList, &amp;quot;makeDexElements&amp;quot;,
                    ArrayList.class, File.class);

            return (Object[]) makeDexElements.invoke(dexPathList, files,
                    optimizedDirectory);
        }
    }

    /**
     * Installer for platform versions 4 to 13.
     */
    private static final class V4 {
        private static void install(ClassLoader loader,
                List&amp;lt;File&amp;gt; additionalClassPathEntries)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, IOException {
            /*
             * The patched class loader is expected to be a descendant of
             * dalvik.system.DexClassLoader. We modify its fields mPaths,
             * mFiles, mZips and mDexs to append additional DEX file entries.
             */
            int extraSize = additionalClassPathEntries.size();

            Field pathField = findField(loader, &amp;quot;path&amp;quot;);

            StringBuilder path = new StringBuilder(
                    (String) pathField.get(loader));
            String[] extraPaths = new String[extraSize];
            File[] extraFiles = new File[extraSize];
            ZipFile[] extraZips = new ZipFile[extraSize];
            DexFile[] extraDexs = new DexFile[extraSize];
            for (ListIterator&amp;lt;File&amp;gt; iterator = additionalClassPathEntries
                    .listIterator(); iterator.hasNext();) {
                File additionalEntry = iterator.next();
                String entryPath = additionalEntry.getAbsolutePath();
                path.append(&amp;apos;:&amp;apos;).append(entryPath);
                int index = iterator.previousIndex();
                extraPaths[index] = entryPath;
                extraFiles[index] = additionalEntry;
                extraZips[index] = new ZipFile(additionalEntry);
                extraDexs[index] = DexFile.loadDex(entryPath, entryPath
                        + &amp;quot;.dex&amp;quot;, 0);
            }

            pathField.set(loader, path.toString());
            expandFieldArray(loader, &amp;quot;mPaths&amp;quot;, extraPaths);
            expandFieldArray(loader, &amp;quot;mFiles&amp;quot;, extraFiles);
            expandFieldArray(loader, &amp;quot;mZips&amp;quot;, extraZips);
            expandFieldArray(loader, &amp;quot;mDexs&amp;quot;, extraDexs);
        }
    }

}
&lt;/code&gt;&lt;/pre&gt;



 &lt;h3&gt;[AssetsManager.java]&lt;/h3&gt;



 &lt;pre&gt;  &lt;code&gt;package net.mobctrl.hostapk;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;

/**
 * @Author Zheng Haibo
 * @Mail mochuan.zhb@alibaba-inc.com
 * @Company Alibaba Group
 * @PersonalWebsite http://www.mobctrl.net
 * @version $Id: AssetsManager.java, v 0.1 2015年12月11日 下午4:41:10 mochuan.zhb Exp $
 * @Description
 */
public class AssetsManager {

    public static final String TAG = &amp;quot;AssetsApkLoader&amp;quot;;

    //从assets复制出去的apk的目标目录
    public static final String APK_DIR = &amp;quot;third_apk&amp;quot;;

    //文件结尾过滤
    public static final String FILE_FILTER = &amp;quot;.apk&amp;quot;;


    /**
     * 将资源文件中的apk文件拷贝到私有目录中
     * 
     * @param context
     */
    public static void copyAllAssetsApk(Context context) {

        AssetManager assetManager = context.getAssets();
        long startTime = System.currentTimeMillis();
        try {
            File dex = context.getDir(APK_DIR, Context.MODE_PRIVATE);
            dex.mkdir();
            String []fileNames = assetManager.list(&amp;quot;&amp;quot;);
            for(String fileName:fileNames){
                if(!fileName.endsWith(FILE_FILTER)){
                    return;
                }
                InputStream in = null;
                OutputStream out = null;
                in = assetManager.open(fileName);
                File f = new File(dex, fileName);
                if (f.exists() &amp;amp;&amp;amp; f.length() == in.available()) {
                    Log.i(TAG, fileName+&amp;quot;no change&amp;quot;);
                    return;
                }
                Log.i(TAG, fileName+&amp;quot; chaneged&amp;quot;);
                out = new FileOutputStream(f);
                byte[] buffer = new byte[2048];
                int read;
                while ((read = in.read(buffer)) != -1) {
                    out.write(buffer, 0, read);
                }
                in.close();
                in = null;
                out.flush();
                out.close();
                out = null;
                Log.i(TAG, fileName+&amp;quot; copy over&amp;quot;);
            }
            Log.i(TAG,&amp;quot;###copyAssets time = &amp;quot;+(System.currentTimeMillis() - startTime));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;以上就是从assets中加载apk的核心代码。&lt;/p&gt;



 &lt;h2&gt;DEMO运行&lt;/h2&gt;

 &lt;p&gt;打开BundleApk项目，编译成apk。然后将BundleApk.apk文件拷贝到HostApk项目的assets目录，在HostApk的MainActivity方法的onCreate当中，调用AssetsMultiDexLoader.install(getApplicationContext());加载BundleApk.apk。然后我们就可以通过如下两种方式调用BundleApk中的类：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;Class.forName   &lt;br /&gt;
由于我们的HostApk没有BundleApk类的引用，所以我们需要用反射的方式调用。&lt;/li&gt;
&lt;/ul&gt;



 &lt;pre&gt;  &lt;code&gt;private void loadClass(){
        try{
            Class&amp;lt;?&amp;gt; clazz = Class.forName(&amp;quot;net.mobctrl.normal.apk.FileUtils&amp;quot;);

            Constructor&amp;lt;?&amp;gt; constructor = clazz.getConstructor();
            Object bundleUtils = constructor.newInstance();

            Method printSumMethod = clazz.getMethod(&amp;quot;print&amp;quot;, Context.class,String.class);
            printSumMethod.setAccessible(true);
            printSumMethod.invoke(bundleUtils,
                    getApplicationContext(), &amp;quot;Hello&amp;quot;);
        }catch(Exception e){
            e.printStackTrace();
        }

    }&lt;/code&gt;&lt;/pre&gt;

 &lt;ul&gt;
  &lt;li&gt;loadClass   &lt;br /&gt;
我们也可以直接获取当前的ClassLoader对象，然后调用其loadClass方法&lt;/li&gt;
&lt;/ul&gt;



 &lt;pre&gt;  &lt;code&gt;@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void loadApk() {
        try {
            Class&amp;lt;?&amp;gt; clazz = getClassLoader()
                    .loadClass(&amp;quot;net.mobctrl.normal.apk.Utils&amp;quot;);
            Constructor&amp;lt;?&amp;gt; constructor = clazz.getConstructor();
            Object bundleUtils = constructor.newInstance();

            Method printSumMethod = clazz.getMethod(&amp;quot;printSum&amp;quot;, Context.class,
                    int.class, int.class, String.class);
            printSumMethod.setAccessible(true);
            Integer sum = (Integer)printSumMethod.invoke(bundleUtils,
                    getApplicationContext(), 10, 20, &amp;quot;计算结果&amp;quot;);
            System.out.println(&amp;quot;debug:sum = &amp;quot; + sum);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }&lt;/code&gt;&lt;/pre&gt;



 &lt;h1&gt;参考文章&lt;/h1&gt;

 &lt;p&gt;[1].  &lt;a href="http://segmentfault.com/a/1190000004062880"&gt;ClassLoader的工作原理&lt;/a&gt;  &lt;br /&gt;
[2].  &lt;a href="https://android.googlesource.com/platform/libcore-snapshot/+/ics-mr1/dalvik/src/main/java/dalvik/system"&gt;PathClassLoader源码&lt;/a&gt;  &lt;br /&gt;
[3].  &lt;a href="https://android.googlesource.com/platform/frameworks/multidex/+/d79604bd38c101b54e41745f85ddc2e04d978af2/library/src/android/support/multidex"&gt;MultiDex源码&lt;/a&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;
    作者：NUPTboyZHB 发表于2015/12/27 10:41:47   &lt;a href="http://blog.csdn.net/nupt123456789/article/details/50411581"&gt;原文链接&lt;/a&gt;
&lt;/div&gt;
 &lt;div&gt;
    阅读：0 评论：0   &lt;a href="http://blog.csdn.net/nupt123456789/article/details/50411581#comments" target="_blank"&gt;查看评论&lt;/a&gt;
&lt;/div&gt;

&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/54960-android-%E6%8F%92%E4%BB%B6-multidex</guid>
      <pubDate>Sun, 27 Dec 2015 18:41:47 CST</pubDate>
    </item>
    <item>
      <title>基于插件开发的Android实现流程</title>
      <link>https://itindex.net/detail/53909-%E6%8F%92%E4%BB%B6-%E5%BC%80%E5%8F%91-android</link>
      <description>&lt;p&gt;转载请注明地址：http://blog.csdn.net/droyon/article/details/20951797&lt;/p&gt;
 &lt;p&gt;本文记述“柯元旦”Android内核剖析中基于类装载器的“插件”架构。&lt;/p&gt;
 &lt;p&gt;插件的概念：&lt;/p&gt;
 &lt;p&gt;1、插件不能独立运行，而必须运行于一个宿主程序中，即由宿主程序去调用插件程序。&lt;/p&gt;
 &lt;p&gt;2、插件一般可以独安装。&lt;/p&gt;
 &lt;p&gt;3、宿主程序中可以管理不同的插件，包括查看插件的数目，禁用或者使用某个插件。&lt;/p&gt;
 &lt;p&gt;4、宿主程序应该保证插件的向下兼容性，即新版本的宿主程序可以运行较老版本的插件。&lt;/p&gt;
 &lt;p&gt;下面详细看一下这种架构：&lt;/p&gt;
 &lt;p&gt;1、宿主程序：&lt;/p&gt;
 &lt;p&gt;新建Android项目PluginDevAndroid&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://img.blog.csdn.net/20140310212024718?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHJveW9u/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center"&gt;&lt;/img&gt;  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;2、插件项目1：Plugin1&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://img.blog.csdn.net/20140310212032625?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHJveW9u/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center"&gt;&lt;/img&gt;  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;3、插件项目2：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://img.blog.csdn.net/20140310212037968?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHJveW9u/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center"&gt;&lt;/img&gt;  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;4、综述：&lt;/p&gt;
 &lt;p&gt;4.1、接口类一般定义在宿主项目中，比如本例中的IPluginDev.java&lt;/p&gt;
 &lt;p&gt;4.2、插件项目需要应用IPluginDev时，则必须通过一个外部的jar包，并且该jar包是以Library的形式被添加到Plugin项目的build Path，而不是以“外部的”jar方式添加。&lt;/p&gt;
 &lt;p&gt;如图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://img.blog.csdn.net/20140310213300625?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHJveW9u/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center"&gt;&lt;/img&gt;  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;4.3、宿主程序想要知道系统中有哪些插件，可以定义一个Action，本例中使用的是如下action。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://img.blog.csdn.net/20140310213611656?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHJveW9u/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center"&gt;&lt;/img&gt;  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;这样的话，宿主程序就可以通过PackageManager类的queryIntentActivities函数查询相关的插件的列表了。&lt;/p&gt;
 &lt;p&gt;得到了  &lt;strong&gt;插件&lt;/strong&gt;的PackageName，就可以访问插件的资源内容。例如：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;				Resources res = pm.getResourcesForApplication(packageName);
				int id = 0;
				id = res.getIdentifier(&amp;quot;version&amp;quot;, &amp;quot;string&amp;quot;, packageName);
				String version = res.getString(id);
				Log.d(&amp;quot;hlwang&amp;quot;, &amp;quot;MainActivity test version is:&amp;quot;+version);&lt;/pre&gt;这段代码中，首先获取插件的Resource对象，接着得到名称为version字段的字符串id值，然后再调用getString获得该变量的值，于是宿主程序就知道插件程序中的资源内容了。
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;关键代码：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;private void test(){
		Intent intent = new Intent(&amp;quot;com.example.plugindevandroid.plugin&amp;quot;,null);
		final PackageManager pm = getPackageManager();
		final List&amp;lt;ResolveInfo&amp;gt; plugins = pm.queryIntentActivities(intent, 0);
		
		for(ResolveInfo r:plugins){
			ActivityInfo activityInfo = r.activityInfo;
			
			String div = System.getProperty(&amp;quot;path.separator&amp;quot;);
			String packageName = activityInfo.packageName;
			String packageName0 = getPackageName();
			String dexPath = activityInfo.applicationInfo.sourceDir;
			String dexOutputDir1 = activityInfo.applicationInfo.dataDir;
			String dexOutputDir2 = getApplicationInfo().dataDir;
			String libPath = activityInfo.applicationInfo.nativeLibraryDir;
			
			Log.d(&amp;quot;hlwang&amp;quot;, &amp;quot;MainActivity test div is:&amp;quot;+div
					+&amp;quot;,packageName is:&amp;quot;+packageName
					+&amp;quot;,packageName0 is:&amp;quot;+packageName0
					+&amp;quot;,dexPath is:&amp;quot;+dexPath
					+&amp;quot;,dexOutputDir1 is:&amp;quot;+dexOutputDir1
					+&amp;quot;,dexOutputDir2 is:&amp;quot;+dexOutputDir2
					+&amp;quot;,libPath is:&amp;quot;+libPath);
			
			DexClassLoader dexCl = new DexClassLoader(dexPath, dexOutputDir2, libPath, getClassLoader());
			Log.d(&amp;quot;hlwang&amp;quot;, &amp;quot;MainActivity test clazzName is:&amp;quot;+packageName+&amp;quot;.PluginVersion&amp;quot;);
			try{
				Class clazz = dexCl.loadClass(packageName+&amp;quot;.PluginVersion&amp;quot;);
				IPluginDev plugin = (IPluginDev) clazz.newInstance();
				String name = plugin.getName();
				Log.d(&amp;quot;hlwang&amp;quot;, &amp;quot;MainActivity test name is:&amp;quot;+name);
				
				Resources res = pm.getResourcesForApplication(packageName);
				int id = 0;
				id = res.getIdentifier(&amp;quot;version&amp;quot;, &amp;quot;string&amp;quot;, packageName);
				String version = res.getString(id);
				Log.d(&amp;quot;hlwang&amp;quot;, &amp;quot;MainActivity test version is:&amp;quot;+version);
				
				
				PluginObject p = new PluginObject();
				p.name = name;
				p.version = version;
				Log.d(&amp;quot;hlwang&amp;quot;, &amp;quot;MainActivity test p is:&amp;quot;+p);
				mList.add(p);
			}catch(Exception e){
				Log.d(&amp;quot;hlwang&amp;quot;, &amp;quot;MainActivity exception eeeeeeeeeeeee&amp;quot;);
				e.printStackTrace();
			}
			
		}
		Log.d(&amp;quot;hlwang&amp;quot;, &amp;quot;MainActivity test list size is:&amp;quot;+mList.size());
		setListAdapter(new ArrayAdapter&amp;lt;PluginObject&amp;gt;(this,
                android.R.layout.simple_list_item_1, mList));
	}&lt;/pre&gt;这段代码中，首先得到插件的List&amp;lt;ResolverInfo&amp;gt;列表。
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;然后得到插件的packageName，以及插件的dexPath目录。&lt;/p&gt;
 &lt;p&gt;再次，得到dexOutputDir目录。&lt;/p&gt;
 &lt;p&gt;libPath一般只c/c++使用的库文件。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;DexClassLoader的参数意义：&lt;/p&gt;
 &lt;p&gt;dexPath：插件apk或者jar包文件的路径&lt;/p&gt;
 &lt;p&gt;dexOutputDir：将目标apk或者jar包解压的文件的存放目录。因为宿主程序只对本应用程序所在的目录由存取权限。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
运行截图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://img.blog.csdn.net/20140310214713078?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHJveW9u/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center"&gt;&lt;/img&gt;  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
&lt;/p&gt;

 &lt;div&gt;
    作者：hailushijie 发表于2014-3-10 21:47:30   &lt;a href="http://blog.csdn.net/droyon/article/details/20951797"&gt;原文链接&lt;/a&gt;
&lt;/div&gt;
 &lt;div&gt;
    阅读：30 评论：0   &lt;a href="http://blog.csdn.net/droyon/article/details/20951797#comments" target="_blank"&gt;查看评论&lt;/a&gt;
&lt;/div&gt;

&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/53909-%E6%8F%92%E4%BB%B6-%E5%BC%80%E5%8F%91-android</guid>
      <pubDate>Tue, 11 Mar 2014 05:47:30 CST</pubDate>
    </item>
    <item>
      <title>Android 使用动态加载框架DL进行插件化开发</title>
      <link>https://itindex.net/detail/53908-android-%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD-%E6%A1%86%E6%9E%B6</link>
      <description>&lt;p&gt;如有转载，请声明出处: 时之沙:   &lt;a href="http://blog.csdn.net/t12x3456" target="_blank"&gt;http://blog.csdn.net/t12x3456&lt;/a&gt;    （来自时之沙的csdn博客）  &lt;br /&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;概述:   &lt;br /&gt;&lt;/p&gt; &lt;p&gt;        随着应用的不断迭代，应用的体积不断增大,项目越来越臃肿,冗余增加.项目新功能的添加，无法确定与用户匹配性，发生严重异常往往牵一发而动全身,只能紧急发布补丁版本，强制用户进行更新.结果频繁的更新，反而容易降低用户使用黏性.或者是公司业务的不断发展,同系的应用越来越多,传统方式需要通过用户量最大的主项目进行引导下载并安装.&lt;/p&gt; &lt;p&gt;       怎么办?参考浏览器-插件开发模式:  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;         一. 来可以将自己的应用分拆，某些功能可以在插件中实现，用到时再进行下载，而且不用安装.  如果有新功能的添加，不需要更新应用，只要预留插件管理，我们就可以通过添加插件的方式，动态更新自己的应用,该功能需要改进或扩展，更新插件即可，无需频繁安装或卸载(容易造成用户反感).   &lt;br /&gt;&lt;/p&gt; &lt;p&gt;        二. 对应同系应用，正常的引流方式只能引导用户进行新应用的下载和安装,如果使用插件化开发，则无需安装应用，关闭插件功能也十分方便，省去应用安装和卸载的过程,可以实现无缝引流.  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;    这里要向大家推荐一个开源的动态加载框架  &lt;a href="https://github.com/singwhatiwanna/dynamic-load-apk" target="_blank"&gt;DL&lt;/a&gt;, 该项目由  &lt;a href="https://github.com/singwhatiwanna/" target="_blank"&gt;singwhatiwanna&lt;/a&gt;发起,目前一共有三个人开发，我有幸成为了其中的contributor.  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;     如果你对DL动态加载框架还不熟悉，建议先看一下这篇文章:&lt;/p&gt; &lt;p&gt;    APK动态加载框架DL解析  &lt;a href="http://blog.csdn.net/singwhatiwanna/article/details/39937639" target="_blank"&gt; http://blog.csdn.net/singwhatiwanna/article/details/39937639&lt;/a&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;如果你看过之后还会不太清晰，请看下DL插件化框架的全景图,如下&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://img.blog.csdn.net/20141018095209656?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdDEyeDM0NTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;这里我主要向大家介绍一下利用DL框架进行开发的具体步骤:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;1. 首先我们需要从github上获取项目代码:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;     &lt;a href="https://github.com/singwhatiwanna/dynamic-load-apk" target="_blank"&gt;https://github.com/singwhatiwanna/dynamic-load-apk&lt;/a&gt;&lt;/p&gt; &lt;p&gt;   这里我们可以看到下载后的目录如下&lt;/p&gt; &lt;p&gt;    &lt;img alt="" src="http://img.blog.csdn.net/20141016231347646?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdDEyeDM0NTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;   &lt;br /&gt;&lt;/p&gt; &lt;p&gt;        lib目录就是我们的公共插件库  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;        sample目录是对应的demo,  具体的工程可以参照上面的DL全景图中的三种模式:&lt;/p&gt;        由于一般项目中，代码管理和开发团队相对独立，  一般插件工程的团队是很难接触主项目团队的代码.因此，这里主要以第一种，也是我们最为推荐的方式进行， 采用插件不依赖宿主的方式进行开发. 不需要两个团队过多的交互，开发效率相对较高. &lt;br /&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;2 导入lib工程,如下所示:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;    &lt;img alt="" src="http://img.blog.csdn.net/20141016234432286?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdDEyeDM0NTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;可以看到bin目录下的dl-lib.jar, 如果我们需要对lib工程进行修改，重新build获取对应的dl-lib.jar即可&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3. 插件工程的开发, 导入demo中的main-plugin工程&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;     &lt;img alt="" src="http://img.blog.csdn.net/20141017001500703?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdDEyeDM0NTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;    首先还是要强调插件开发的注意事项，以免出现不必要的错误  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;    插件也需要引用DL的jar包，但是不能放入到插件工程的libs目录下面，换句话说，就是插件编译的时候依赖jar包但是打包成apk的时候不要把jar包打进去，这是因为，dl-lib.jar已经在宿主工程中存在了，如果插件中也有这个jar包，就会发生类链接错误，原因很简单，内存中有两份一样的类，重复了。至于support-v4也是同样的道理。对于eclipse很简单，只需要在插件工程中创建一个目录，比如external-jars，然后把dl-lib.jar和support-v4.jar放进去，同时在.classpath中追加如下两句即可： &lt;/p&gt; &lt;pre&gt;&amp;lt;classpathentry kind=&amp;quot;lib&amp;quot; path=&amp;quot;external-jars/dl-lib.jar&amp;quot;/&amp;gt;
&amp;lt;classpathentry kind=&amp;quot;lib&amp;quot; path=&amp;quot;external-jars/android-support-v4.jar&amp;quot;/&amp;gt;&lt;/pre&gt;   然后是插件开发中的具体步骤 &lt;p&gt;  &lt;strong&gt; (1) 如果原有的为Activity,这里需要改为继承DLBasePluginActivity,如果原来为FragmentActivity,那么需要继承DLBasePluginFragmentActivity, for example:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;   &lt;/p&gt; &lt;pre&gt;public class MainActivity extends DLBasePluginActivity &lt;/pre&gt; &lt;pre&gt;TestFragmentActivity extends DLBasePluginFragmentActivity&lt;/pre&gt; &lt;br /&gt; &lt;strong&gt; (2) 如果需要插件独立安装运行, 只要将jar放到libs下面即可,若支持动态加载，仍需按上述注意事项加入 exteral-jars中  &lt;br /&gt;&lt;/strong&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt; (3)插件所需要权限需要在宿主工程中声明&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;       如果是实际开发，一般为从服务器获取插件，这里我们方便自己调试演示,将运行生成对应的插件apk,放入sd卡上的DynamicLoadHost目录中  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;4. 宿主工程中将上述生成的dl-lib.jar加入libs即可,如下所示为demo中提供的宿主工程&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;     &lt;img alt="" src="http://img.blog.csdn.net/20141016234720703?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdDEyeDM0NTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;     &lt;br /&gt;&lt;/p&gt; &lt;p&gt;    在宿主工程中，首先我们需要获取要调用的插件apk对应的MainActivity,DL的demo中插件路径为 sd卡上的DynamicLoadHost目录,没有的话需要创建，或者根据自己需求进行修改.  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;pre&gt;        String pluginFolder = Environment.getExternalStorageDirectory() + &amp;quot;/DynamicLoadHost&amp;quot;;
        File file = new File(pluginFolder);
        File[] plugins = file.listFiles();
        if (plugins == null || plugins.length == 0) {
            mNoPluginTextView.setVisibility(View.VISIBLE);
            return;
        }

        for (File plugin : plugins) {
            PluginItem item = new PluginItem();
            item.pluginPath = plugin.getAbsolutePath();
            item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
            if (item.packageInfo.activities != null &amp;amp;&amp;amp; item.packageInfo.activities.length &amp;gt; 0) {
                item.launcherActivityName = item.packageInfo.activities[0].name;
            }
            mPluginItems.add(item);
        }&lt;/pre&gt; &lt;br /&gt;接着是调起响应的apk,这时需要使用dl-lib.jar: &lt;p&gt;&lt;/p&gt; &lt;p&gt;   &lt;strong&gt; (1)通过Class.forName的方式获取我们需要调用的插件apk中MainActivity的class对象&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;  (2) 就上面提到的，我们需要判断该对象继承自DLProxActivity还是DLProxFragmentActivity,得到对应的代理class对象&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;  (3)使用对应的代理class对象调起插件apk&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;pre&gt; PluginItem item = mPluginItems.get(position);
        Class&amp;lt;?&amp;gt; proxyCls = null;

        try {
            Class&amp;lt;?&amp;gt; cls = Class.forName(item.launcherActivityName, false,
                    DLClassLoader.getClassLoader(item.pluginPath, getApplicationContext(), getClassLoader()));
            if (cls.asSubclass(DLBasePluginActivity.class) != null) {
                proxyCls = DLProxyActivity.class;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(this,
                    &amp;quot;load plugin apk failed, load class &amp;quot; + item.launcherActivityName + &amp;quot; failed.&amp;quot;,
                    Toast.LENGTH_SHORT).show();
        } catch (ClassCastException e) {
            // ignored
        } finally {
            if (proxyCls == null) {
                proxyCls = DLProxyFragmentActivity.class;
            }
            Intent intent = new Intent(this, proxyCls);
            intent.putExtra(DLConstants.EXTRA_DEX_PATH,
                    mPluginItems.get(position).pluginPath);
            startActivity(intent);
        }&lt;/pre&gt; &lt;br /&gt; &lt;p&gt;       最后运行宿主工程  &lt;strong&gt;main-host&lt;/strong&gt;,就可以看到最终的效果:&lt;/p&gt; &lt;p&gt;       &lt;img alt="" src="http://img.my.csdn.net/uploads/201410/18/1413600994_4880.gif"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;       相信大家经过如上步骤，可以对DL动态加载框架进行开发有了一定了解.目前DL框架仍在不断扩展中，欢迎对我们的项目进行star,fork，或者提出宝贵的建议.，如有问题请及时反馈,我们会在后续版本中进行修复或者改进.  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;
    作者：t12x3456 发表于2014-10-20 0:06:28   &lt;a href="http://blog.csdn.net/t12x3456/article/details/39958755"&gt;原文链接&lt;/a&gt;
&lt;/div&gt;
 &lt;div&gt;
    阅读：125 评论：1   &lt;a href="http://blog.csdn.net/t12x3456/article/details/39958755#comments" target="_blank"&gt;查看评论&lt;/a&gt;
&lt;/div&gt;

&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/53908-android-%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD-%E6%A1%86%E6%9E%B6</guid>
      <pubDate>Mon, 20 Oct 2014 08:06:28 CST</pubDate>
    </item>
    <item>
      <title>流行WordPress SEO插件曝高危SQL注入漏洞</title>
      <link>https://itindex.net/detail/52926-%E6%B5%81%E8%A1%8C-wordpress-seo</link>
      <description>&lt;p&gt;  &lt;img src="http://image.3001.net/images/20150312/14261481458643.png!small" title="6.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;最新消息，全球最流行的CMS应用WordPress插件WordPress SEO by Yoast曝高危SQL注入漏洞，该插件使用频率相当高，用户高达可达千万。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;漏洞简述&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;WordPress SEO by Yoast插件是WordPress平台下非常流行的SEO插件，看其在Yoast网站上高达1400万次的下载量就知道了。&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;该漏洞是WordPress漏洞扫描器“WPScan”开发者Ryan Dewhurst发现——1.7.3.3之前版本的WordPress SEO by Yoast都会受到SQL盲注web应用程序漏洞的影响。SQL注入漏洞之所以被标记为高危漏洞，是因为它可能会导致大量数据和敏感信息泄露。通常，在SQL注入攻击中，攻击者会通过客户端在应用程序中输入一个畸形的SQL请求。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;‍&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;攻击详情&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;该漏洞仅影响WordPress内部用户，因为该漏洞存在于admin/class-bulk-editor-list-table.php文件中，而此文件只有WordPress管理员、编辑和特权作者才能访问。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;为了成功利用这一漏洞，攻击者需要从授权用户（管理员、编辑、作者）处利用该漏洞。当然授权用户是不会乖乖帮你攻击东家的，这就需要社会工程学的帮助了，攻击者可以欺骗用户进入一个精心编写的URL中，如果授权用户成为了此次攻击的受害者，那么攻击者就可利用此漏洞在受害者的WordPress网站上执行任意SQL请求。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;漏洞利用POC&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Ryan提供了一个SQL盲注漏洞的poc：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;http://victim-wordpress-website.com/wp-admin/admin.php?page=wpseo_bulk-editor&amp;amp;type=title&amp;amp;orderby=post_date%2c(select%20*%20from%20(select(sleep(10)))a)&amp;amp;order=asc&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;修复补丁&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;1.7.4版本的WordPress SEO by Yoast（最新版）已经修复了该漏洞，顺便还修复了其中的CSRF漏洞。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;最新版本的WordPress已经废除了自动更新插件的功能，建议用户尽快手动更新WordPress SEO by Yoast。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;[参考来源&lt;/strong&gt;  &lt;a href="http://thehackernews.com/2015/03/wordpress-seo-by-yoast-plugin.html" target="_blank" title=""&gt;   &lt;strong&gt;thehackernews&lt;/strong&gt;&lt;/a&gt;  &lt;strong&gt;，转载请注明来自FreeBuf黑客与极客（FreeBuf.COM）]&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>漏洞 资讯 WordPress SEO 高危漏洞</category>
      <guid isPermaLink="true">https://itindex.net/detail/52926-%E6%B5%81%E8%A1%8C-wordpress-seo</guid>
      <pubDate>Thu, 12 Mar 2015 17:16:54 CST</pubDate>
    </item>
    <item>
      <title>Activiti的Eclipse插件安装指南</title>
      <link>https://itindex.net/detail/53041-activiti-eclipse-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;div&gt;
  &lt;p&gt;     群里有一哥们儿安装Activiti的Eclipse插件安装了2天了还没装好，我看了都快急死了，好人做到底，特地为他写了这篇博客。&lt;/p&gt;
  &lt;p&gt;    我个人不喜欢在线安装的方式，因为速度太慢，而且过于傻瓜式，我对于傻瓜式的东西不感兴趣，这也是为什么我喜欢Eclispe而不喜欢用MyEclipse的原因，当然MyEclipse更卡也有一部分原因，额，扯远了哈，回归正题，离线方式安装Activiti的Eclipse插件的话，首先你需要去下载Activiti-designer压缩包即Activiti的eclipse插件安装包，下载地址如下：&lt;/p&gt;
  &lt;p&gt;       &lt;strong&gt;http://www.activiti.org/designer/archived/&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;如图下载：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9540/c2b2ab71-9310-3ae1-b698-0b63894da93a.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; 下载完成后，你将得到一个zip压缩包如图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9542/54469d10-c896-3dc4-8d02-ccdf94fccbd3.jpg"&gt;&lt;/img&gt;   &lt;br /&gt;    &lt;br /&gt; 然后在任意盘符新建一个文件夹用来存放解压后的文件，这里我是在E盘新建一个activiti-designer-0.6.0文件夹，如图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9546/12379e22-ad3b-381c-8af1-27780f8df93f.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; 然后选择下载得到的zip压缩包，鼠标右键解压文件，如图操作：   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9548/946ef5e4-7722-3318-8a3a-f9c196f1c187.jpg"&gt;&lt;/img&gt;   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9550/d9a295ee-b2c7-3dac-8c12-78c9c85daff9.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt; 解压后如图：   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9552/70ce1892-4775-355f-b1db-5eb52ec88c88.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; 删除artifacts.jar、content.jar、site.xml这3个文件，删除后如图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9554/089e556b-bd37-391b-affd-5aee5c5b15bb.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt; 接着在E:\activiti-designer-0.6.0下新建一个eclipse文件夹，如图：   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9556/a81449d0-ac0e-342b-b97a-03db438b0487.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt; 接着把E:\activiti-designer-0.6.0文件夹下的文件以及目录(除了eclipse之外)全部剪切到eclipse文件夹下，如图操作：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9558/fe5f28f3-e226-35b1-bdc0-56525c988c4b.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; 点击进入eclipse文件夹，然后ctrl + v粘帖进去，操作完后效果如图：   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9560/a98658f2-ef46-32f5-8452-aa12fd9080e7.jpg"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;
  &lt;p&gt; 然后切到你的eclipse安装根目录(   &lt;strong&gt;我不用MyEclipse,别问我为什么不以MyEclipse为例来做教程&lt;/strong&gt;)，如图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9473/22c55e06-ab65-35aa-957c-ceae4d913644.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; 我的Eclipse安装在E盘根目录下，看到dropins文件夹了吗？我们的插件link文件就全部定义在这里，点击进入dropins文件夹，在里面新建一个activiti.link文件，如图操作：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9475/46c75f1a-6320-39e8-b35d-40ae816a85ea.jpg"&gt;&lt;/img&gt;   &lt;br /&gt;    &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9477/4b5d5add-0cd1-3cdb-8e8a-2721dbc47499.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; 然后使用任何一款文本编辑器打开activiti.link文件，比如记事本，NotePad++,EditPlus等都行，这里我使用的是Sublime text2,如图：   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9562/625419a0-02cc-3c2b-8ae6-671939db3165.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; &lt;/p&gt;
  &lt;p&gt; 然后重启你的Eclipse，哦，忘了说了，我用的Eclipse版本是3.7.0，如图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9564/e3153657-629b-3e34-b184-24ffd5492d29.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; 建议你Eclipse版本至少得是3.6.0版本及以上。&lt;/p&gt;
  &lt;p&gt;为了避免Eclipse缓存原因，在重启Eclipse之前，记得先clean下Eclipse的缓存，如图操作：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9570/a214e62f-9140-306e-ae23-17ec4c0ce027.jpg"&gt;&lt;/img&gt;   &lt;br /&gt;    &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9574/f08f4e19-a2c0-31f2-976d-f92a7c253edd.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; 设置好后，双击打开你的Eclipse,然后检查是否安装成功，具体请如图操作，看图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9576/0915044f-22ae-3046-91e5-1da6536b366d.jpg"&gt;&lt;/img&gt;   &lt;br /&gt;   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/9578/2db7515e-8187-3d7d-8961-c8e4e35688fd.jpg"&gt;&lt;/img&gt;   &lt;br /&gt; 照着我的教程一步一步做，100%是可以安装成功的，不要再来问我为什么安装不成功，那问题肯定是你没有看仔细，没有按教程里的截图照葫芦画瓢。而且，   &lt;strong&gt;你学会了安装Activiti的Eclipse插件，其实以后安装其他的Eclipse插件步骤也是差不多的，大家要学会举一反三&lt;/strong&gt;，这也是为什么我一向很反对在线方式安装，不仅仅是安装过程很漫长，更重要的是，采用links离线安装，更能锻炼你的动手实践能力，让你更了解Eclipse插件是如何安装的。就说这么多了，本来是要昨晚就要写完这篇的，结果昨晚写到12点的时候实在太困了，扛不住，写了一半就睡了，证明我老咯，连12点都熬不住了，2年前每天奋战到1-2点都跟打了鸡血似得，废话不多说了，Thanks all !!!&lt;/p&gt;
  &lt;p&gt; &lt;/p&gt;
  &lt;p&gt;如果你还有什么问题请加我Ｑ-Q：7-3-6-0-3-1-3-0-5，&lt;/p&gt;
  &lt;p&gt;或者加裙   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0106/8557/86838862-91a6-3a21-b7ff-5467512e3210.jpg"&gt;&lt;/img&gt;一起交流学习！&lt;/p&gt;
&lt;/div&gt;
          
           &lt;br /&gt; &lt;br /&gt;
          
             &lt;a href="http://iamyida.iteye.com/blog/2195309#comments"&gt;已有   &lt;strong&gt;0&lt;/strong&gt; 人发表留言，猛击-&amp;gt;&amp;gt;  &lt;strong&gt;这里&lt;/strong&gt;&amp;lt;&amp;lt;-参与讨论&lt;/a&gt;
          
           &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
ITeye推荐
 &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;a href="http://www.iteye.com/clicks/433" target="_blank"&gt;—软件人才免语言低担保 赴美带薪读研！— &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
          
        &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/53041-activiti-eclipse-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Wed, 25 Mar 2015 10:08:47 CST</pubDate>
    </item>
    <item>
      <title>jQuery.bsgrid 1.30 发布，简易 jQuery Grid 插件</title>
      <link>https://itindex.net/detail/52312-jquery-bsgrid-%E7%AE%80%E6%98%93</link>
      <description>&lt;h1&gt;jQuery.bsgrid 简单易用的jQuery Grid插件&lt;/h1&gt; &lt;p&gt;jquery bsgrid，A simple jQuery Grid plugin with pagation, export and easy to expand. 一个简单易用的jQuery Grid插件，内置提供多套皮肤且非常容易扩展，支持分页或不分页，支持json、xml数据格式，对导出友好，扩展性友好。&lt;/p&gt; &lt;p&gt;本插件的目标是提供简洁实用、代码轻便、扩展性强的Grid功能。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://static.oschina.net/uploads/space/2014/1226/161430_p99J_260040.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;本插件的早期及当前版本已在多个项目中使用。&lt;/p&gt; &lt;p&gt;开源协议：Apache Licence 2   &lt;br /&gt;插件版本：1.30  &lt;br /&gt;在线演示：  &lt;a href="http://bsgrid.oschina.mopaas.com/" target="_blank"&gt;http://bsgrid.oschina.mopaas.com/&lt;/a&gt;   &lt;br /&gt;本开源插件已提供交流群，详见演示页面或源码主页，欢迎交流提意见或对插件进行完善优化。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;本次重大更新：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;1，提供grid.extend扩展，推荐grid扩展接口使用方式，更方便扩展；  &lt;br /&gt;     扩展示例1：  &lt;a href="http://bsgrid.oschina.mopaas.com/examples/grid/extend.html" target="_blank"&gt;http://bsgrid.oschina.mopaas.com/examples/grid/extend.html&lt;/a&gt;  &lt;br /&gt;     效果图示如下，此示例中详细演示说明了grid.extend如何自定义扩展使用  &lt;br /&gt;  &lt;img alt="" src="http://static.oschina.net/uploads/space/2014/1226/155730_Auty_260040.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;     扩展接口说明（使用参考上面的示例或grid.extend.js）：  &lt;br /&gt;       extend config methos, use write see grid.extend.js(Note that it&amp;apos;s advanced use with little difficult):  &lt;br /&gt;       extend.initGridMethods: extend init grid methods  &lt;br /&gt;       extend.beforeRenderGridMethods: extend before render grid methods  &lt;br /&gt;       extend.renderPerRowMethods: extend render per row methods, no matter blank row or not blank row, before render per column methods  &lt;br /&gt;       extend.renderPerColumnMethods: extend render per column methods, no matter blank column or not blank column  &lt;br /&gt;       extend.afterRenderGridMethods: extend after render grid methods&lt;/p&gt; &lt;p&gt;2，grid表头可拖动列宽  &lt;br /&gt;      示例：  &lt;a href="http://bsgrid.oschina.mopaas.com/examples/grid/move-column-extend.html" target="_blank"&gt;http://bsgrid.oschina.mopaas.com/examples/grid/move-column-extend.html&lt;/a&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;3，grid完整方法及属性演示说明，使用此grid强烈推荐熟悉此页面及代码、说明  &lt;br /&gt;       &lt;a href="http://bsgrid.oschina.mopaas.com/examples/grid/standard.html" target="_blank"&gt;http://bsgrid.oschina.mopaas.com/examples/grid/standard.html&lt;/a&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;4，版本API定型，对实现进行了一点优化；  &lt;br /&gt;5，较大的优化了示例展示&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>软件更新新闻</category>
      <guid isPermaLink="true">https://itindex.net/detail/52312-jquery-bsgrid-%E7%AE%80%E6%98%93</guid>
      <pubDate>Fri, 26 Dec 2014 16:19:20 CST</pubDate>
    </item>
    <item>
      <title>利用firebreath开发跨浏览器插件</title>
      <link>https://itindex.net/detail/51062-%E5%88%A9%E7%94%A8-firebreath-%E5%BC%80%E5%8F%91</link>
      <description>&lt;div&gt;
  &lt;p&gt;在研发ZCMS系统过程中，我们发现很多用户在上传截图时，需要先另存为图片文件然后再选择上传，过程操作复杂。于是我们想通过用户直接粘贴图片到编辑器并上传。要实现这一功能必须实现获取用户剪切板中的图片数据并保存到本地。浏览器本身没有相应的功能，因此我们考虑到用浏览器插件的方式实现。为了兼容各个浏览器，我们选择了frirebreath，实现插件在不同浏览器的通用。&lt;/p&gt;
  &lt;p&gt; &lt;/p&gt;
  &lt;p&gt;首先介绍一下FireBreath。FireBreath 旨在提供一个跨平台支持的浏览器插件体系架构，面向：&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;NPAPI 浏览器（windows, mac, and linux）:
    &lt;ul&gt;
     &lt;li&gt;Gecko/Firefox&lt;/li&gt;
     &lt;li&gt;Google Chrome&lt;/li&gt;
     &lt;li&gt;Apple Safari&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
   &lt;li&gt;ActiveX 架构:&lt;/li&gt;
   &lt;ul&gt;
    &lt;li&gt;Microsoft Internet Explorer 6, 7, and 8,9,10,11&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
  &lt;p&gt;下面将分步介绍开发过程。   &lt;br /&gt;1.下载最新的FireBreath代码    &lt;a href="http://www.firebreath.org/display/documentation/Download"&gt;http://www.firebreath.org/display/documentation/Download&lt;/a&gt;   &lt;br /&gt;2.下载安装python 2.7    &lt;a href="https://www.python.org/downloads/release/python-278/"&gt;https://www.python.org/downloads/release/python-278/&lt;/a&gt;&lt;/p&gt;
  &lt;div&gt;3.解压FireBreath,并进入目录运行 python fbgen.py&lt;/div&gt;
  &lt;div&gt; 运行后目录下将生成project目录，并生成相应的文件夹生成后，运行相应的prep*.cmd,比如prep2008.cmd，将生成适应vs2008版本的工程文件&lt;/div&gt;
  &lt;div&gt;
   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0101/1325/50728681-c4c2-3fd9-815d-7940f1585338.png"&gt;&lt;/img&gt;   &lt;br /&gt;    &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0101/1327/ed0df158-ab05-3db3-bc72-721857652da6.png"&gt;&lt;/img&gt;   &lt;br /&gt; &lt;/div&gt;
  &lt;div&gt;   &lt;img alt=""&gt;&lt;/img&gt;&lt;/div&gt;
  &lt;div&gt;4.打开sln文件&lt;/div&gt;
  &lt;div&gt;
   &lt;br /&gt;   &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0101/1329/5733f1b8-bdef-3081-abfc-1009d880827d.png"&gt;&lt;/img&gt;   &lt;br /&gt; &lt;/div&gt;
  &lt;div&gt;
   &lt;div&gt;工程中生成了相应的npapi和activex的代码，主要修改对应项目名称目录下的文件即可。&lt;/div&gt;
   &lt;div&gt;其中***API.cpp和***API.h中定义了相关的示例方法，比如echo方法，我们可以直接编译。对应目录下会生成对应的np***.dll文件。我们可以打开FBControl.htm进行测试。在测试之前，需要注册到浏览器中，firefox和chrome需要通过注册表注册找到HKEY_LOCAL_MACHINE\Software\MozillaPlugins\ ，添加@zvingsoft/ZCMSPastePlugin，并设置path description，以及MimeTypes&lt;/div&gt;
   &lt;div&gt;    &lt;img alt=""&gt;&lt;/img&gt;&lt;/div&gt;
   &lt;div&gt;
    &lt;div&gt;
     &lt;br /&gt;     &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0101/1331/c780985a-0abc-330b-af22-ce49a1b18f5b.png"&gt;&lt;/img&gt;     &lt;br /&gt; &lt;/div&gt;
    &lt;div&gt;在firefox和chrome地址栏中输入 about:plugins查看对应的插件是否已经成功安装&lt;/div&gt;
    &lt;img alt=""&gt;&lt;/img&gt;
&lt;/div&gt;
   &lt;div&gt;
    &lt;br /&gt;    &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0101/1333/8c097980-16f1-3c7f-8162-b2e156f66633.png"&gt;&lt;/img&gt;
&lt;/div&gt;
   &lt;div&gt; &lt;/div&gt;
   &lt;div&gt;可以打开FBControl.htm进行测试    &lt;br /&gt;     &lt;br /&gt;    &lt;img alt="" src="http://dl2.iteye.com/upload/attachment/0101/1335/58b61b0a-755c-3027-9d38-0bd9caed6248.png"&gt;&lt;/img&gt;    &lt;br /&gt; &lt;/div&gt;
   &lt;div&gt; &lt;/div&gt;
   &lt;div&gt;    &lt;img alt=""&gt;&lt;/img&gt;&lt;/div&gt;
   &lt;div&gt; &lt;/div&gt;
   &lt;div&gt;    &lt;img alt=""&gt;&lt;/img&gt;&lt;/div&gt;
   &lt;div&gt;注意：在ie中需要通过regsvr32对dll注册 &lt;/div&gt;
   &lt;div&gt; &lt;/div&gt;
   &lt;div&gt;5.在***API.h和***API.cpp文件中增加图片保存方法&lt;/div&gt;
   &lt;div&gt; &lt;/div&gt;
   &lt;div&gt;***API.h&lt;/div&gt;
   &lt;div&gt;
    &lt;pre&gt;//注册方法暴露到js方法中调用
registerMethod(&amp;quot;saveClipImage&amp;quot;, make_method(this, &amp;amp;ZCMSPastePluginAPI::saveClipImage));

......

FB::variant saveClipImage();

// Event helpers
 FB_JSAPI_EVENT(test, 0
FB_JSAPI_EVENT(saveClipImage, 0, ());&lt;/pre&gt;
 &lt;/div&gt;
   &lt;div&gt; &lt;/div&gt;
   &lt;div&gt;***API.cpp&lt;/div&gt;
   &lt;div&gt;
    &lt;pre&gt;FB::variant ZCMSPastePluginAPI::saveClipImage()
{
    if (!OpenClipboard(NULL))
  return &amp;quot;&amp;quot;;
 
 HBITMAP hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP);
 CloseClipboard();

 //HWND hWnd = GetDesktopWindow();
 if (hBitmap == NULL)
  return &amp;quot;&amp;quot;;
 CImage image;
 image.Attach(hBitmap);
 TCHAR OutPath[MAX_PATH];
    int StrLen = GetTempPath(MAX_PATH, OutPath);
 CString str = CString((LPCTSTR)OutPath) + CString(_T(&amp;quot;zcmspaste.png&amp;quot;));
 image.Save((LPCTSTR)str, Gdiplus::ImageFormatPNG); 
    // return &amp;quot;foobar&amp;quot;;
    return str;
}&lt;/pre&gt;
 &lt;/div&gt;
   &lt;div&gt; &lt;/div&gt;
   &lt;div&gt;通过js调用相关方法&lt;/div&gt;
   &lt;div&gt;     
    &lt;pre&gt;  function saveClipImage()
        {
            if(plugin().valid){
                var path = plugin().saveClipImage();
                if(!path){
                 alert(&amp;quot;剪贴板中没有图片&amp;quot;);
                }else{
                 alert(&amp;quot;图片已保存：&amp;quot;+path);
                 document.getElementById(&amp;quot;test&amp;quot;).src = path;
                }
            } else {
                alert(&amp;quot;Plugin is not working :(&amp;quot;);
            }
        }&lt;/pre&gt;
 &lt;/div&gt;
   &lt;div&gt; &lt;/div&gt;
   &lt;div&gt;6.为了能够让用户直接使用，建议使用innosetup制作安装文件，下面示例中将dll进行注册，并加入到注册表。&lt;/div&gt;
   &lt;div&gt;
    &lt;pre&gt;[Files]
Source: &amp;quot;G:\FireBreath1.7\build\bin\ZCMSPastePlugin\Debug\npZCMSPastePlugin.dll&amp;quot;; DestDir: &amp;quot;{app}&amp;quot;; Flags: regserver restartreplace noregerror replacesameversion uninsnosharedfileprompt sharedfile ignoreversion


[Registry]
Root: HKLM; Subkey: Software\MozillaPlugins\@zvingsoft/ZCMSPastePlugin; ValueType: string; Flags: uninsdeletekey
Root: HKLM; Subkey: Software\MozillaPlugins\@zvingsoft/ZCMSPastePlugin; ValueName: Path; ValueData: {app}\npZCMSPastePlugin.dll; ValueType: string
Root: HKLM; Subkey: Software\MozillaPlugins\@zvingsoft/ZCMSPastePlugin; ValueName: Description; ValueData: ZCMS paste image Plugin; ValueType: string
Root: HKLM; Subkey: Software\MozillaPlugins\@zvingsoft/ZCMSPastePlugin\MimeTypes; ValueType: string; Flags: uninsdeletekey
Root: HKLM; Subkey: Software\MozillaPlugins\@zvingsoft/ZCMSPastePlugin\MimeTypes\application/x-zcms-paste-plugin; ValueType: string; Flags: uninsdeletekey&lt;/pre&gt;
 &lt;/div&gt;
&lt;/div&gt;
  &lt;div&gt; &lt;/div&gt;
&lt;/div&gt;
          
           &lt;br /&gt; &lt;br /&gt;
          
             &lt;a href="http://rainsky.iteye.com/blog/2116370#comments"&gt;已有   &lt;strong&gt;0&lt;/strong&gt; 人发表留言，猛击-&amp;gt;&amp;gt;  &lt;strong&gt;这里&lt;/strong&gt;&amp;lt;&amp;lt;-参与讨论&lt;/a&gt;
          
           &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
ITeye推荐
 &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;a href="http://www.iteye.com/clicks/433" target="_blank"&gt;—软件人才免语言低担保 赴美带薪读研！— &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
          
        &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/51062-%E5%88%A9%E7%94%A8-firebreath-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Sun, 14 Sep 2014 22:33:42 CST</pubDate>
    </item>
    <item>
      <title>40个Bootstrap的扩展和插件集合</title>
      <link>https://itindex.net/detail/51034-bootstrap-%E6%89%A9%E5%B1%95-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions18.jpg"&gt;   &lt;img alt="extensions18" height="248" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions18-495x248.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;br /&gt;
Bootstrap的出现可以说让很多程序员相当的兴奋。在漫长的过去，我们看到过很多呆板的原型网页，简单甚至粗暴，毫无设计感和色彩感可言。那是一个个技术大牛们痛苦的拉出的界面。随着网页设计师的出现，慢慢的网站建设开始从单一的程序员一条龙服务开始转变为网页设计师和程序员来共同完成。但是对于很多喜欢自己捣鼓的程序员来说，如果有一条完整统一的样式可以调用，那是多么美好的一件事情。于是，伟大的Bootstrap终于诞生啦。淡然，Bootstrap不仅仅是程序员的福音。很多前端设计师也开始使用起来。因为其高效的架构，通用的维护，甚至可以设计的相当时尚，还有很多插件和扩展可以直接调用。还有什么不爱的理由呢！今天我们要分享出的就是类似这样的一些优秀的Bootstrap扩展和插件。可以让你的网站架构更加高效。赶快来看看吧！&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/Serhioromano/bootstrap-calendar" target="_blank"&gt;Bootstrap Calendar&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Full view calendar with year, month, week and day views based on templates with Twitter Bootstrap.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions2.jpg"&gt;   &lt;img alt="extensions2" height="276" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions2-495x276.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/noreiller/bootstrap-modal-carousel" target="_blank"&gt;Bootstrap Modal Carousel&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A set of plugins to display a carousel into a fullscreen modal box.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions3.jpg"&gt;   &lt;img alt="extensions3" height="326" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions3-495x326.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/ace-subido/css3-microsoft-metro-buttons" target="_blank"&gt;Css3 Microsoft Metro Buttons&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;My CSS3 library for making Microsoft-metro themed buttons.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions1.jpg"&gt;   &lt;img alt="extensions1" height="325" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions1-495x325.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/sairam/bootstrap-prompts" target="_blank"&gt;Bootstrap Prompts&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;BootStrap Alert with modal instead of Browser alert.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions12.jpg"&gt;   &lt;img alt="extensions12" height="281" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions12-495x281.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="http://arthurgouveia.com/prettyCheckable/" target="_blank"&gt;Pretty Checkable&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;This plugin replaces the default checkboxes and radio inputs for better looking ones.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions4.jpg"&gt;   &lt;img alt="extensions4" height="369" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions4-495x369.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/tkrotoff/jquery-simplecolorpicker" target="_blank"&gt;Jquery Simplecolorpicker&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Yet another jQuery color picker. This plugin is unobtrusive and integrates well with Twitter Bootstrap (it works just fine without).&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions5.jpg"&gt;   &lt;img alt="extensions5" height="343" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions5-495x343.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/danielfarrell/bootstrap-combobox" target="_blank"&gt;Bootstrap Combobox&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A combobox plugin that works with twitter bootstrap.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions6.jpg"&gt;   &lt;img alt="extensions6" height="177" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions6-495x177.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="http://www.eyecon.ro/bootstrap-datepicker/" target="_blank"&gt;Bootstrap Datepicker&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions7.jpg"&gt;   &lt;img alt="extensions7" height="314" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions7-495x314.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="http://www.dangrossman.info/2012/08/20/a-date-range-picker-for-twitter-bootstrap/" target="_blank"&gt;A Date Range Picker for Bootstrap&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions8.jpg"&gt;   &lt;img alt="extensions8" height="221" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions8-495x221.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="http://weareoutman.github.io/clockpicker/" target="_blank"&gt;Clockpicker&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A clock-style timepicker for Bootstrap (or jQuery).&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions9.jpg"&gt;   &lt;img alt="extensions9" height="283" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions9-495x283.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/makeusabrew/bootbox" target="_blank"&gt;Bootbox&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Wrappers for JavaScript alert(), confirm() and other flexible dialogs using Twitter’s bootstrap framework.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions10.jpg"&gt;   &lt;img alt="extensions10" height="209" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions10-495x209.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/gunnar-t/jquery.gridform" target="_blank"&gt;Jquery Gridform&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;jQuery-Plugin for creating complex forms (table-based) with usage of bootstrap and (optional) Font-Awesome.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions11.jpg"&gt;   &lt;img alt="extensions11" height="319" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions11-495x319.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/ReactiveRaven/jqBootstrapValidation" target="_blank"&gt;JqBootstrap Validation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A JQuery validation framework for bootstrap forms.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions13.jpg"&gt;   &lt;img alt="extensions13" height="363" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions13-495x363.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/bwsewell/tablecloth" target="_blank"&gt;Table clothjs&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A CSS and JS bootstrap to style and manipulate data tables.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions14.jpg"&gt;   &lt;img alt="extensions14" height="213" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions14-495x213.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/gr2m/bootstrap-navigable-table" target="_blank"&gt;Bootstrap Navigable Table&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A Bootstrap plugin for elegant navigating along table inputs.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions15.jpg"&gt;   &lt;img alt="extensions15" height="271" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions15-495x271.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/ethaizone/Bootstrap-Confirmation" target="_blank"&gt;Bootstrap Confirmation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Javascript plug-in for Twitter bootstrap. Require JQuery and Bootstrap-Tooptip.js to run. This is in Beta state.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions16.jpg"&gt;   &lt;img alt="extensions16" height="278" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions16-495x278.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/blueimp/jQuery-File-Upload" target="_blank"&gt;jQuery File Upload Demo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;File Upload widget with multiple file selection, drag and drop support, progress bar, validation and preview images, audio and video for jQuery.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions17.jpg"&gt;   &lt;img alt="extensions17" height="254" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions17-495x254.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/blueimp/Bootstrap-Image-Gallery" target="_blank"&gt;Bootstrap Image Gallery&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Bootstrap Image Gallery is an extension to blueimp Gallery, a touch-enabled, responsive and customizable image and video gallery.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions18.jpg"&gt;   &lt;img alt="extensions18" height="248" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions18-495x248.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/vitalets/x-editable" target="_blank"&gt;X-editable&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions19.jpg"&gt;   &lt;img alt="extensions19" height="264" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions19-495x264.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/plozi/jQDrawBootstrapGrid" target="_blank"&gt;jQDraw Bootstrap Grid&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A simple jQuery plugin that draws grid columns to a twitter bootstrap enabled layout.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions20.jpg"&gt;   &lt;img alt="extensions20" height="272" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions20-495x272.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/marcaube/bootstrap-magnify" target="_blank"&gt;Bootstrap Magnify&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Small bootstrap js plugin to enhance porte-folios and image galleries.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions21.jpg"&gt;   &lt;img alt="extensions21" height="345" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions21-495x345.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/toopay/bootstrap-markdown" target="_blank"&gt;Bootstrap Markdown&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Bootstrap plugin for markdown editing.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions22.jpg"&gt;   &lt;img alt="extensions22" height="330" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions22-495x330.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/jschr/bootstrap-modal" target="_blank"&gt;Bootstrap Modal&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Extends the default Bootstrap Modal class. Responsive, stackable, ajax and more.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions23.jpg"&gt;   &lt;img alt="extensions23" height="359" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions23-495x359.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/aroc/Bootstrap-Scroll-Modal" target="_blank"&gt;Bootstrap Scroll Modal&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A modification of the Twitter Bootstrap Modal plugin – allows for any height modal with full page scrolling of said modal.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions24.jpg"&gt;   &lt;img alt="extensions24" height="307" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions24-495x307.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="http://demo.onokumus.com/metisMenu/" target="_blank"&gt;MetisMenu&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Easy menu jQuery plugin for Twitter Bootstrap 3.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions25.jpg"&gt;   &lt;img alt="extensions25" height="223" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions25-495x223.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/sydcanem/bootstrap-contextmenu" target="_blank"&gt;Bootstrap Contextmenu&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Context menu plugin for Twitter’s Bootstrap framework.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions26.jpg"&gt;   &lt;img alt="extensions26" height="264" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions26-495x264.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/botmonster/jquery-bootpag" target="_blank"&gt;Jquery Bootpag&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;This jQuery plugin helps you create dynamic pagination with Bootstrap or in any other html pages.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions27.jpg"&gt;   &lt;img alt="extensions27" height="270" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions27-495x270.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/esimakin/twbs-pagination" target="_blank"&gt;Twbs Pagination&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions28.jpg"&gt;   &lt;img alt="extensions28" height="250" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions28-495x250.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/lipis/bootstrap-social" target="_blank"&gt;Bootstrap Social&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Social Buttons made in pure CSS based on Bootstrap and Font Awesome!&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions29.jpg"&gt;   &lt;img alt="extensions29" height="228" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions29-495x228.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="http://gustavohenke.github.io/bselect/" target="_blank"&gt;Bselect&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;The select decorator component that was missing for Twitter Bootstrap.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions30.jpg"&gt;   &lt;img alt="extensions30" height="200" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions30-495x200.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="http://davidstutz.github.io/bootstrap-multiselect/" target="_blank"&gt;Bootstrap Multiselect&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Bootstrap Multiselect is a JQuery based plugin to provide an intuitive user interface for using select inputs with the multiple attribute present.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions31.jpg"&gt;   &lt;img alt="extensions31" height="277" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions31-495x277.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/rroppolo/bootstrap-multisuggestplugin" target="_blank"&gt;Bootstrap Multisuggest Plugin&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions32.jpg"&gt;   &lt;img alt="extensions32" height="257" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions32-495x257.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/maxwells/bootstrap-tags" target="_blank"&gt;Bootstrap Tags&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Bootstrap Tags is a jQuery plugin meant to extend Twitter Bootstrap to include tagging functionality.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions33.jpg"&gt;   &lt;img alt="extensions33" height="279" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions33-495x279.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/gfranko/jquery.tocify.js" target="_blank"&gt;Tocify&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A jQuery Table of Contents plugin that can be themed with Twitter Bootstrap or jQueryUI.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions34.jpg"&gt;   &lt;img alt="extensions34" height="302" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions34-495x302.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="http://sliptree.github.io/bootstrap-tokenfield/" target="_blank"&gt;Tokenfield&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions35.jpg"&gt;   &lt;img alt="extensions35" height="180" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions35-495x180.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/aexmachina/tabcordion" target="_blank"&gt;Tabcordion&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A jQuery plugin that transforms Bootstrap tabs to an accordion and vice versa. Useful for responsive mobile sites.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions36.jpg"&gt;   &lt;img alt="extensions36" height="166" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions36-495x166.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/okendoken/bootstrap-tabcollapse" target="_blank"&gt;Bootstrap Tabcollapse&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Bootstrap Tab Collapse plugin. Switches bootstrap tabs component to collapse for small screens.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions37.jpg"&gt;   &lt;img alt="extensions37" height="284" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions37-495x284.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/zgs225/easy-tree" target="_blank"&gt;Easy Tree&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;A jquery and bootstrap basic tree view plugin, easily use.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions38.jpg"&gt;   &lt;img alt="extensions38" height="328" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions38-495x328.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/morrissinger/BootstrapTreeNav" target="_blank"&gt;BootstrapTreeNav&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Tree navigation plugin for Twitter Bootstrap 3.0&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions39.jpg"&gt;   &lt;img alt="extensions39" height="282" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions39-495x282.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;a href="https://github.com/sorich87/bootstrap-tour" target="_blank"&gt;Bootstrap Tour&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Quick and easy product tours with Twitter Bootstrap Popovers.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions40.jpg"&gt;   &lt;img alt="extensions40" height="233" src="http://www.jackchen.cn/blog/wp-content/uploads/2014/09/extensions40-495x233.jpg" width="495"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;Read more:   &lt;a href="http://www.smashingapps.com/2014/08/29/40-extensions-and-plugins-for-extending-bootstrap.html#ixzz3Czk4gy7W"&gt;http://www.smashingapps.com/2014/08/29/40-extensions-and-plugins-for-extending-bootstrap.html#ixzz3Czk4gy7W&lt;/a&gt;&lt;/p&gt;
 &lt;table border="0" cellpadding="3" cellspacing="0"&gt;
    
      &lt;tr&gt;
           &lt;td colspan="4"&gt;    &lt;strong&gt;猜您也喜欢：&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    
          &lt;tr&gt;
                   &lt;td valign="top" width="102"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.jackchen.cn%2Fblog%2Farchives%2F12705.html&amp;from=http%3A%2F%2Fwww.jackchen.cn%2Fblog%2Farchives%2F13797.html" target="_blank" title="15&amp;#20010;&amp;#26368;&amp;#22909;&amp;#30340;Bootstrap&amp;#35774;&amp;#35745;&amp;#24037;&amp;#20855;"&gt;
                             &lt;img height="96px" src="http://static.wumii.cn/site_images/ti/11KRUc6iT.jpg?i=qQ5WqL8m" width="96px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        15个最好的Bootstrap设计工具
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="102"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.jackchen.cn%2Fblog%2Farchives%2F9498.html&amp;from=http%3A%2F%2Fwww.jackchen.cn%2Fblog%2Farchives%2F13797.html" target="_blank" title="Twitter Bootstrap&amp;#25152;&amp;#26377;&amp;#26679;&amp;#24335;PSD&amp;#32032;&amp;#26448;"&gt;
                             &lt;img height="96px" src="http://static.wumii.cn/site_images/ti/kC0bL8mm.jpg?i=GRTkLRDy" width="96px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        Twitter Bootstrap所有样式PSD素材
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="102"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.jackchen.cn%2Fblog%2Farchives%2F11940.html&amp;from=http%3A%2F%2Fwww.jackchen.cn%2Fblog%2Farchives%2F13797.html" target="_blank" title="40&amp;#20010;&amp;#26377;&amp;#29992;&amp;#30340;Bootstrap&amp;#24037;&amp;#20855;&amp;#21644;WEB&amp;#24320;&amp;#21457;&amp;#24037;&amp;#20855;"&gt;
                             &lt;img height="96px" src="http://static.wumii.cn/site_images/ti/6VdoNIhd.jpg?i=mEt7HJW3" width="96px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        40个有用的Bootstrap工具和WEB开发工具
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="102"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.jackchen.cn%2Fblog%2Farchives%2F13775.html&amp;from=http%3A%2F%2Fwww.jackchen.cn%2Fblog%2Farchives%2F13797.html" target="_blank" title="iPhone6 &amp; Apple Watch&amp;#21457;&amp;#24067;&amp;#20250;&amp;#24635;&amp;#32467;&amp;#20043;&amp;#21916;&amp;#19982;&amp;#24551;"&gt;
                             &lt;img height="96px" src="http://static.wumii.cn/site_images/ti/vos3iWVL.jpg?i=IdDaHsbG" width="96px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        iPhone6 &amp;amp; Apple Watch发布会总结之喜与忧
                    &lt;/a&gt;
                &lt;/td&gt;
        &lt;/tr&gt;
    
      &lt;tr&gt;
           &lt;td align="right" colspan="4"&gt;
                &lt;a href="http://www.wumii.com/widget/relatedItems" target="_blank" title="&amp;#26080;&amp;#35269;&amp;#20851;&amp;#32852;&amp;#25512;&amp;#33616;"&gt;
                无觅
            &lt;/a&gt;
        &lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/51034-bootstrap-%E6%89%A9%E5%B1%95-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Thu, 11 Sep 2014 09:33:27 CST</pubDate>
    </item>
    <item>
      <title>Eclipse常用的高效插件</title>
      <link>https://itindex.net/detail/51850-eclipse-%E6%8F%92%E4%BB%B6</link>
      <description>&lt;p&gt;今天为使用Eclipse的程序员们，收集一些比较高效的插件，这些插件可以帮助你提高工作效率。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;Properties Editor &lt;/h3&gt;
 &lt;p&gt;Properties Editor 编辑java的属性文件，并可以自动存盘为Unicode格式，在写一下配置文件的时候，不用工具生成。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;官网：  &lt;a href="http://propedit.sourceforge.jp/index_en.html" target="_blank"&gt;http://propedit.sourceforge.jp/index_en.html &lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;Fat Jar&lt;/h3&gt;
 &lt;p&gt;Fat Jar 打包插件，可以方便的完成各种打包任务，可以包含外部的包等。这个插件比自带的好多很多。方便有第三方lib的程序员&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;官网：  &lt;a href="http://fjep.sourceforge.net/" target="_blank"&gt;http://fjep.sourceforge.net/ &lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;Checkclipse&lt;/h3&gt;
 &lt;p&gt;Checkclipse 是一个用来检查代码的风格、写法是否符合规范的Eclipse插件&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;官网：  &lt;a href="http://sourceforge.net/projects/checkclipse/"&gt;http://sourceforge.net/projects/checkclipse/&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;Maven 插件 m2eclipse  &lt;br /&gt;&lt;/h3&gt;
 &lt;p&gt;这是一个在Eclipse环境中集成的Maven插件，不过多解释，安装方法自行google&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;Git插件 EGit&lt;/h3&gt;
 &lt;p&gt;EGit目前还是一个出于孵化期间的项目，因此EGit 0.7.1并没有提供对Git全部功能的支持，同 时也无法保证没有bug(当然EGit 0.7.1是经过严格的测试之后才发布的正式版本)，但对于笔者这样的用户来说能满足基本的功能并且可以方便的享受Eclipse整合git功能即可。同 时作者表示在Eclipse下一个发行版Eclipse Helios到来之际将发布EGit 0.8，届时EGit的功能将更加强大，同时提供更多的新特性支持。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;安装地址：  &lt;a href="http://download.eclipse.org/egit/updates" target="_blank"&gt;http://download.eclipse.org/egit/updates&lt;/a&gt; &lt;/p&gt;
 &lt;h3&gt;Snowberry &lt;/h3&gt;
 &lt;p&gt;Snowberry 是供Java开发者使用的Eclipse插件，Snowberry 能够将您编写的Java代码同步显示为“类图”，并展现类与类之间的关系。查看父类和接口、寻找已知的子类和实现，都以直观的类图操作呈现出来。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;下载地址：  &lt;a href="http://pan.baidu.com/s/1c0eXLz6"&gt;http://pan.baidu.com/s/1c0eXLz6&lt;/a&gt; &lt;/p&gt;
 &lt;h3&gt;Eclipse Color Themes  &lt;br /&gt;&lt;/h3&gt;
 &lt;p&gt;一个很赞的eclipse插件，可以简单方便地实现eclipse下的代码配色。另外插件作者还专门为此插件做了一个eclipse配色网站，配色多达728个，开发者们也可以在此网站分享自己的配色方案。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;官网：  &lt;a href="http://eclipsecolorthemes.org/"&gt;http://eclipsecolorthemes.org/&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;a href="http://www.itjhwd.com/eclipsekjj/" target="_blank" title="&amp;#38142;&amp;#21521; &amp;#26368;&amp;#24120;&amp;#29992;&amp;#30340;Eclipse&amp;#24555;&amp;#25463;&amp;#38190; &amp;#30340;&amp;#22266;&amp;#23450;&amp;#38142;&amp;#25509;"&gt;最常用的Eclipse快捷键&lt;/a&gt;&lt;/h3&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;
  &lt;p&gt;欢迎来到IT江湖,加入我们官方群    &lt;a href="http://jq.qq.com/?_wv=1027&amp;k=Leh9oz" target="_blank"&gt;383126909&lt;/a&gt;,学习更多,共同发展.&lt;/p&gt;
  &lt;p&gt;关注“IT江湖”微信公众号,每日推送优质文章,丰富大家的知识.&lt;/p&gt;
  &lt;p&gt;微信扫一扫或者搜索 “itjh0223”   IT江湖,每一个IT人的江湖!&lt;/p&gt;
  &lt;p&gt;   &lt;a href="http://www.itjhwd.com/wp-content/uploads/2014/10/17341412928018.jpg"&gt;    &lt;img alt="Eclipse&amp;#24120;&amp;#29992;&amp;#30340;&amp;#39640;&amp;#25928;&amp;#25554;&amp;#20214; - &amp;#31532;1&amp;#24352;  | IT&amp;#27743;&amp;#28246;" height="150" src="http://www.itjhwd.com/wp-content/uploads/2014/10/17341412928018.jpg" title="Eclipse&amp;#24120;&amp;#29992;&amp;#30340;&amp;#39640;&amp;#25928;&amp;#25554;&amp;#20214; - &amp;#31532;1&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="715"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/h3&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>编程语言</category>
      <guid isPermaLink="true">https://itindex.net/detail/51850-eclipse-%E6%8F%92%E4%BB%B6</guid>
      <pubDate>Fri, 21 Nov 2014 10:35:50 CST</pubDate>
    </item>
    <item>
      <title>jQuery插件编写之三部曲</title>
      <link>https://itindex.net/detail/50584-jquery-%E6%8F%92%E4%BB%B6-%E4%B8%89%E9%83%A8%E6%9B%B2</link>
      <description>&lt;div&gt;
  &lt;p&gt;1、选择一个jQuery框架，如：&lt;/p&gt;
  &lt;pre&gt;/*!
 * jQuery.myPlugin
 *
 * @version  1.0.0
 * @date     2014/07/16
 * @author   Lime
 * @license  
 */
(function (jQuery) {

	//定义你的属性名myPlugin
    jQuery.fn.myPlugin = function (options) { 
		
		//替换默认参数
        var options = jQuery.extend({}, jQuery.fn.myPlugin.defaults, options);
        
		//使用return this.each运用在多个控件上并实现链式操作 
        return this.each(function () {
              //在这里实现你的方法
        });
    };

    //使用暴露方式设置插件默认参数，这对于让插件的使用者更容易用较少的代码覆盖和修改插件默认设置
    jQuery.fn.myPlugin.defaults = {
    };

})(jQuery);&lt;/pre&gt;
  &lt;p&gt; 2、添加引用编写好的插件&lt;/p&gt;
  &lt;p&gt; 首先给你的插件起个名，推荐命名方法为：jquery.[插件名].js，如上面可以命名为jquery.myPlugin.js&lt;/p&gt;
  &lt;p&gt; 然后在你的项目里添加引用&amp;lt;script src=&amp;quot;jquery.myPlugin.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;，当然在这之前你必须要引用jquery库。&lt;/p&gt;
  &lt;p&gt; &lt;/p&gt;
  &lt;p&gt;3、插件的使用&lt;/p&gt;
  &lt;pre&gt;&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; 
	$(document).ready(function () { 
		$(&amp;quot;#div1&amp;quot;).myPlugin(); 
	}); 
&amp;lt;/script&amp;gt; 
&lt;/pre&gt;
  &lt;p&gt; 有木有发现简直简单到爆啊，是的，就这么简单！&lt;/p&gt;
  &lt;p&gt; &lt;/p&gt;
  &lt;p&gt;附：select下拉绑定jQuery插件&lt;/p&gt;
  &lt;pre&gt;/*!
 * jQuery.selectDataBind
 *
 * @version  1.0.0
 * @date     2014/07/16
 * @author   Lime&amp;lt;zhrjin@163.com&amp;gt;
 * @license  
 */
(function (jQuery) {
 
    jQuery.fn.selectDataBind = function (options) {
 
        var options = jQuery.extend({}, jQuery.fn.selectDataBind.defaults, options);
        return this.each(function () {
            var jOption = options.options;
            var jVaule = options.values;
            var bEmpty = options.empty;
            var j = 0;
            if (bEmpty) {
                this.options[0] = new Option(&amp;quot;&amp;quot;, &amp;quot;&amp;quot;);
                j++;
            }
 
            for (var i = 0; i &amp;lt; jOption.length; i++) {
                var objOption = new Option(jOption[i], jVaule[i]);
                this.options[j++] = objOption;
            }
 
        });
    };
 
    jQuery.fn.selectDataBind.defaults = {
        options: [],
        values: [],
        empty: true
    };
 
})(jQuery);&lt;/pre&gt;
  &lt;p&gt; &lt;/p&gt;
  &lt;p&gt; &lt;/p&gt;
&lt;/div&gt;
          
           &lt;br /&gt; &lt;br /&gt;
          
             &lt;a href="http://zrj-software.iteye.com/blog/2099281#comments"&gt;已有   &lt;strong&gt;0&lt;/strong&gt; 人发表留言，猛击-&amp;gt;&amp;gt;  &lt;strong&gt;这里&lt;/strong&gt;&amp;lt;&amp;lt;-参与讨论&lt;/a&gt;
          
           &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
ITeye推荐
 &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;a href="http://www.iteye.com/clicks/433" target="_blank"&gt;—软件人才免语言低担保 赴美带薪读研！— &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
          
        &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/50584-jquery-%E6%8F%92%E4%BB%B6-%E4%B8%89%E9%83%A8%E6%9B%B2</guid>
      <pubDate>Sat, 02 Aug 2014 09:56:29 CST</pubDate>
    </item>
  </channel>
</rss>

