Android插件化開發,初入殿堂

很久沒有寫博客了,此次準備寫寫我這幾天的研究成果——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包;插件

相關文章
相關標籤/搜索