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
//配置selinux权限:allow untrusted_app app_data_file:file { execute }; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { //有存储权限 // 路径为 /storage/emulated/0/Android/data/com.example.insert/files String path = getExternalFilesDir(null).getPath()+"/plugin-debug.apk"; //插件模块apk存放位置 PluginManager.getInstance().loadPlugin( this, path); // 加载插件模块的apk文件 } else { //没有存储权限,申请权限 requestPermissions(new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100); } } public void onClick(View view) { //要跳转到plugin_core模块的代理Activity,首先要获取插件apk中的入口Activity类 Intent intent = new Intent(this, ProxyActivity.class); ActivityInfo[] activityInfos = PluginManager.getInstance().getmPackageInfo().activities; // 获取插件apk中的Activity数组信息 if (activityInfos.length > 0) { // 获取的插件包中的Activity不为空 , 才进行界面跳转 // 取插件apk中的第1个Activity,次序就是在AndroidManifest.xml清单文件中定义Activity组件的次序,因此必须将Launcher Activity定义在第一个位置,不能在Launcher Activity之前定义Activity组件 intent.putExtra("className", activityInfos[0].name); // 传入的是代理的目标组件的全类名,即插件apk中第一个activity的全类名 startActivity(intent); } } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.insert"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> <application android:requestLegacyExternalStorage="true" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Insert"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
2.plugin_core
定义一个Activity基类BaseActivity,继承AppCompatActivity并实现PluginActivityInterface接口。插件模块中的Activity类都继承该类,它是具体的Activity业务类的父类。
BaseActivity中涉及到的生命周期函数重复了,如AppCompatActivity中的onCreate()方法与PluginActivityInterface接口中的onCreate()方法是重复的,这里在每个方法前面加上@SuppressLint("MissingSuperCall")注解,忽略该报错。
public class BaseActivity extends AppCompatActivity implements PluginActivityInterface { private Activity proxyActivity; //注入的Activity,代理该Activity类作为上下文 @Override public void attach(Activity proxyActivity) { this.proxyActivity = proxyActivity; //注入代理Activity,在PluginActivity中将代理Activity组件注入进来 } @Override public void setContentView(int layoutResID) { // 调用代理Activity中的setContentView方法 if (proxyActivity != null) { proxyActivity.setContentView( layoutResID); }else{ super.setContentView(layoutResID); } } @Override public void setContentView(View view) { if (proxyActivity != null) { proxyActivity.setContentView(view); }else{ super.setContentView(view); } } @Override public <T extends View> T findViewById(int id){ if (proxyActivity != null) { return proxyActivity.findViewById(id); }else{ return super.findViewById(id); } } @Override public void startActivity(Intent intent) { if (proxyActivity != null) { intent.putExtra("className", intent.getComponent().getClassName()); proxyActivity.startActivity(intent); }else{ super.startActivity(intent); } } @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 onPause() { } @SuppressLint("MissingSuperCall") @Override public void onStop() { } @SuppressLint("MissingSuperCall") @Override public void onDestroy() { } @SuppressLint("MissingSuperCall") @Override public void onSaveInstanceState(Bundle outState) { } }
public interface PluginActivityInterface { //绑定代理Activity void attach(Activity proxyActivity); void onCreate(Bundle savedInstanceState); void onStart(); void onResume(); void onPause(); void onStop(); void onDestroy(); void onSaveInstanceState(Bundle outState); boolean onTouchEvent(MotionEvent event); void onBackPressed(); }
public class PluginManager { private DexClassLoader mDexClassLoader;//类加载器,用于加载插件包apk中的classes.dex文件中的字节码对象 private Resources mResources; //从插件包apk中加载的资源 private PackageInfo mPackageInfo; //插件包信息类 private Context mContext; //加载插件的上下文对象 private static PluginManager instance; //获取单例类 public static PluginManager getInstance(){ if (instance == null) { instance = new PluginManager(); } return instance; } // 加载插件,参数分别为加载插件的app的上下文、加载的插件包地址 public void loadPlugin(Context context, String loadPath) { this.mContext = context; //DexClassLoader的optimizedDirectory目录必须是私有,即模式为Context.MODE_PRIVATE File optimizedDirectory = context.getDir( "cache_plugin", Context.MODE_PRIVATE); Log.e("TAG",loadPath); mDexClassLoader = new DexClassLoader( loadPath, optimizedDirectory.getAbsolutePath(), null, context.getClassLoader()); // 创建类加载器 // 加载资源 try { AssetManager assetManager = AssetManager.class.newInstance(); //通过反射创建AssetManager Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class); //通过反射获取AssetManager中的addAssetPath隐藏方法 addAssetPathMethod.invoke( assetManager, loadPath); //调用反射方法 mResources = new Resources( assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration()); //获取资源 mPackageInfo = context.getPackageManager().getPackageArchiveInfo(loadPath, PackageManager.GET_ACTIVITIES); // 获取插件包中的Activity类信息 } catch (Exception e) { e.printStackTrace(); } } //获取类加载器 public DexClassLoader getmDexClassLoader(){ return mDexClassLoader; } //获取插件包中的Package信息 public PackageInfo getmPackageInfo() { return mPackageInfo; } //获取插件包中的资源 public Resources getmResources() { return mResources; } }
在宿主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了。
//该Activity只是个空壳,主要用于持有从插件apk加载的Activity类,并在ProxyActivity生命周期方法中调用对应PluginActivity类的生命周期方法 public class ProxyActivity extends Activity { private String className = ""; //被代理的目标Activity组件的全类名 private PluginActivityInterface pluginActivity;//插件包中的Activity界面组件 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_proxy); className = getIntent().getStringExtra("className"); //得到要代理的目标组件的全类名,即插件apk中的入口activity的全类名 try { Class<?> clazz = getClassLoader().loadClass( className); //使用类加载器加载插件中的界面组件。注意这里类加载器必须是PluginManager中的类加载器,即getClassLoader()是重写的方法,该方法返回PluginManager中的类加载器 Activity activity = (Activity) clazz.newInstance(); //使用反射创建插件apk中的Activity if (activity instanceof PluginActivityInterface) { // 判断Activity组件是否是PluginActivityInterface接口类型的 this.pluginActivity = (PluginActivityInterface) activity; // 如果是PluginActivityInterface类型 , 则强转为该类型 pluginActivity.attach( this); // 上下文注入。将ProxyActivity绑定注入到插件包的PluginActivity类中。该ProxyActivity具有运行的上下文,一旦绑定注入成功,则被代理的PluginActivity也具有了上下文 pluginActivity.onCreate(savedInstanceState); //调用pluginActivity的onCreate生命周期 } } catch (Exception e) { e.printStackTrace(); } } @Override protected void onStart() { super.onStart(); pluginActivity.onStart(); } @Override protected void onResume() { super.onResume(); pluginActivity.onResume(); } @Override protected void onPause() { super.onPause(); pluginActivity.onPause(); } @Override protected void onStop() { super.onStop(); pluginActivity.onStop(); } @Override protected void onDestroy() { super.onDestroy(); pluginActivity.onDestroy(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); pluginActivity.onSaveInstanceState(outState); } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); return pluginActivity.onTouchEvent(event); } @Override public void onBackPressed() { //super.onBackPressed(); //pluginActivity.onBackPressed(); finish(); } //重写getClassLoader方法,因为这里需要使用插件包加载的类加载器 @Override public ClassLoader getClassLoader() { return PluginManager.getInstance().getmDexClassLoader(); } //重写getResources方法,因为这里需要使用插件包加载的资源 @Override public Resources getResources() { return PluginManager.getInstance().getmResources(); } }
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.plugin_core"> <application> <activity android:name=".ProxyActivity"/> </application> </manifest>
3.plugin
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.plugin"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Insert"> <activity android:name=".PluginActivity" android:exported="true"> </activity> </application> </manifest>
所有的插件包中的Activity都要继承 BaseActivity。
这样写的目的是为了方便在代理Activity中可以随意调用插件apk中的Activity类的生命周期函数,这些生命周期函数都是protected方法,不能直接调用,否则每个方法调用时,还要先反射修改访问性,才能调用
public class PluginActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
代码完成,接下来看如何运行:
①首先编译插件模块,生成插件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中。