很久沒有寫博客了,此次準備寫寫我這幾天的研究成果——Android插件化開發框架CJFrameForAndroid。 java
首先,你須要知道什麼是插件化開發。就拿最多見的QQ來講,在第三個界面動態那裏有個管理,點開後能夠選擇不少的增植功能,這裏騰訊只放了一些網頁應用,那麼若是將來想加入一個打飛機遊戲,要怎麼作?讓用戶從新安裝嗎,這就是插件化開發所解決的問題。git
用一句話來歸納插件式開發:你基本上能夠理解爲讓一個apk不安裝也能夠被運行。只不過這個運行是有不少限制的運行,因此才叫插件不然就叫病毒了。其實在目前淘寶、百度、騰訊、等都有成熟的動態加載框架,包括apkplug,可是它們都是不開源的。github
說一下我認爲這項技術的難點:一、一個未被安裝的apk正常狀況沒法被運行;二、這個apk的資源沒辦法被引用;三、這個apk的界面就算被加載,也沒辦法與用戶交互。 app
最初查遍了資料,第一點好解決,在Android中有一個dexClassLoad類加載器,你們應該明白了,就是經過反射加載一個類來運行。第二點,網上有兩種方法:能夠將插件的資源放到sd卡上經過流的形式讀取,不過也有人反對說用流讀取會有問題,通配性太差;一種比較好的解決辦法是將apk中的資源複製一份到當前app內,而後就能夠加載了。這種辦法是不錯,可是用戶每下載一次插件就複製一份,長此以往,對空間要求過高了,還有就是第三點也沒辦法解決。而第三點,在github上有一個叫AndroidDynamicLoader的項目,是經過用Fragment作爲插件的表現形式,因爲Fragment特殊性(既能夠處理邏輯交互又具有與Activity相同的生命週期)。但是Fragment限制性太大了,太過碎片化使得使用起來複雜性太高。直到我找到了一篇360的官方博客,博客給了一種思路:經過代理/委託模式設計的Application類去動態的改變一個apk所在的環境,實現動態加載的目的。抱着這種思路,我曾想本身去設計一個application類,可是技術有限,太複雜了,因而結合AndroidDynamicLoader的思路與360的思路,我本身設計了一個Activity去代理插件的Activity,因而就有了CJFrameForAndroid.框架
首先解釋幾個名詞:ide
APP項目:指要調用插件apk的那個已經安裝到用戶手機上的應用。
插件項目:指沒有被安裝且但願藉助已經安裝到手機上的項目運行的apk。
插件化:Activity繼承自CJActivity,且與APP項目jar包衝突已經解決的插件項目稱爲已經被插件化。
Activity事務:在CJFrameForAndroid中,一個Activity的生命週期以及交互事件統稱爲Activity的事務。
託管所:指插件中的一個委派/代理Activity,經過這個Activity去處理插件中Activity的所有事務,從而表現爲就像插件中的Activity在運行同樣。測試
CJFrameForAndroid的實現原理是經過類加載器,動態加載存在於SD卡上的apk包中的Activity。經過使用一個託管所,插件Activity所有事務(包括聲明週期與交互事件)將交由託管所來處理,間接實現插件的運行。
一句話描述:CJFrameForAndroid中的託管所,複製了插件中的Activity,來替代插件中的Activity與用戶交互。
看到這裏你應該就明白了,整個框架最核心的部分就是這個託管所。這裏給出CJFrameForAndroid中這個託管所的細節代碼:this
/** * 經過反射,獲取到插件的資源訪問器 */ protected void initResources() { try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod( "addAssetPath", String.class); addAssetPath.invoke(assetManager, mDexPath); mAssetManager = assetManager; } catch (Exception e) { e.printStackTrace(); } Resources superRes = super.getResources(); mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); mTheme = mResources.newTheme(); mTheme.setTo(super.getTheme()); } /** * 啓動插件的Activity */ protected void launchPluginActivity() { PackageInfo packageInfo = CJTool.getAppInfo(this, mDexPath); if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) { String activityName = packageInfo.activities[mAtyIndex].name; mClass = activityName; launchPluginActivity(mClass); } } /** * 啓動指定的Activity * * @param className * 要啓動的Activity完整類名 */ protected void launchPluginActivity(final String className) { try { Class<?> atyClass = getClassLoader().loadClass(className); Constructor<?> atyConstructor = atyClass .getConstructor(new Class[] {}); Object instance = atyConstructor.newInstance(new Object[] {}); setRemoteActivity(instance); mPluginAty.setProxy(this, mDexPath); Bundle bundle = new Bundle(); bundle.putInt(CJConfig.FROM, CJConfig.FROM_PROXY_APP); mPluginAty.onCreate(bundle); } catch (Exception e) { e.printStackTrace(); } } /** * 保留一份插件Activity對象 */ protected void setRemoteActivity(Object activity) { if (activity instanceof I_CJActivity) { mPluginAty = (I_CJActivity) activity; } else { throw new ClassCastException( "plugin activity must implements I_CJActivity"); } }
本框架目前僅僅是一個開發階段,僅僅是實現了插件Activity的運行(原理上來講,動態註冊的廣播也能夠運行),而Service、contentProvider都沒辦法使用,這些都仍在研究中。
在將來的某一天,也許會將這個CJFrameForAndroid插件框架與KJFrameForAndroid快捷開發框架合併,組成一個更完善應用開發框架,對本身說:加油!spa
●目前僅支持Activity和Fragment,其餘特殊組件暫未測試。
●APP項目和插件項目中,都須要使用到CJFrameForAndroid的jar包。
●在項目中必須加入托管所聲明。
●在開發插件的時候,必須繼承CJActivity;
●在插件的Activity中,一切使用this的部分必須使用that來替代;
●在插件Activity跳轉時,推薦使用CJActivityUtils類來輔助跳轉;
●在插件和APP兩個工程中不能引用相同的jar包;插件