Android 插件化“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 插件化“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )相关的知识,希望对你有一定的参考价值。
android 插件化系列文章目录
【Android 插件化】插件化简介 ( 组件化与插件化 )
【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 )
【Android 插件化】插件化原理 ( 类加载器 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 原理与实现思路 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 注入上下文的使用 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )
【Android 插件化】“ 插桩式 “ 插件化框架 ( 运行应用 | 代码整理 )
文章目录
前言
上一篇博客 【Android 插件化】“ 插桩式 “ 插件化框架 ( 注入上下文的使用 ) 中对注入的上下文进行了相关代理操作 , 插件包中的界面组件 PluginActivity 凡是涉及上下文的操作 , 都统一委托给注入的上下文来完成 ;
一、" 宿主 " 模块启动 " 插件 " 模块过程
1、" 插件 " 模块类名获取
加载 " 插件 " 模块 apk 安装包 :
拷贝插件包 : 应用启动后 , 先将插件包 apk 文件从 assets 目录拷贝到 getExternalFilesDir(null) 目录中 ;
加载插件包 : 使用 PluginManager 加载插件包 ;
/*
加载 " 插件 " 模块的 apk 文件
先将该插件包拷贝到
*/
String path = getExternalFilesDir(null).getAbsolutePath() + "/plugin-debug.apk";
PluginManager.getInstance().loadPlugin(this, path);
获取 " 插件 " 模块中的 Activity 数组信息 :
// 获取 " 插件 " 模块中的 Activity 数组信息
ActivityInfo[] activityInfos = PluginManager.getInstance().getmPackageInfo().activities;
获取插件包信息 : 其中的 loadPath , 就是 getExternalFilesDir(null).getAbsolutePath() + "/plugin-debug.apk"
路径 ;
/**
* 插件化框架核心类
*/
public class PluginManager {
/**
* 插件包信息类
*/
private PackageInfo mPackageInfo;
/**
* 加载插件
* @param context 加载插件的应用的上下文
* @param loadPath 加载的插件包地址
*/
public void loadPlugin(Context context, String loadPath) {
this.mContext = context;
// 获取插件包中的 Activity 类信息
mPackageInfo = context.getPackageManager().getPackageArchiveInfo(
loadPath, // 加载的插件包路径
PackageManager.GET_ACTIVITIES); // 获取的包信息类名
}
/**
* 获取插件包中的 Package 信息
* @return
*/
public PackageInfo getmPackageInfo() {
return mPackageInfo;
}
}
获取要启动的 Activity 全类名 : 获取插件包中的 Launcher Activity 的全类名 , 并封装到 Intent 中 , 启动 Activity ;
public void onClick(View view) {
// 跳转到 plugin_core 模块的代理 Activity
// 首先要获取 " 插件 " 模块中的入口 Activity 类
Intent intent = new Intent(this, ProxyActivity.class);
// 获取 " 插件 " 模块中的 Activity 数组信息
ActivityInfo[] activityInfos = PluginManager.getInstance().getmPackageInfo().activities;
// 获取的插件包中的 Activity 不为空 , 才进行界面跳转
if (activityInfos.length > 0) {
// 这里取插件包中的第 0 个 Activity
// 次序就是在 AndroidManifest.xml 清单文件中定义 Activity 组件的次序
// 必须将 Launcher Activity 定义在第一个位置
// 不能在 Launcher Activity 之前定义 Activity 组件
// 传入的是代理的目标组件的全类名
intent.putExtra("CLASS_NAME", activityInfos[0]);
startActivity(intent);
}
}
特别注意在插件包的 AndroidManifest.xml 清单文件中定义 Activity 组件的次序 , Launcher Activity 必须定义为底 1 1 1 个 ;
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="kim.hsl.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.Plugin">
<activity android:name=".PluginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
2、" 插件 " 模块类加载器获取
拿到了插件包中要启动 Activity 的全类名 , 在 ProxyActivity 中 , 就可以根据该 Activity 全类名 , 使用 从 PluginManager 中获取的 类加载器 , 使用 反射 加载该全类名对应的 Activity 类 , 并使用反射初始化该类 ;
/**
* 该 Activity 只是个空壳 ;
* 主要用于持有从 apk 加载的 Activity 类
* 并在 ProxyActivity 声明周期方法中调用对应 PluginActivity 类的生命周期方法
*/
public class ProxyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_proxy);
// 要代理的目标组件的全类名
className = getIntent().getStringExtra("CLASS_NAME");
// 注意此处的 ClassLoader 类加载器必须是插件管理器中的类加载器
try {
// 使用类加载器加载插件中的界面组件
Class<?> clazz = getClassLoader().loadClass(className);
// 使用反射创建插件界面组件 Activity
Activity activity = (Activity) clazz.newInstance();
// 判断 Activity 组件是否是 PluginActivityInterface 接口类型的
if (activity instanceof PluginActivityInterface){
// 如果是 PluginActivityInterface 类型 , 则强转为该类型
this.pluginActivity = (PluginActivityInterface) activity;
// 上下文注入
// 将该 ProxyActivity 绑定注入到 插件包的 PluginActivity 类中
// 该 PluginActivity 具有运行的上下文
// 一旦绑定注入成功 , 则被代理的 PluginActivity 也具有了上下文
pluginActivity.attach(this);
// 调用
pluginActivity.onCreate(savedInstanceState);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
注意 ProxyActivity 的 getClassLoader 必须被覆盖 , 否则 使用 " 宿主 " 模块的类加载器 无法查找到 " 插件 " 模块安装包中的字节码类 ;
/**
* 该 Activity 只是个空壳 ;
* 主要用于持有从 apk 加载的 Activity 类
* 并在 ProxyActivity 声明周期方法中调用对应 PluginActivity 类的生命周期方法
*/
public class ProxyActivity extends AppCompatActivity {
/**
* 需要使用插件包加载的类加载器
* @return
*/
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance().getmDexClassLoader();
}
}
插件包类加载器是通过创建 DexClassLoader 获得的 , 需要传入插件包中的 dex 字节码类 ;
/**
* 插件化框架核心类
*/
public class PluginManager {
/**
* 类加载器
* 用于加载插件包 apk 中的 classes.dex 文件中的字节码对象
*/
private DexClassLoader mDexClassLoader;
/**
* 加载插件
* @param context 加载插件的应用的上下文
* @param loadPath 加载的插件包地址
*/
public void loadPlugin(Context context, String loadPath) {
this.mContext = context;
// DexClassLoader 的 optimizedDirectory 操作目录必须是私有的
// ( 模式必须是 Context.MODE_PRIVATE )
File optimizedDirectory = context.getDir("plugin", Context.MODE_PRIVATE);
// 创建 DexClassLoader
mDexClassLoader = new DexClassLoader(
loadPath, // 加载路径
optimizedDirectory.getAbsolutePath(), // apk 解压缓存目录
null,
context.getClassLoader() // DexClassLoader 加载器的父类加载器
);
}
/**
* 获取类加载器
* @return
*/
public DexClassLoader getmDexClassLoader() {
return mDexClassLoader;
}
}
二、" 宿主 " 模块加载 " 插件 " 模块中的资源文件
在 " 宿主 " 模块中 , 使用 Resources 是无法获取到 " 插件 " 模块中的资源文件的 , 在使用 " 插件 " 模块中的资源文件之前 , 必须先加载其中的资源文件 ;
/**
* 该 Activity 只是个空壳 ;
* 主要用于持有从 apk 加载的 Activity 类
* 并在 ProxyActivity 声明周期方法中调用对应 PluginActivity 类的生命周期方法
*/
public class ProxyActivity extends AppCompatActivity {
/**
* 需要使用插件包中加载的资源
* @return
*/
@Override
public Resources getResources() {
return PluginManager.getInstance().getmResources();
}
}
插件包中的 Resources 加载过程如下 :
首先 , 通过反射创建 AssetManager ,
然后 , 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法 ,
再后 , 调用 AssetManager 的 addAssetPath 方法 ,
最后 , 通过 AssetManager 创建 Resources 对象 ;
/**
* 插件化框架核心类
*/
public class PluginManager {
/**
* 加载插件
* @param context 加载插件的应用的上下文
* @param loadPath 加载的插件包地址
*/
public void loadPlugin(Context context, String loadPath) {
this.mContext = context;
// 加载资源
try {
// 通过反射创建 AssetManager
AssetManager assetManager = AssetManager.class.newInstance();
// 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
Method addAssetPathMethod = assetManager.
getClass().
getDeclaredMethod("addAssetPath");
// 调用反射方法
addAssetPathMethod.invoke(assetManager, loadPath);
// 获取资源
mResources = new Resources(
assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration()
);
} catch (IllegalAccessException e) {
// 调用 AssetManager.class.newInstance() 反射构造方法异常
e.printStackTrace();
} catch (InstantiationException e) {
// 调用 AssetManager.class.newInstance() 反射构造方法异常
e.printStackTrace();
} catch (NoSuchMethodException e) {
// getDeclaredMethod 反射方法异常
e.printStackTrace();
} catch (InvocationTargetException e) {
// invoke 执行反射方法异常
e.printStackTrace();
}
}
/**
* 获取插件包中的资源
* @return
*/
public Resources getmResources() {
return mResources;
}
}
三、博客资源
博客资源 :
- GitHub : https://github.com/han1202012/Plugin
以上是关于Android 插件化“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )的主要内容,如果未能解决你的问题,请参考以下文章
Android 插件化“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )
Android 插件化“ 插桩式 “ 插件化框架 ( 代理 Activity 组件开发 )
Android 插件化“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )
Android 插件化“ 插桩式 “ 插件化框架 ( 原理与实现思路 )