Android黑科技动态加载(四)之动态启动插件Activity

目录

Android黑科技动态加载(一)之Java中的ClassLoader
Android黑科技动态加载(二)之Android中的ClassLoader
Android黑科技动态加载(三)之动态加载资源
Android黑科技动态加载(四)之插件化开发

项目地址

android_plugin_activity.gif

如果做插件化

Android动态加载技术三个关键问题详解一文中指出插件化需要解决的三个问题. 其中两个问题已经在前面博客中解决了. 现在剩下的问题就是如何去启动Activity.

启动已经安装的插件的Activity很简单, 只需要使用隐式启动就可以了

那对于未安装的插件的Activity, 我们使用一种思想叫做代理Activity

代理Activity

代理Activity的思想就是通过代理Activity占坑, 然后我们资源或者业务逻辑之类的都是加载插件Activity的. 这样我们就能把插件Activity加"启动起来".

编码

首先我们需要新建一个Bean来存放已经加载的插件APK信息

/**
 * 用来存放已经加载的插件APK信息
 */
public class PluginApk {
    public PackageInfo packageInfo;
    public Resources resources;
    public ClassLoader classLoader;

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

}

然后我们根据之前加载资源的方式去加载插件APK的信息


/**
 * 创建一个Entity保存APK的信息
 *
 * @param apkPath
 * @return
 */
private PluginApk createApk(String apkPath) {
    PluginApk pluginApk = null;
    try {
        // 事实就是跟前面那样动态加载资源的原理是一样的
        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;
}


/**
 * 查询APK的包信息
 *
 * @param apkPath
 * @return
 */

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

/**
 * 加载并创建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); // 缓存中不存在, 则开始创建APK信息
        if (pluginApk != null) {
            // 缓存
            pluginApk.packageInfo = packageInfo;
            sMap.put(packageInfo.packageName, pluginApk);
        } else {
            throw new NullPointerException("plugin apk is null");
        }
    }
    return pluginApk;
}

至此, 我们的插件APK资源就已经获取完毕了. 下面我们就开始编写代理Activity的逻辑吧.


/**
 * 代理Activity, 真正启动的Activity是这个, 但是加载的资源是插件Activity的
 */
public class ProxyActivity extends Activity {
    LifeCircleController mPluginController = new LifeCircleController(this);    //  用于管理代理Activity生命周倩和资源的类


    @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();
    }
}

说白, 代理Activity的逻辑不多, 就是一个空壳. 重要的逻辑都写在LifeCircleController中.

/**
 * 代理Activity生命周期以及资源管理类
 */
public class LifeCircleController implements Pluginable {

    public static final String KEY_PLUGIN_CLASS_NAME = "plugin_class_name"; // 用来传递需要需要启动的Activity的类名
    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();
    }


    /**
     * 解析需要启动的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中, 使其能够调用代理Activity对应的应用资源等方法
        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;
    }
}

其中我们提供一个专门同步生命周期的接口

public interface Pluginable {
    void onCreate(Bundle var1);

    void onRestart();

    void onStart();

    void onResume();

    void onPause();

    void onStop();

    void onDestroy();
}

还有一个Attachable接口


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

下面的就是所有插件Activity都必须继承的父类


/**
 * 所有插件Activity都必须继承的基类
 */
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去设置加载到的资源, 因为代理Activity本身就是优先使用插件APK的ClassLoader和Resource, 所以该方法会加载到插件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相关类需要导出Jar包, 这样主包跟插件包才能使用同样的类

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容