第一代插件化:代理实现(dynamic-load-apk)

第一代插件化:代理实现(dynamic-load-apk)_第1张图片
第一代插件化:代理实现(dynamic-load-apk).jpg

原理与背景

如何启动未安装的apk(四大组件)里的类。如何加载类、加载资源、管理组件生命周期。

类加载

外部的dex文件,通过DexClassLoader类加载。根据插件的apk路径,构造对应的类加载器:

    /**
     * 类加载:
     * 外部dex文件,通过 DexClassLoader 类加载。
     * @param apkPath apk路径
     * @return DexClassLoader对象
     */
    private DexClassLoader createDexClassLoader(String apkPath) {
        File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
        return new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(), null, mContext.getClassLoader());
    }

资源加载

通过Resource对象加载资源,把插件apk里的资源加载到AssetManager中:

/**
     *
     * @param apkPath apk路径
     * @return pluginApk
     */
    private PluginApk createApk(String apkPath) {
        String addAssetPathMethod = "addAssetPath";
        PluginApk pluginApk = null;
        try { // 资源加载
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);
            addAssetPath.invoke(assetManager, apkPath);// 把资源通过反射加载进去
            Resources pluginRes = new Resources(assetManager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration());
            pluginApk = new PluginApk(pluginRes);
            pluginApk.classLoader = createDexClassLoader(apkPath);
        } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return pluginApk;
    }

生命周期

早期dynamic-load-apk采用代理方式,通过空壳Activity作为代理(Proxy),系统对该Activity的回调都映射到插件Activity。插件Activity都继承这个PluginActivity,侵入性强。避免侵入性成了二代插件化框架目标。

代理实现

在plugin(module库library级别)中:

PluginManager类来实现插件的加载,通过Map集合管理所有插件apk。根据插件apk路径实现类加载、资源加载、启动插件activity等。
PluginApk类:是插件apk的实体映射类。
PluginActivity:所有插件都要继承这个类,这是一个壳子。继承了Activity的一些方法。
ProxyActivity:系统实际启动的类,必须在主app中注册(代理,启动的时候躲过主app对插件activity注册检查),系统通过该类触发对应方法。代理给LifeCircleController去具体实现。
LifeCircleController:具体实现类,系统实际启动的类。

在插件(pluginapp)

只需要继承plugin(library)中的PluginActivity就行:

MainActivity extends PluginActivity

我是直接生成plugin.apk(debug.apk方便我调试)。如果直接运行这个apk会包错

java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.res.Resources.getBoolean(int)'

但是不影响生成apk。把这个plugin.apk放到sdcard/plugins文件下。方便下面主app去加载。

在主工程(app)中:

注册:

        
        

添加权限:

    
    

启动插件pluginapp代码:根据路径和包名启动

public class MainActivity extends Activity {
    public final static String PLUGIN_NAME = "plugin.apk";
    public final static String PLUGIN_PACKAGE_NAME = "com.george.pluginapp";
    public final static String PLUGIN_CLAZZ_NAME = "com.george.pluginapp.MainActivity";
    private PluginManager mPluginManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.tv_content).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.putExtra(Constants.PACKAGE_NAME, PLUGIN_PACKAGE_NAME);
                intent.putExtra(Constants.PLUGIN_CLASS_NAME, PLUGIN_CLAZZ_NAME);
                mPluginManager.startActivity(intent);
            }
        });
        Utils.verifyStoragePermissions(this);

        PluginManager.init(getApplicationContext());// 初始化插件加载器
        mPluginManager = PluginManager.getInstance();
        String pluginApkPath = Environment.getExternalStorageDirectory() + File.separator + "plugins" + File.separator + PLUGIN_NAME;// 插件apk路径
        VLog.log("can read: " + Environment.getExternalStorageDirectory().canRead());
        VLog.log(pluginApkPath);
        mPluginManager.loadApk(pluginApkPath);// 插件apk加载进主app中
    }
}

打开主app,点击跳转插件按钮,发现成功加载。
demo地址

你可能感兴趣的:(第一代插件化:代理实现(dynamic-load-apk))