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