Android插件化1-插桩式-Activity插件化

Android插件化1-插桩式-Activity插件化

Android插件化2-插桩式-Service插件化

Android插件化3-插桩式-动态广播插件化

GitHub:https://github.com/345166018/AndroidPlugin

文章目录

  • 1 插件化概述
    • 1.1 动态加载技术
    • 1.2 动态加载技术作用
  • 2 插桩式原理
  • 3 Activity插件化
  • 4 Activity插件化实现
    • 4.1 pluginstand
    • 4.2 插件taopiaopiao
    • 4.3 宿主 app
      • 4.3.1 获取插件
      • 4.3.2 创建ProxyActivity

1 插件化概述

1.1 动态加载技术

动态加载技术分为:

  1. 插件化
  2. 热修复

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

1.2 动态加载技术作用

插件化的作用:主要用于解决应用越来越庞大以及功能模块的解耦,所以小项目中一般用的不多。业务复杂,模块的解耦。应用的接入。65536限制,内存占用大。

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

2 插桩式原理

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

Android插件化1-插桩式-Activity插件化_第1张图片

图:代理思想


Android插件化1-插桩式-Activity插件化_第2张图片

图:宿主与插件的关系

3 Activity插件化

Activity 是 App 中使用频率最高的组件,各种插件化框架的主要精力都放在 Activity 上。
Activity 的插件化需要解决 3 方面的技术问题:

  1. 宿主 App 可以加载插件 App 中类 。
  2. 宿主 App 可以加载插件 App 中的资源 。
  3. 宿主 App 可以加载插件中的 Activity 。

4 Activity插件化实现

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

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

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

Android插件化1-插桩式-Activity插件化_第3张图片

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

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

4.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方法实现在插件中跳转到另一个页面。

4.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);
    }

下面是具体实现。

4.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;
    }
}

4.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中。

GitHub

你可能感兴趣的:(01,Android架构设计,#,Android插件化,android,插件化)