文章目录
Android 插件化系列文章目录
前言
一、" 宿主 " 模块启动 " 插件 " 模块过程
1、" 插件 " 模块类名获取
2、" 插件 " 模块类加载器获取
二、" 宿主 " 模块加载 " 插件 " 模块中的资源文件
三、博客资源
前言
上一篇博客 【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 11 个 ;
<?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