Android插件化1-Activity插件化

1 插件化概述

插件化属于动态加载技术
动态加载技术:插件化、热修复

动态加载技术:在应用程序运行时,动态加载一些程序中原本不存在的可执行文件并运行这些文件里的代码逻辑,可执行文件总的来说分为两个,一个是动态链接库so,另一种是dex相关文件(dex文件包含jar/apk文件)。

插件化的作用:主要用于解决应用越来越庞大以及功能模块的解耦,所以小项目中一般用的不多。

热修复:主要用于修复bug。

业务复杂,模块的解耦。
应用的接入。
65536限制,内存占用大。

2 插桩式原理与实现

在宿主 App 中使用反射加载插件中的类 A, A 是没有生命周期的,就是一个普普通通的类而已 。比如插件里的 Activity , 就算我们“欺骗了” AMS 检查 AndroidManifest 的过程,这个Activity 也启动不了,类似 onResume 、 onPaused 这些生命周期函数都不能被正常调用,因为宿主 App 根本就不把它当作 Activity 来对待 。为此,我们在宿主 App 中设计一个代理类 ProxyActivity ,这是一个 Activity ,是宿主 App所认识的 。 让 ProxyActivity 内部有一个对插件 ActivityA 的引用,让ProxyActivity 的任何生命周期函数都调用 ActivityA 中同名函数。

在这里插入图片描述

图:代理思想


在这里插入图片描述

图:宿主与插件的关系

新建Android项目,并创建两个Module:

  • Module1:pluginstand,是一个Android Library;
  • Module2:taopiaopiao,是一个Phone Module。

将pluginstand作为依赖库分别添加到app Module和taopiaopiao Module中。

在这里插入图片描述

因为插件(tiaopiaopiao)只是一个apk,并没有安装到手机上,不具有生命周期,这就需要通过获取宿主(app)的生命周期。

2.1 pluginstand

在pluginstand中创建一个接口PayInterfaceActivity,包含了各个生命周期的方法,如下:

public interface PayInterfaceActivity {
    void attach(Activity proxyActivity);
    void onCreate(Bundle saveInstanceState);
    void onStart();
    void onResume();
    void onPause();
    void onStop();
    void onDestroy();
    void onSaveInstanceState(Bundle outState);
    boolean onTouchEvent(MotionEvent event);
    void onBackPressed();
}

2.2 插件taopiaopiao

在taopiaopiao中创建一个基类BaseActivity实现PayInterfaceActivity接口,如下:

public class BaseActivity extends Activity implements PayInterfaceActivity {
    protected Activity that;

    @Override
    public void attach(Activity proxyActivity) {//1
        this.that = proxyActivity;
    }
    @Override
    public void setContentView(View view) {
        if(that != null){
            that.setContentView(view);
        } else {
            super.setContentView(view);
        }
    }
    @Override
    public void setContentView(int layoutResID) {
        if(that != null){
            that.setContentView(layoutResID);
        } else {
            super.setContentView(layoutResID);
        }
    }
    @Override
    public void startActivity(Intent intent) {
        Intent m  = new Intent();
        m.putExtra("className",intent.getComponent().getClassName());
        that.startActivity(m);
    }
    @Override
    public View findViewById(int id) {
        return that.findViewById(id);
    }
    @Override
    public void onCreate(Bundle saveInstanceState) {

    }
    @Override
    public void onStart() {

    }
    @Override
    public void onResume() {

    }
    @Override
    public void onPause() {

    }
    @Override
    public void onStop() {

    }
    @Override
    public void onDestroy() {

    }
    @Override
    public void onSaveInstanceState(Bundle outState) {

    }
}

注释1:通过attach方法,将宿主(app)的ProxyActivity传入


插件taopiaopiao中的主页面TaoMainActivity,如下:

public class TaoMainActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {//1
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//2
        findViewById(R.id.img).setOnClickListener(new View.OnClickListener() {//3
            @Override
            public void onClick(View v) {
                Toast.makeText(that,"插件",Toast.LENGTH_SHORT).show();
                startActivity(new Intent(that, SceondActivity.class));//4
            }
        });
    }
}

注释1:覆写了BaseActivity中的onCreate方法。
注释2:调用BaseActivity的setContentView方法。
注释3:调用BaseActivity的findViewById方法。
注释4:调用BaseActivity的startActivity方法实现在插件中跳转到另一个页面。

2.3 宿主 app

在MainActivity有两个按钮,一个用于加载插件,一个用于跳转到插件的页面

public void load(View view) {
        PluginManager.getInstance().loadPath(this);
    }
  public void click(View view) {
        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
        startActivity(intent);
    }

下面是具体实现。

2.3.1 获取插件

将taopiaopiao中生成的apk拷贝到data/data/com.hongx.plugin/app_plugin

在这里插入图片描述

创建PluginManager用于加载apk,并获取Resources、DexClassLoader和PackageInfo,如下:

public class PluginManager {
    
    private static final PluginManager ourInstance = new PluginManager();
    private DexClassLoader dexClassLoader;
    private Resources resources;
    private PackageInfo packageInfo;
    
    public static PluginManager getInstance(){
        return ourInstance;
    }

    public PluginManager() {
    }
    
    public void loadPath(Context context){
        File filesDir = context.getDir("plugin", Context.MODE_PRIVATE);
        String name = "plugin.apk";
        String path = new File(filesDir, name).getAbsolutePath();

        PackageManager packageManager = context.getPackageManager();
        packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);

        //activity
        ///data/data/com.hongx.plugin/app_dex/plugin.dex
        File dex = context.getDir("dex", Context.MODE_PRIVATE);
        dexClassLoader = new DexClassLoader(path, dex.getAbsolutePath(), null, context.getClassLoader());

        //resource
        try {
            AssetManager manager = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            //path = /data/data/com.hongx.plugin/app_plugin/plugin.apk
            addAssetPath.invoke(manager, path);
            resources = new Resources(manager,
                    context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Resources getResources(){
        return  resources;
    }

    public DexClassLoader getDexClassLoader(){
        return dexClassLoader;
    }

    public PackageInfo getPackageInfo(){
        return packageInfo;
    }
}

2.3.2 创建ProxyActivity

public class ProxyActivity extends Activity {
    //需要加载插件的全类名
    private String className;
    PayInterfaceActivity payInterfaceActivity;

    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        className = getIntent().getStringExtra("className");//1

        try {
            //TaoMainActivity
            Class<?> aClass = getClassLoader().loadClass(className);
            Constructor constructor = aClass.getConstructor(new Class[]{});
            Object in = constructor.newInstance(new Object[]{});//2
            payInterfaceActivity = (PayInterfaceActivity) in;
            payInterfaceActivity.attach(this);//3

            //如果需要参数,可以使用Bundle
            Bundle bundle = new Bundle();
            payInterfaceActivity.onCreate(bundle);//4
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className", className);
        super.startActivity(intent1);
    }

    //重写加载类
    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getDexClassLoader();
    }

    //重写加载资源
    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources();
    }

    @Override
    protected void onStart() {
        super.onStart();
        payInterfaceActivity.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        payInterfaceActivity.onResume();
    }

    @Override
    protected void onStop() {
        super.onStop();
        payInterfaceActivity.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        payInterfaceActivity.onDestroy();
    }
}

注释1:在上面已经提到点击页面跳转按钮跳转至插件的TaoMainActivity。PluginManager.getInstance().getPackageInfo()得到的是插件的包信息,activities[0]对应的就是ToMainActivity,也就是获取到ToMainActivity的类名并将类名传递到ProxyActivity中以供使用。

注释2:通过类加载器和反射机制获取到了TaoMainActivity对象。

注释3:将ProxyActivity传递到TaoMainActivity中。

注释4:调用了TaoMainActivity的onCreate方法。


再来分析下TaoMainActivity:

public class TaoMainActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//1
        findViewById(R.id.img).setOnClickListener(new View.OnClickListener() {//2
            @Override
            public void onClick(View v) {
                Toast.makeText(that,"插件",Toast.LENGTH_SHORT).show();
                startActivity(new Intent(that, SceondActivity.class));
            }
        });
    }
}
  • 注释1:调用的是BaseActivity中的setContentView方法,其实最终调用的是ProxyActivity的setContentView方法,代码如下:
  @Override
    public void setContentView(int layoutResID) {
        if(that != null){
            that.setContentView(layoutResID);
        } else {
            super.setContentView(layoutResID);
        }
    }
  • 注释2:跳转到taopiaopiao的SceondActivity

BaseActivity的startActivity方法如下:

    @Override
    public void startActivity(Intent intent) {
        Intent m  = new Intent();
        m.putExtra("className",intent.getComponent().getClassName());
        that.startActivity(m);
    }

最后还是使用了ProxyActivity的startActivity方法,如下:

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className", className);
        super.startActivity(intent1);
    }

className就是SecondActivity的类名,流程和加载TaoMainActivity一样,也是将类名传递到ProxyActivity中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章