Android外掛程式化開發---運行未安裝apk中的Service

來源:互聯網
上載者:User

標籤: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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.