標籤:cjframeforandroid kjframeforandroid android開發 android應用 開發架構
如果你還不知道什麼叫外掛程式化開發,那麼你應該先讀一讀之前寫的這篇部落格:Android外掛程式化開發,初入殿堂
上一篇部落客要從整體角度分析了一下Android外掛程式化開發的幾個痛點與動態載入沒有被安裝的apk中的Activity和資源的方法。其實一般的外掛程式開發主要也就是載入個Activity,讀取一些資源圖片之類的。但是總有遇到特殊情況的時候,比如載入Service。
要動態載入Service,有兩種思路:一是通過NDK的形式,將Service通過C++運行起來(這種方法我沒有嘗試,只聽群裡的朋友說實現過);另一種就是我使用的,具體思路和上一篇中提到載入Activity的方法一樣,使用託管所的形式,由於上一篇部落格沒有講清楚,這裡就詳細講一下通過託管所實現載入外掛程式中Service的方法。
以下幾點是每一個Android開發組肯定都知到的: 一個apk如果沒有被安裝的話是沒有辦法直接啟動並執行。一個JAVA類的class檔案是可以通過classload類載入器讀取的。一個apk實際上就是一個壓縮包,其中包含了一個.dex檔案就是我們的代碼檔案。那麼,接下來基本思路我們就可以明確了:apk沒辦法直接運行,apk中有代碼檔案,代碼檔案可以被classload讀取。
在Android中有兩種classload,分別是DexClassLoader、PathClassLoader。後者只能載入/data/app目錄下的apk也就是apk必須要安裝才能被載入,這不是我們想要的,所以我們使用前者:DexClassLoader。
public class CJClassLoader extends DexClassLoader { //建立一個外掛程式載入器集合,對固定的dex使用固定的載入器可以防止多個載入器同時載入一個dex造成的錯誤。 private static final HashMap<String, CJClassLoader> pluginLoader = new HashMap<String, CJClassLoader>(); protected CJClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, optimizedDirectory, libraryPath, parent); } /** * 返回dexPath對應的載入器 */ public static CJClassLoader getClassLoader(String dexPath, Context cxt, ClassLoader parent) { CJClassLoader cjLoader = pluginLoader.get(dexPath); if (cjLoader == null) { // 擷取到app的啟動路徑 final String dexOutputPath = cxt .getDir("dex", Context.MODE_PRIVATE).getAbsolutePath(); cjLoader = new CJClassLoader(dexPath, dexOutputPath, null, parent); pluginLoader.put(dexPath, cjLoader); } return cjLoader; }}
以上只是一個開始,接著我們需要考慮一個問題,一個Service是有oncreate->onstart->ondestroy生命週期以及一些回調方法的,這些回調方法在我們正常使用的時候是由父類們(包括has...a...關係)或者說是SDK管理的,那麼當我們通過類載入器載入的時候,它是沒有能夠管理的父類的,也就是說我們需要自己類比SDK去管理外掛程式Service的回呼函數。那麼這個去管理外掛程式Service的類,就是之前提到的託管所。
這裡是我將Service中的回調方法抽出來寫成的一個介面
public interface I_CJService { IBinder onBind(Intent intent); void onCreate(); int onStartCommand(Intent intent, int flags, int startId); void onDestroy(); void onConfigurationChanged(Configuration newConfig); void onLowMemory(); void onTrimMemory(int level); boolean onUnbind(Intent intent); void onRebind(Intent intent); void onTaskRemoved(Intent rootIntent);}
//一個託管所類class CJProxyService extends Service{ //採用內含項目關聯性 protected I_CJService mPluginService; // 外掛程式Service對象}
這裡採用內含項目關聯性而不是採用繼承(或者說實現一個介面)的方式,
是由於我們需要重寫Service中的方法,而這些被重寫的方法都需要用到介面對象相應的介面方法。
public class CJProxyService extends Service{ @Override public void onConfigurationChanged(Configuration newConfig) { mPluginService.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig); } @Override public void onLowMemory() { mPluginService.onLowMemory(); super.onLowMemory(); } @Override @SuppressLint("NewApi") public void onTrimMemory(int level) { mPluginService.onTrimMemory(level); super.onTrimMemory(level); } @Override public boolean onUnbind(Intent intent) { mPluginService.onUnbind(intent); return super.onUnbind(intent); } @Override public void onRebind(Intent intent) { mPluginService.onRebind(intent); super.onRebind(intent); }}
看到這裡大家應該也就明白了,託管所實際上就是一個普通的Service類,但是這個託管所是正常啟動並執行,是由SDK管理回呼函數的,我們通過這個Service的回呼函數去調用外掛程式Service中相應的回調方法,就間接的管理了外掛程式Service的生命週期(此處可以類比Activity與Fragment的關係)
到這裡為止,我們已經可以成功調起一個外掛程式Service了,接下來的問題就是這個I_CJSrvice對象從哪裡來?很簡單,通過類載入器載入一個
private void init(Intent itFromApp) { Object instance = null; try { Class<?> serviceClass; if (CJConfig.DEF_STR.equals(mDexPath)) { serviceClass = super.getClassLoader().loadClass(mClass); } else { serviceClass = this.getClassLoader().loadClass(mClass); } Constructor<?> serviceConstructor = serviceClass .getConstructor(new Class[] {}); instance = serviceConstructor.newInstance(new Object[] {}); } catch (Exception e) { } setRemoteService(instance); mPluginService.setProxy(this, mDexPath); } /** * 保留一份外掛程式Service對象 */ protected void setRemoteService(Object service) { if (service instanceof I_CJService) { mPluginService = (I_CJService) service; } else { throw new ClassCastException( "plugin service must implements I_CJService"); } }
這樣就可以拿到一個I_CJSrvice對象mPluginService了,如果到此為止,還是會有問題,因為此時mPluginService中例如onStart方法還對應的是那個外掛程式中的onStart也就是父類的onStart(這裡比較繞,我不知道該如何描述),而之前我們又說過,通過反射載入的類是沒有父類的,那麼如果此時強制調用那個反射對象的@Override方法是會報null 指標的,因為找不到父類。那麼解決的辦法就是再去外掛程式Service中重寫每個@Override的方法。
//.......篇幅有限,部分截取public abstract class CJService extends Service implements I_CJService { /** * that指標指向的是當前外掛程式的Context(由於是外掛程式化開發,this指標絕對不能使用) */ protected Service that; // 替代this指標 @Override public IBinder onBind(Intent intent) { if (mFrom == CJConfig.FROM_PLUGIN) { return null; } else { return that.onBind(intent); } }}
通過代可以看到:我們使用了一個that對象來替代原本的this對象,然後我們只需要通過在託管所中將這個that對象賦值為託管所的this對象,也就是外掛程式中的所有that.xxx都相當於調用的是託管所的this.xxx,那麼動態替換的目的就達到了,這樣我們也就成功的載入了一個未被安裝的外掛程式apk中的Service。
有關本類中的代碼,以及完整的Demo,你可以關註:Android外掛程式式開發架構 CJFrameForAndroid
Android外掛程式化開發---運行未安裝apk中的Service