Android插件化開發,運行未安裝apk中的Service

        若是你還不知道什麼叫插件化開發,那麼你應該先讀一讀以前寫的這篇博客: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

相關文章
相關標籤/搜索