若是你還不知道什麼叫插件化開發,那麼你應該先讀一讀以前寫的這篇博客:Android插件化開發,初入殿堂
java
上一篇博客主要從總體角度分析了一下Android插件化開發的幾個難點與動態加載沒有被安裝的apk中的Activity和資源的方法。其實通常的插件開發主要也就是加載個Activity,讀取一些資源圖片之類的。可是總有遇到特殊狀況的時候,好比加載Service。git
要動態加載Service,有兩種思路:一是經過NDK的形式,將Service經過C++運行起來(這種方法我沒有嘗試,只聽羣裏的朋友說實現過);另外一種就是我使用的,具體思路和上一篇中提到加載Activity的方法同樣,使用託管所的形式,因爲上一篇博客沒有講清楚,這裏就詳細講一下經過託管所實現加載插件中Service的方法。github
如下幾點是每個Android開發組確定都知到的: 一個apk若是沒有被安裝的話是沒有辦法直接運行的。一個JAVA類的class文件是能夠經過classload類加載器讀取的。一個apk實際上就是一個壓縮包,其中包含了一個.dex文件就是咱們的代碼文件。那麼,接下來基本思路咱們就能夠明確了:apk沒辦法直接運行,apk中有代碼文件,代碼文件能夠被classload讀取。app
在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的類,就是以前提到的託管所。ide
這裏是我將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); }
.this
//一個託管所類 class CJProxyService extends Service{ //採用包含關係 protected I_CJService mPluginService; // 插件Service對象 }
這裏採用包含關係而不是採用繼承(或者說實現一個接口)的方式,是因爲咱們須要重寫Service中的方法,而這些被重寫的方法都須要用到接口對象相應的接口方法。
.net
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方法是會報空指針的,由於找不到父類。那麼解決的辦法就是再去插件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