• Android 插件化


    demo

    如果要加载插件模块编译的apk插件包中的Activity类,需要执行如下流程:

    1)加载类对象:使用DexClassLoader加载Activity对应的Class字节码类对象;

    2)管理生命周期:处理加载进来的Activity类的生命周期,创建ProxyActivity,通过其生命周期回调,管理插件包中加载的未纳入应用管理的组件Activity类;

    3)注入上下文:为加载进来的Activity类注入上下文;

    4)加载资源:使用AssetManager将插件包apk中的资源主动加载进来。
     

    1.创建工程

    app为宿主app 

    plugin 为插件app

    plugin_core为插件化的核心加载文件

    2.app

    1. //配置selinux权限:allow untrusted_app app_data_file:file { execute };
    2. public class MainActivity extends AppCompatActivity {
    3. @Override
    4. protected void onCreate(Bundle savedInstanceState) {
    5. super.onCreate(savedInstanceState);
    6. setContentView(R.layout.activity_main);
    7. if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    8. && checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { //有存储权限
    9. // 路径为 /storage/emulated/0/Android/data/com.example.insert/files
    10. String path = getExternalFilesDir(null).getPath()+"/plugin-debug.apk"; //插件模块apk存放位置
    11. PluginManager.getInstance().loadPlugin( this, path); // 加载插件模块的apk文件
    12. } else { //没有存储权限,申请权限
    13. requestPermissions(new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
    14. }
    15. }
    16. public void onClick(View view) {
    17. //要跳转到plugin_core模块的代理Activity,首先要获取插件apk中的入口Activity类
    18. Intent intent = new Intent(this, ProxyActivity.class);
    19. ActivityInfo[] activityInfos = PluginManager.getInstance().getmPackageInfo().activities; // 获取插件apk中的Activity数组信息
    20. if (activityInfos.length > 0) { // 获取的插件包中的Activity不为空 , 才进行界面跳转
    21. // 取插件apk中的第1个Activity,次序就是在AndroidManifest.xml清单文件中定义Activity组件的次序,因此必须将Launcher Activity定义在第一个位置,不能在Launcher Activity之前定义Activity组件
    22. intent.putExtra("className", activityInfos[0].name); // 传入的是代理的目标组件的全类名,即插件apk中第一个activity的全类名
    23. startActivity(intent);
    24. }
    25. }
    26. }
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    3. package="com.example.insert">
    4. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    5. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    6. <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
    7. <application
    8. android:requestLegacyExternalStorage="true"
    9. android:allowBackup="true"
    10. android:icon="@mipmap/ic_launcher"
    11. android:label="@string/app_name"
    12. android:roundIcon="@mipmap/ic_launcher_round"
    13. android:supportsRtl="true"
    14. android:theme="@style/Theme.Insert">
    15. <activity
    16. android:name=".MainActivity"
    17. android:exported="true">
    18. <intent-filter>
    19. <action android:name="android.intent.action.MAIN" />
    20. <category android:name="android.intent.category.LAUNCHER" />
    21. </intent-filter>
    22. </activity>
    23. </application>
    24. </manifest>

    2.plugin_core

    定义一个Activity基类BaseActivity,继承AppCompatActivity并实现PluginActivityInterface接口。插件模块中的Activity类都继承该类,它是具体的Activity业务类的父类。

    BaseActivity中涉及到的生命周期函数重复了,如AppCompatActivity中的onCreate()方法与PluginActivityInterface接口中的onCreate()方法是重复的,这里在每个方法前面加上@SuppressLint("MissingSuperCall")注解,忽略该报错。
     

    1. public class BaseActivity extends AppCompatActivity implements PluginActivityInterface {
    2. private Activity proxyActivity; //注入的Activity,代理该Activity类作为上下文
    3. @Override
    4. public void attach(Activity proxyActivity) {
    5. this.proxyActivity = proxyActivity; //注入代理Activity,在PluginActivity中将代理Activity组件注入进来
    6. }
    7. @Override
    8. public void setContentView(int layoutResID) {
    9. // 调用代理Activity中的setContentView方法
    10. if (proxyActivity != null) {
    11. proxyActivity.setContentView( layoutResID);
    12. }else{
    13. super.setContentView(layoutResID);
    14. }
    15. }
    16. @Override
    17. public void setContentView(View view) {
    18. if (proxyActivity != null) {
    19. proxyActivity.setContentView(view);
    20. }else{
    21. super.setContentView(view);
    22. }
    23. }
    24. @Override
    25. public extends View> T findViewById(int id){
    26. if (proxyActivity != null) {
    27. return proxyActivity.findViewById(id);
    28. }else{
    29. return super.findViewById(id);
    30. }
    31. }
    32. @Override
    33. public void startActivity(Intent intent) {
    34. if (proxyActivity != null) {
    35. intent.putExtra("className", intent.getComponent().getClassName());
    36. proxyActivity.startActivity(intent);
    37. }else{
    38. super.startActivity(intent);
    39. }
    40. }
    41. @SuppressLint("MissingSuperCall")
    42. @Override
    43. public void onCreate(Bundle savedInstanceState) {
    44. }
    45. @SuppressLint("MissingSuperCall")
    46. @Override
    47. public void onStart() {
    48. }
    49. @SuppressLint("MissingSuperCall")
    50. @Override
    51. public void onResume() {
    52. }
    53. @SuppressLint("MissingSuperCall")
    54. @Override
    55. public void onPause() {
    56. }
    57. @SuppressLint("MissingSuperCall")
    58. @Override
    59. public void onStop() {
    60. }
    61. @SuppressLint("MissingSuperCall")
    62. @Override
    63. public void onDestroy() {
    64. }
    65. @SuppressLint("MissingSuperCall")
    66. @Override
    67. public void onSaveInstanceState(Bundle outState) {
    68. }
    69. }
    1. public interface PluginActivityInterface {
    2. //绑定代理Activity
    3. void attach(Activity proxyActivity);
    4. void onCreate(Bundle savedInstanceState);
    5. void onStart();
    6. void onResume();
    7. void onPause();
    8. void onStop();
    9. void onDestroy();
    10. void onSaveInstanceState(Bundle outState);
    11. boolean onTouchEvent(MotionEvent event);
    12. void onBackPressed();
    13. }
    1. public class PluginManager {
    2. private DexClassLoader mDexClassLoader;//类加载器,用于加载插件包apk中的classes.dex文件中的字节码对象
    3. private Resources mResources; //从插件包apk中加载的资源
    4. private PackageInfo mPackageInfo; //插件包信息类
    5. private Context mContext; //加载插件的上下文对象
    6. private static PluginManager instance;
    7. //获取单例类
    8. public static PluginManager getInstance(){
    9. if (instance == null) {
    10. instance = new PluginManager();
    11. }
    12. return instance;
    13. }
    14. // 加载插件,参数分别为加载插件的app的上下文、加载的插件包地址
    15. public void loadPlugin(Context context, String loadPath) {
    16. this.mContext = context;
    17. //DexClassLoader的optimizedDirectory目录必须是私有,即模式为Context.MODE_PRIVATE
    18. File optimizedDirectory = context.getDir( "cache_plugin", Context.MODE_PRIVATE);
    19. Log.e("TAG",loadPath);
    20. mDexClassLoader = new DexClassLoader( loadPath, optimizedDirectory.getAbsolutePath(), null, context.getClassLoader()); // 创建类加载器
    21. // 加载资源
    22. try {
    23. AssetManager assetManager = AssetManager.class.newInstance(); //通过反射创建AssetManager
    24. Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class); //通过反射获取AssetManager中的addAssetPath隐藏方法
    25. addAssetPathMethod.invoke( assetManager, loadPath); //调用反射方法
    26. mResources = new Resources( assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration()); //获取资源
    27. mPackageInfo = context.getPackageManager().getPackageArchiveInfo(loadPath, PackageManager.GET_ACTIVITIES); // 获取插件包中的Activity类信息
    28. } catch (Exception e) {
    29. e.printStackTrace();
    30. }
    31. }
    32. //获取类加载器
    33. public DexClassLoader getmDexClassLoader(){
    34. return mDexClassLoader;
    35. }
    36. //获取插件包中的Package信息
    37. public PackageInfo getmPackageInfo() {
    38. return mPackageInfo;
    39. }
    40. //获取插件包中的资源
    41. public Resources getmResources() {
    42. return mResources;
    43. }
    44. }

     

    在宿主apk中需要跳转到插件apk时,首先创建一个跳转到ProxyActivity(在核心库里)的intent,在intent中加上插件apk的入口activity。这样ProxyActivity作为代理Activity ,它持有从插件apk中加载的PluginActivity类对象。

    ProxyActivity是空的Activity,没有任何实际的业务逻辑,只是作为一个生命周期的转接代理接口。但是ProxyActivity有着完整的生命周期回调机制,在进入该界面时会回调onCreate、onStart、onResume生命周期方法,在退出该界面时会回调 onPause、onStop、onDestroy生命周期方法。因此只需要在ProxyActivity的生命周期方法中,调用PluginActivity相应的生命周期方法即可。而且ProxyActivity运行时会有上下文,PluginActivity使用上下文时调用ProxyActivity的上下文。
    注意:插件模块的包名可以和宿主的包名一样也可以不一样。当插件apk的包名和宿主一样时,要注意插件apk里的Activity名字一定不要和宿主apk中Activity的名字一样,否则包名和Activity名都一样就无法跳转到插件Activity了。

    1. //该Activity只是个空壳,主要用于持有从插件apk加载的Activity类,并在ProxyActivity生命周期方法中调用对应PluginActivity类的生命周期方法
    2. public class ProxyActivity extends Activity {
    3. private String className = ""; //被代理的目标Activity组件的全类名
    4. private PluginActivityInterface pluginActivity;//插件包中的Activity界面组件
    5. @Override
    6. protected void onCreate(Bundle savedInstanceState) {
    7. super.onCreate(savedInstanceState);
    8. setContentView(R.layout.activity_proxy);
    9. className = getIntent().getStringExtra("className"); //得到要代理的目标组件的全类名,即插件apk中的入口activity的全类名
    10. try {
    11. Class clazz = getClassLoader().loadClass(
    12. className); //使用类加载器加载插件中的界面组件。注意这里类加载器必须是PluginManager中的类加载器,即getClassLoader()是重写的方法,该方法返回PluginManager中的类加载器
    13. Activity activity = (Activity) clazz.newInstance(); //使用反射创建插件apk中的Activity
    14. if (activity instanceof PluginActivityInterface) { // 判断Activity组件是否是PluginActivityInterface接口类型的
    15. this.pluginActivity =
    16. (PluginActivityInterface) activity; // 如果是PluginActivityInterface类型 , 则强转为该类型
    17. pluginActivity.attach(
    18. this); // 上下文注入。将ProxyActivity绑定注入到插件包的PluginActivity类中。该ProxyActivity具有运行的上下文,一旦绑定注入成功,则被代理的PluginActivity也具有了上下文
    19. pluginActivity.onCreate(savedInstanceState); //调用pluginActivity的onCreate生命周期
    20. }
    21. } catch (Exception e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. @Override
    26. protected void onStart() {
    27. super.onStart();
    28. pluginActivity.onStart();
    29. }
    30. @Override
    31. protected void onResume() {
    32. super.onResume();
    33. pluginActivity.onResume();
    34. }
    35. @Override
    36. protected void onPause() {
    37. super.onPause();
    38. pluginActivity.onPause();
    39. }
    40. @Override
    41. protected void onStop() {
    42. super.onStop();
    43. pluginActivity.onStop();
    44. }
    45. @Override
    46. protected void onDestroy() {
    47. super.onDestroy();
    48. pluginActivity.onDestroy();
    49. }
    50. @Override
    51. protected void onSaveInstanceState(Bundle outState) {
    52. super.onSaveInstanceState(outState);
    53. pluginActivity.onSaveInstanceState(outState);
    54. }
    55. @Override
    56. public boolean onTouchEvent(MotionEvent event) {
    57. super.onTouchEvent(event);
    58. return pluginActivity.onTouchEvent(event);
    59. }
    60. @Override
    61. public void onBackPressed() {
    62. //super.onBackPressed();
    63. //pluginActivity.onBackPressed();
    64. finish();
    65. }
    66. //重写getClassLoader方法,因为这里需要使用插件包加载的类加载器
    67. @Override
    68. public ClassLoader getClassLoader() {
    69. return PluginManager.getInstance().getmDexClassLoader();
    70. }
    71. //重写getResources方法,因为这里需要使用插件包加载的资源
    72. @Override
    73. public Resources getResources() {
    74. return PluginManager.getInstance().getmResources();
    75. }
    76. }

     

    1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    2. package="com.example.plugin_core">
    3. <application>
    4. <activity android:name=".ProxyActivity"/>
    5. </application>
    6. </manifest>

    3.plugin

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    3. package="com.example.plugin">
    4. <application
    5. android:allowBackup="true"
    6. android:icon="@mipmap/ic_launcher"
    7. android:label="@string/app_name"
    8. android:roundIcon="@mipmap/ic_launcher_round"
    9. android:supportsRtl="true"
    10. android:theme="@style/Theme.Insert">
    11. <activity
    12. android:name=".PluginActivity"
    13. android:exported="true">
    14. </activity>
    15. </application>
    16. </manifest>

     

    所有的插件包中的Activity都要继承 BaseActivity。

    这样写的目的是为了方便在代理Activity中可以随意调用插件apk中的Activity类的生命周期函数,这些生命周期函数都是protected方法,不能直接调用,否则每个方法调用时,还要先反射修改访问性,才能调用
     

    1. public class PluginActivity extends BaseActivity {
    2. @Override
    3. public void onCreate(Bundle savedInstanceState) {
    4. super.onCreate(savedInstanceState);
    5. setContentView(R.layout.activity_main);
    6. }
    7. }

    代码完成,接下来看如何运行:

    ①首先编译插件模块,生成插件apk安装包:

    ②拷贝插件apk

    将编译的插件包apk拷贝到 /storage/emulated/0/Android/data/com.example.insert/files目录中。在 " Device FIle Explorer " 面板中 , 右键点击/storage/emulated/0/DCIM目录 , 点击"Upload " 即可将apk上传到指定位置。

    ③运行宿主模块,在onCreate方法中就会加载解析插件apk,解析dex文件与资源文件。此时点击跳转按钮,即可跳转到插件模块Activity中。
     

  • 相关阅读:
    【神印王座】两位新老婆登场!龙皓晨神勇救美,采儿却大发醋意!
    systemctl教程(systemctl命令)(systemd)(service命令)
    MYSQL进阶
    前端 js面试题(二)
    小程序使用uni.createAnimation只执行一次的问题
    Allegro批量剪断走线操作
    迅为RK3568开发板驱动指南第六篇-平台总线
    ES6汇总
    向着趋势奔跑:银行客户中心转型,重构商业模式是关键
    构建高效问题解答平台:使用Cpolar和Tipas在Ubuntu上搭建专属问答网站
  • 原文地址:https://blog.csdn.net/xiaowang_lj/article/details/128081889