Android外掛化(三):載入外掛apk中的Resource資源
轉自:http://blog.csdn.net/nupt123456789/article/details/50411581
簡介
如何載入未安裝apk中的資原始檔呢?我們從android.content.res.AssetManager.java的原始碼中發現,它有一個私有方法addAssetPath,只需要將apk的路徑作為引數傳入,我們就可以獲得對應的AssetsManager物件,然後我們就可以使用AssetsManager物件,建立一個Resources物件,然後就可以從Resource物件中訪問apk中的資源了。總結如下:
- 1.新建一個AssetManager物件
- 2.通過反射呼叫addAssetPath方法
- 3.以AssetsManager物件為引數,建立Resources物件即可。
程式碼如下:
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">package</span> net.mobctrl.hostapk; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> java.io.File; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> android.content.Context; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> android.content.res.AssetManager; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> android.content.res.Resources; <span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @Author</span> Zheng Haibo *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @PersonalWebsite</span> http://www.mobctrl.net *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @version</span> $Id: LoaderResManager.java, v 0.1 2015年12月11日 下午7:58:59 mochuan.zhb * Exp $ *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @Description</span> 動態載入資源的管理器 */</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">BundlerResourceLoader</span> {</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> AssetManager <span class="hljs-title" style="box-sizing: border-box;">createAssetManager</span>(String apkPath) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">try</span> { AssetManager assetManager = AssetManager.class.newInstance(); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">try</span> { AssetManager.class.getDeclaredMethod(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"addAssetPath"</span>, String.class).invoke( assetManager, apkPath); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">catch</span> (Throwable th) { System.out.println(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"debug:createAssetManager :"</span>+th.getMessage()); th.printStackTrace(); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> assetManager; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">catch</span> (Throwable th) { System.out.println(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"debug:createAssetManager :"</span>+th.getMessage()); th.printStackTrace(); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>; } <span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** * 獲取Bundle中的資源 *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> context *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> apkPath *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @return</span> */</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> Resources <span class="hljs-title" style="box-sizing: border-box;">getBundleResource</span>(Context context){ AssetsManager.copyAllAssetsApk(context); File dir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE); String apkPath = dir.getAbsolutePath()+<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"/BundleApk.apk"</span>; System.out.println(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"debug:apkPath = "</span>+apkPath+<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">",exists="</span>+(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> File(apkPath).exists())); AssetManager assetManager = createAssetManager(apkPath); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration()); } } </code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li></ul>
DEMO
注意:我們使用Resources物件,獲取資源時,傳遞的ID必須是離線apk中R檔案對應的資源的ID。如果使用getIdentifier方法,第一個引數是資源名稱,第二個引數是資源型別,第三個引數是離線apk的包名,切記第三個引數。
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">Resources resources = BundlerResourceLoader.getBundleResource(getApplicationContext()); imageView = (ImageView)findViewById(R.id.image_view_iv); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(resources != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>){ String str = resources.getString(resources.getIdentifier(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"test_str"</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"string"</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"net.mobctrl.normal.apk"</span>)); String strById = resources.getString(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x7f050001</span>);<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//注意,id參照Bundle apk中的R檔案</span> System.out.println(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"debug:"</span>+str); Toast.makeText(getApplicationContext(),strById, Toast.LENGTH_SHORT).show(); Drawable drawable = resources.getDrawable(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x7f020000</span>);<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//注意,id參照Bundle apk中的R檔案</span> imageView.setImageDrawable(drawable); }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>
上述程式碼是載入離線apk中的字串和Drawable資源,那麼layout資源呢?
問題引入
我們使用LayoutInflate物件,一般使用方法如下:
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">View view = LayoutInflater.from(context).inflate(R.layout.main_fragment, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
其中,R.layout.main_fragment我們可以通過上述方法獲取其ID,那麼關鍵的一步就是如何生成一個context?直接傳入當前的context是不行的。
解決方案有2個:
- 1.建立一個自己的ContextImpl,Override其方法。
- 2.通過反射,直接替換當前context的mResources私有成員變數。<>br
當然,我們是使用第二種方案:
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"> <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">attachBaseContext</span>(Context context) { replaceContextResources(context); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.attachBaseContext(context); } <span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** * 使用反射的方式,使用Bundle的Resource物件,替換Context的mResources物件 *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> context */</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">replaceContextResources</span>(Context context){ <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">try</span> { Field field = context.getClass().getDeclaredField(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"mResources"</span>); field.setAccessible(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>); field.set(context, mBundleResources); System.out.println(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"debug:repalceResources succ"</span>); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">catch</span> (Exception e) { System.out.println(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"debug:repalceResources error"</span>); e.printStackTrace(); } } </code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li></ul>
我們在Activity的attachBaseContext方法中,對Context的mResources進行替換,這樣,我們就可以載入離線apk中的佈局了。
資原始檔的打包過程
如果想要做到外掛化,需要了解Android資原始檔的打包過程,這樣可以為每一個外掛進行編號,然後按照規則生成R檔案。例如,以攜程DynamicAPK為例,它將外掛的R檔案按照如下規則:
- 1.R檔案為int型,前8位代表外掛的Id,其中兩個特殊的Id:Host是0x7f,android系統自帶的是以0x01開頭.
- 2.緊跟著的8位是區分資源型別的,比如layout,id,string,dimen等
- 3.後面16位是資源的編號
按照上述規則生成對應的外掛apk。然後在執行時,我們可以寫一個ResourceManager類,它繼承自Resource物件,然後所有的Activity,都將其context的mResource成員變數修改為ResourceManager類,然後Override其方法,然後在載入資源時,根據不同的id的字首,查詢對應外掛的Resource即可。也就是說,用一個類做分發。
Android外掛化相關資料
- 1.Android動態載入基礎 ClassLoader工作機制 http://segmentfault.com/a/1190000004062880
- 2.Android動態載入黑科技 動態建立Activity模式 http://segmentfault.com/a/1190000004077469
- 3.Android外掛化框架Github總結 https://github.com/liaohuqiu/android-dynamic-load-awesome
- 4.攜程動態載入框架原始碼 https://github.com/CtripMobile/DynamicAPK
- 5.攜程Android App外掛化和動態載入實踐 http://www.infoq.com/cn/articles/ctrip-android-dynamic-loading
- 6.dex分包變形記 http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=401345907&idx=1http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=401345907&idx=1&sn=debdddf25950aaaa10f575472629b557
- 7.各大熱補丁方案分析和比較 http://blog.zhaiyifan.cn/2015/11/20/HotPatchCompare/
- 8.Android App 線上熱修復方案 http://lirenlong.github.io/hotfix/
- 9.Android 熱補丁動態修復框架小結 http://blog.csdn.net/lmj623565791/article/details/49883661
- 10.Android熱更新實現原理 http://blog.csdn.net/lzyzsd/article/details/49843581
- 11.【新技能get】讓App像Web一樣釋出新版本 http://bugly.qq.com/blog/?p=781
- 12.Android動態載入技術 系列索引 http://segmentfault.com/a/1190000004086213
- 13.Android對第三方類庫執行時載入 http://blog.csdn.net/dzg1977/article/details/41683173
- 14.關於Android如何動態載入res http://nobodycare.me/2014/11/07/about-loading-res-from-apk-directly/
- 15.Android應用程式資源的編譯和打包過程分析 http://blog.csdn.net/luoshengyang/article/details/8744683
- 16.Android應用程式資源管理器(Asset Manager)的建立過程分析 http://blog.csdn.net/luoshengyang/article/details/8791064
- 17.Android 自動編譯、打包生成apk檔案 1 - 命令列方式 http://blog.csdn.net/androiddevelop/article/details/10948639
相關文章
- Android外掛化(二):使用DexClassLoader動態載入assets中的apkAndroidAPK
- Android外掛化(一):使用改進的MultiDex動態載入assets中的apkAndroidIDEAPK
- 外掛化之程式碼呼叫與載入資源
- 外掛化技術:宿主訪問外掛資源
- Android外掛化原理(一)Activity外掛化Android
- framework外掛化技術-資源載入(免安裝)Framework
- Activity的外掛化(三)
- Android進階(十)資源和Service的外掛化Android
- framework外掛化技術-類載入Framework
- 外掛化設計三
- Android外掛化——VirtualAPK外掛框架接入專案AndroidAPK框架
- Android-動態載入外掛資源,皮膚包的實現原理Android
- Android外掛化技術之旅 1 開篇 - 實現啟動外掛與呼叫外掛中的Activity和ServiceAndroid
- Android外掛化開篇Android
- 淺析Android外掛化Android
- [外掛擴充套件]qq登入外掛套件
- 使用 Webapck 優化 VS Code 外掛載入效能Web優化
- 外掛化知識梳理(9) 資源的動態載入示例及原始碼分析原始碼
- android 外掛化開發 開源專案列表Android
- [外掛擴充套件]三級聯動外掛!!!!!!!!套件
- Android外掛化的一種方案Android
- [Android]AAB外掛化架構Android架構
- cad載入外掛快捷鍵命令 cad安裝外掛的快捷鍵
- win10外掛字幕srt怎麼載入_win10外掛字幕srt如何載入Win10
- webpack外掛之三Web
- 谷歌外掛下載谷歌
- tampermonkey外掛指令碼油猴外掛下載 - 篡改猴瀏覽器外掛指令碼瀏覽器
- 外掛化知識梳理(7) 類的動態載入入門
- [Android元件化]AAB外掛化架構Android元件化架構
- Activity的外掛化(一)
- Activity的外掛化(二)
- 熱修復與外掛化基礎——Java與Android的類載入器JavaAndroid
- 圖片懶載入外掛實戰
- Android 外掛化 動態升級Android
- Android外掛化原理解析——概要Android
- 外掛如何呼叫本外掛的View?View
- 微信登入外掛
- Chrome外掛入門Chrome