Android黑科技動(dòng)態(tài)加載(四)之動(dòng)態(tài)啟動(dòng)插件Activity

目錄

Android黑科技動(dòng)態(tài)加載(一)之Java中的ClassLoader
Android黑科技動(dòng)態(tài)加載(二)之Android中的ClassLoader
Android黑科技動(dòng)態(tài)加載(三)之動(dòng)態(tài)加載資源
Android黑科技動(dòng)態(tài)加載(四)之插件化開(kāi)發(fā)

項(xiàng)目地址

android_plugin_activity.gif

如果做插件化

Android動(dòng)態(tài)加載技術(shù)三個(gè)關(guān)鍵問(wèn)題詳解一文中指出插件化需要解決的三個(gè)問(wèn)題. 其中兩個(gè)問(wèn)題已經(jīng)在前面博客中解決了. 現(xiàn)在剩下的問(wèn)題就是如何去啟動(dòng)Activity.

啟動(dòng)已經(jīng)安裝的插件的Activity很簡(jiǎn)單, 只需要使用隱式啟動(dòng)就可以了

那對(duì)于未安裝的插件的Activity, 我們使用一種思想叫做代理Activity

代理Activity

代理Activity的思想就是通過(guò)代理Activity占坑, 然后我們資源或者業(yè)務(wù)邏輯之類(lèi)的都是加載插件Activity的. 這樣我們就能把插件Activity加"啟動(dòng)起來(lái)".

編碼

首先我們需要新建一個(gè)Bean來(lái)存放已經(jīng)加載的插件APK信息

/**
 * 用來(lái)存放已經(jīng)加載的插件APK信息
 */
public class PluginApk {
    public PackageInfo packageInfo;
    public Resources resources;
    public ClassLoader classLoader;

    public PluginApk(Resources resources) {
        this.resources = resources;
    }

}

然后我們根據(jù)之前加載資源的方式去加載插件APK的信息


/**
 * 創(chuàng)建一個(gè)Entity保存APK的信息
 *
 * @param apkPath
 * @return
 */
private PluginApk createApk(String apkPath) {
    PluginApk pluginApk = null;
    try {
        // 事實(shí)就是跟前面那樣動(dòng)態(tài)加載資源的原理是一樣的
        AssetManager assetManager = AssetManager.class.newInstance();
        Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);
        addAssetPathMethod.invoke(assetManager, apkPath);
        Resources resources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(),
                mContext.getResources().getConfiguration());
        pluginApk = new PluginApk(resources);
        pluginApk.classLoader = createDexClassLoader(apkPath);
    } catch (Exception e) {
        e.printStackTrace();
    }

    return pluginApk;
}


/**
 * 查詢(xún)APK的包信息
 *
 * @param apkPath
 * @return
 */

private PackageInfo queryPackageInfo(String apkPath) {
    PackageManager packageManager = mContext.getPackageManager();
    PackageInfo packageInfo = packageManager.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
    return packageInfo;
}

/**
 * 加載并創(chuàng)建APK的信息
 *
 * @param apkPath
 * @return
 */
public PluginApk loadApk(String apkPath) {
    PackageInfo packageInfo = queryPackageInfo(apkPath);    // 獲取未安裝的插件APK包信息
    if (packageInfo == null || TextUtils.isEmpty(packageInfo.packageName)) {
        return null;
    }
    PluginApk pluginApk = sMap.get(packageInfo.packageName);    // 從緩存中獲取
    if (pluginApk == null) {
        pluginApk = createApk(apkPath); // 緩存中不存在, 則開(kāi)始創(chuàng)建APK信息
        if (pluginApk != null) {
            // 緩存
            pluginApk.packageInfo = packageInfo;
            sMap.put(packageInfo.packageName, pluginApk);
        } else {
            throw new NullPointerException("plugin apk is null");
        }
    }
    return pluginApk;
}

至此, 我們的插件APK資源就已經(jīng)獲取完畢了. 下面我們就開(kāi)始編寫(xiě)代理Activity的邏輯吧.


/**
 * 代理Activity, 真正啟動(dòng)的Activity是這個(gè), 但是加載的資源是插件Activity的
 */
public class ProxyActivity extends Activity {
    LifeCircleController mPluginController = new LifeCircleController(this);    //  用于管理代理Activity生命周倩和資源的類(lèi)


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPluginController.onCreate(getIntent().getExtras());
    }

    @Override
    public Resources getResources() {
        Resources resources = mPluginController.getResources();
        return null != resources ? resources : super.getResources();
    }

    @Override
    public AssetManager getAssets() {
        AssetManager assets = mPluginController.getAssets();
        return null != assets ? mPluginController.getAssets() : super.getAssets();
    }

    @Override
    public ClassLoader getClassLoader() {
        ClassLoader loader = mPluginController.getClassLoader();
        return null != loader ? loader : super.getClassLoader();
    }
}

說(shuō)白, 代理Activity的邏輯不多, 就是一個(gè)空殼. 重要的邏輯都寫(xiě)在LifeCircleController中.

/**
 * 代理Activity生命周期以及資源管理類(lèi)
 */
public class LifeCircleController implements Pluginable {

    public static final String KEY_PLUGIN_CLASS_NAME = "plugin_class_name"; // 用來(lái)傳遞需要需要啟動(dòng)的Activity的類(lèi)名
    public static final String KEY_PACKAGE_NAME = "package_name";   // Activity所在插件APK的包名
    Activity mActivityProxy;
    PluginActivity mPluginActivity;
    PluginApk mPluginApk;

    public LifeCircleController(ProxyActivity activityProxy) {
        this.mActivityProxy = activityProxy;
    }

    /**
     * 加載插件Activity
     *
     * @param classLoader
     * @param pluginName
     * @return
     */
    private PluginActivity loadPluginActivity(ClassLoader classLoader, String pluginName) {
        PluginActivity activity = null;
        try {
            Class cls = classLoader.loadClass(pluginName);
            activity = (PluginActivity) cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return activity;
    }

    /**
     * 代理獲取資源
     *
     * @return
     */
    public Resources getResources() {
        return null != mPluginApk ? mPluginApk.resources : null;
    }

    /**
     * 代理獲取資源
     *
     * @return
     */
    public AssetManager getAssets() {
        return mPluginApk.resources.getAssets();
    }


    /**
     * 解析需要啟動(dòng)的Activity
     *
     * @param extras
     */
    @Override
    public void onCreate(Bundle extras) {
        String pluginName = extras.getString(KEY_PLUGIN_CLASS_NAME);
        String packageName = extras.getString(LifeCircleController.KEY_PACKAGE_NAME);
        mPluginApk = ActivityManager.getInstance().getPluginApk(packageName);   //  獲取加載的插件APK信息
        mPluginActivity = loadPluginActivity(mPluginApk.classLoader, pluginName);   // 加載插件Activity
        mPluginActivity.attach(mActivityProxy); //  綁定代理Activity到插件Activity中, 使其能夠調(diào)用代理Activity對(duì)應(yīng)的應(yīng)用資源等方法
        mPluginActivity.onCreate(null); //  代理生命周期
    }

    // 代理生命周期

    @Override
    public void onRestart() {
        mPluginActivity.onRestart();
    }

    @Override
    public void onStart() {
        mPluginActivity.onStart();
    }

    @Override
    public void onResume() {
        mPluginActivity.onResume();
    }

    @Override
    public void onPause() {
        mPluginActivity.onPause();
    }

    @Override
    public void onStop() {
        mPluginActivity.onStop();
    }

    @Override
    public void onDestroy() {
        mPluginActivity.onDestroy();
    }


    public ClassLoader getClassLoader() {
        return mPluginApk.classLoader;
    }
}

其中我們提供一個(gè)專(zhuān)門(mén)同步生命周期的接口

public interface Pluginable {
    void onCreate(Bundle var1);

    void onRestart();

    void onStart();

    void onResume();

    void onPause();

    void onStop();

    void onDestroy();
}

還有一個(gè)Attachable接口


public interface Attachable<T> {
    void attach(T data);
}

下面的就是所有插件Activity都必須繼承的父類(lèi)


/**
 * 所有插件Activity都必須繼承的基類(lèi)
 */
public class PluginActivity extends Activity implements Pluginable, Attachable<Activity> {

    private Activity mProxyActivity;


    @Override
    public Window getWindow() {
        return mProxyActivity.getWindow();
    }


    @Override
    public View findViewById(int id) {
        return mProxyActivity.findViewById(id);
    }

    /**
     * 使用代理Activity去設(shè)置加載到的資源, 因?yàn)榇鞟ctivity本身就是優(yōu)先使用插件APK的ClassLoader和Resource, 所以該方法會(huì)加載到插件APK的布局
     *
     * @param layoutResID
     */
    @Override
    public void setContentView(int layoutResID) {
        mProxyActivity.setContentView(layoutResID);
    }


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    }

    @Override
    public void onRestart() {
    }

    @Override
    public void onStart() {
    }

    @Override
    public void onResume() {
    }

    @Override
    public void onPause() {
    }

    @Override
    public void onStop() {
    }

    @Override
    public void onDestroy() {
    }


    @Override
    public void attach(Activity data) {
        this.mProxyActivity = data;
    }
}

然后Plugin相關(guān)類(lèi)需要導(dǎo)出Jar包, 這樣主包跟插件包才能使用同樣的類(lèi)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汇恤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子河质,更是在濱河造成了極大的恐慌跷叉,老刑警劉巖顿锰,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異泡垃,居然都是意外死亡捂掰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)想括,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)陷谱,“玉大人,你說(shuō)我怎么就攤上這事瑟蜈⊙萄罚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵铺根,是天一觀(guān)的道長(zhǎng)宪躯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)位迂,這世上最難降的妖魔是什么访雪? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任肮砾,我火速辦了婚禮皱卓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痹换。我一直安慰自己泻帮,他們只是感情好精置,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著锣杂,像睡著了一般脂倦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹲堂,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天狼讨,我揣著相機(jī)與錄音,去河邊找鬼柒竞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛播聪,可吹牛的內(nèi)容都是我干的朽基。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼离陶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼稼虎!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起招刨,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤霎俩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體打却,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杉适,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柳击。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猿推。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖捌肴,靈堂內(nèi)的尸體忽然破棺而出蹬叭,到底是詐尸還是另有隱情,我是刑警寧澤状知,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布秽五,位于F島的核電站,受9級(jí)特大地震影響饥悴,放射性物質(zhì)發(fā)生泄漏坦喘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一铺坞、第九天 我趴在偏房一處隱蔽的房頂上張望起宽。 院中可真熱鬧,春花似錦济榨、人聲如沸坯沪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腐晾。三九已至,卻和暖如春丐一,著一層夾襖步出監(jiān)牢的瞬間藻糖,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工库车, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巨柒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓柠衍,卻偏偏與公主長(zhǎng)得像洋满,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子珍坊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容