android开发模式之插件化开发

216 阅读4分钟

一、简介

插件化开发是将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。由宿主APP去加载以及运行插件APP。

二、存在的意义

  1. 宿主和插件分开编译
  2. 并发开发
  3. 动态更新插件
  4. 按需下载模块
  5. 解除单个dex函数不能超过65535的限制

处境:开放出来的插件化开发框架比较多,他们各自都有自己的优势和和不足,实现的原理也有差别下面列举开源的插件化框架

github.com/Qihoo360/Dr… github.com/CtripMobile… github.com/mmin18/Andr… github.com/singwhatiwa… github.com/houkx/andro… github.com/bunnyblue/A… github.com/wequick/Sma…

此外存在缺点,插件化开发的APP不能在Google Play上线,也就是没有海外市场。

三、实现步骤,本文demo地址在文末

3.1 新建空白演示demo的项目

这里新建2个application:app包、otherapk包,以及一个library:plugin包

其中app包作为插件化宿主,作为apk的入口。otherapk作为第三方apk包,用于生成符合插件化开发标准的插件apk,不参与宿主的编译。plugin实现加载第三方apk的功能,并制定插件化开发的约束规范。

在这里插入图片描述

3.2 app包中的处理

在这里,我们跳转到plugin包中的代理activity(ProxyActivity)中,传递全局的Context参数,第三方插件apk的SD卡路径(这个插件apk由other包生成),以及需要ProxyActivity代理的插件apk的页面(例如liuxingyu.otherapk.PluginMainActivity,这个可以由PluginManager动态查找得到)。

public void startApk(View view) {
        /**
         * 加载插件
         */
        //设置上下文
        PluginManager.getInstance().setContext(getApplicationContext());
        //加载第三方插件apk 传入第三方插件apk的绝对路径
        PluginManager.getInstance().loadApk(Environment.getExternalStorageDirectory().getAbsolutePath() + "/otherapk-debug.apk");

        /**
         * 跳转到代理Activity中去
         */
        Intent intent = new Intent(this, ProxyActivity.class);
        String otherApkMainActivityName = PluginManager.getInstance().getPluginPackageArchiveInfo().activities[0].name;
        intent.putExtra("className", otherApkMainActivityName);
        startActivity(intent);
    }
3.3 otherapk包中的处理

这里我们新建简单页面,继承于plugin包中的BaseActivity,使之符合插件化开发规范。然后在开发工具中选中otherapk包,点击build -> Make Module 'otherapk',并将生成的包放到对应SD卡目录中

在这里插入图片描述 在这里插入图片描述

public class PluginMainActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin_main);
        TextView textView=that.findViewById(R.id.textView);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent(that,SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}
3.3 plugin包中的处理
3.3.1 代理activity ProxyActivity的实现

这里是整个插件化开发能够运行的重点。这里首先我们需要实现app包中跳转过来的ProxyActivity,并接收传递过来的className参数。然后根据className参数从插件apk中查找到所要代理的activity类class。

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //拿到要启动的Activity
        String className = getIntent().getStringExtra("className");
        try {
            //加载该Activity的字节码对象
            Class<?> aClass = PluginManager.getInstance().getPluginDexClassLoader().loadClass(className);
            //创建该Activity的示例
            Object newInstance = aClass.newInstance();
            //是否遵循了我们的标准
            if (newInstance instanceof PluginInterface) {
                pluginInterface = (PluginInterface) newInstance;
                //将代理Activity的上下文 传入到插件Apk中的activity里面
                pluginInterface.attach(this);
                //创建bundle用来与三方apk传输数据
                Bundle bundle = new Bundle();
                //去执行插件APK中的activity的onCreate
                pluginInterface.onCreate(bundle);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
3.3.2 插件化管理类PluginManager的实现

这里我们要实现ProxyActivity,最重要的功能就是实现PluginManager的插件化管理。我们用PluginManager来接收宿主apk传递过来的Context与插件apk的路径,进而可以获取到插件apk的资源与类。

public class PluginManager {

    private static PluginManager ourInstance = new PluginManager();
    private Context context;

    private DexClassLoader pluginDexClassLoader;
    private Resources pluginResources;

    public PackageInfo getPluginPackageArchiveInfo() {
        return pluginPackageArchiveInfo;
    }

    private PackageInfo pluginPackageArchiveInfo;

    public static PluginManager getInstance() {
        return ourInstance;
    }

    private PluginManager() {
    }

    public void setContext(Context context) {
        this.context = context.getApplicationContext();
    }

    public void loadApk(String dexPath) {
        //(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)
        pluginDexClassLoader = new DexClassLoader(dexPath, context.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath(), null, context.getClassLoader());

        pluginPackageArchiveInfo = context.getPackageManager().getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);

        //Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
        AssetManager assets = null;
        try {
            assets = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assets, dexPath);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        pluginResources = new Resources(assets, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
    }


    public DexClassLoader getPluginDexClassLoader() {
        return pluginDexClassLoader;
    }

    public Resources getPluginResources() {
        return pluginResources;
    }
}
3.3.3 BaseActivity让插件apk调用可以使用的context,制定开发规范

这里我们通过PluginManager获取到的插件apk中的类让ProxyActivity直接代理并不可行,因为插件apk中的this上下文并不存在,所以我们需要让插件apk上下文使用ProxyActivity中的context上下文。

public class BaseActivity extends FragmentActivity implements PluginInterface {
    //主APK的引用对象 也就是说 所有插件apk中用到上下文的地方都是用代理Activity的上下文代替(that)
    protected Activity that;

    @Override
    public void setContentView(int layoutResID) {
        that.setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        that.setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        that.setContentView(view, params);
    }

    @Override
    public LayoutInflater getLayoutInflater() {
        return that.getLayoutInflater();
    }

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

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

    @Override
    public void attach(Activity activity) {
        that = activity;
    }

    @Override
    public ClassLoader getClassLoader() {
        return that.getClassLoader();
    }

    @Override
    public WindowManager getWindowManager() {
        return that.getWindowManager();
    }


    @Override
    public ApplicationInfo getApplicationInfo() {
        return that.getApplicationInfo();
    }

    @Override
    public void finish() {
        that.finish();
    }


    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onRestart() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onPause() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStop() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onSaveInstanceState(Bundle outState) {

    }

    public boolean onTouchEvent(MotionEvent event) {
        return false;
    }

    public void onBackPressed() {
        that.onBackPressed();
    }

    @Override
    public void startActivity(Intent intent) {
        that.startActivity(intent);
    }
}

3.3.4 实现效果

在这里插入图片描述

四、demo地址

download.csdn.net/download/li…