文章目录
Android 插件化系列文章目录
一、编译 " 插件 " 模块
二、首次编译运行 " 宿主 " 模块
三、第二次运行 " 宿主 " 模块
四、" 宿主 " 模块代码
五、" 插件 " 模块代码
六、" 依赖库 " 模块代码
1、插件 Activity 接口
2、插件 Activity 基类
3、代理 Activity ( 桩 )
4、插件管理器
七、博客资源
一、编译 " 插件 " 模块
首先编译 " 插件 " 模块 , 生成 APK 安装包 ;
二、首次编译运行 " 宿主 " 模块
编译运行 " 宿主 " 模块 , 目的是为了生成 getExternalFilesDir(null).getAbsolutePath() 目录 ;
该目录在应用首次运行时自动生成 , 应用卸载后 , 自动删除 ;
该目录的绝对路径名称如下 :
/sdcard/Android/data/kim.hsl.plugin/files
拷贝插件包 : 将 上一章节 编译的插件包 apk 拷贝到 /sdcard/Android/data/kim.hsl.plugin/files 目录中 ;
在 " Device FIle Explorer " 面板中 , 右键点击 /sdcard/Android/data/kim.hsl.plugin/files 目录 , 点击 " Upload " ,
再弹出的对话框中 , 选择编译生成的 apk 安装包 , 上传到该目录中 ;
上传完成 ;
三、第二次运行 " 宿主 " 模块
第一次运行 " 宿主 " 模块 后 , 生成 /sdcard/Android/data/kim.hsl.plugin/files 目录 , 将 " 插件 " 模块编译后的插件包拷贝到该目录中 ;
第二次运行时 , 在 onCreate 方法中就会加载解析该插件包 , 解析 dex 文件与资源文件 ;
点击跳转按钮 , 即可跳转到插件模块 Activity 中 ;
四、" 宿主 " 模块代码
package kim.hsl.plugin; import androidx.appcompat.app.AppCompatActivity; import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Bundle; import android.util.Log; import android.view.View; import com.example.plugin_core.PluginManager; import com.example.plugin_core.ProxyActivity; import pub.devrel.easypermissions.EasyPermissions; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i(TAG, "getClassLoader() : " + getClassLoader()); Log.i(TAG, "getClassLoader().getParent() : " + getClassLoader().getParent()); Log.i(TAG, "getClassLoader().getParent().getParent() : " + getClassLoader().getParent().getParent()); EasyPermissions.requestPermissions( this, "权限申请原理对话框 : 描述申请权限的原理", 100, // 下面是要申请的权限 可变参数列表 Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ); /* 加载 " 插件 " 模块的 apk 文件 先将该插件包拷贝到 */ String path = getExternalFilesDir(null).getAbsolutePath() + "/plugin-debug.apk"; Log.i(TAG, "path : " + path); PluginManager.getInstance().loadPlugin(this, path); } 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) { Log.i(TAG, "CLASS_NAME : " + activityInfos[0].toString()); // 这里取插件包中的第 0 个 Activity // 次序就是在 AndroidManifest.xml 清单文件中定义 Activity 组件的次序 // 必须将 Launcher Activity 定义在第一个位置 // 不能在 Launcher Activity 之前定义 Activity 组件 // 传入的是代理的目标组件的全类名 intent.putExtra("CLASS_NAME", activityInfos[0].name); startActivity(intent); } } }
五、" 插件 " 模块代码
package kim.hsl.plugin; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import com.example.plugin_core.BaseActivity; public class PluginActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_plugin); } }
六、" 依赖库 " 模块代码
1、插件 Activity 接口
package com.example.plugin_core; import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; import androidx.annotation.NonNull; public interface PluginActivityInterface { /** * 绑定代理 Activity * @param proxyActivity */ 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(); }
2、插件 Activity 基类
package com.example.plugin_core; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import androidx.appcompat.app.AppCompatActivity; /** * " 插件 " 模块中的 Activity 类都继承该类 * 具体的 Activity 业务类的父类 */ public class BaseActivity extends AppCompatActivity implements PluginActivityInterface { /** * 注入的 Activity , 代理该 Activity 类作为上下文 */ private Activity proxyActivity; /** * 注入代理 Activity * 在 ProxyActivity 中将代理 Activity 组件注入进来 * @param proxyActivity */ @Override public void attach(Activity proxyActivity) { this.proxyActivity = proxyActivity; } @Override public void setContentView(int layoutResID) { // 调用代理 Activity 中的 setContentView 方法 if (proxyActivity != null) { proxyActivity.setContentView(layoutResID); }else{ super.setContentView(layoutResID); } } @Override public void setContentView(View view) { // 调用代理 Activity 中的 setContentView 方法 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) { } }
3、代理 Activity ( 桩 )
package com.example.plugin_core; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import dalvik.system.DexClassLoader; /** * 插件化框架核心类 */ public class PluginManager { /** * 类加载器 * 用于加载插件包 apk 中的 classes.dex 文件中的字节码对象 */ private DexClassLoader mDexClassLoader; /** * 从插件包 apk 中加载的资源 */ private Resources mResources; /** * 插件包信息类 */ private PackageInfo mPackageInfo; /** * 加载插件的上下文对象 */ private Context mContext; /** * PluginManager 单例 */ private static PluginManager instance; private PluginManager(){ } /** * 获取单例类 * @return */ public static PluginManager getInstance(){ if (instance == null) { instance = new PluginManager(); } return instance; } /** * 加载插件 * @param context 加载插件的应用的上下文 * @param loadPath 加载的插件包地址 */ public void loadPlugin(Context context, String loadPath) { this.mContext = context; // DexClassLoader 的 optimizedDirectory 操作目录必须是私有的 // ( 模式必须是 Context.MODE_PRIVATE ) File optimizedDirectory = context.getDir("cache_plugin", Context.MODE_PRIVATE); // 创建 DexClassLoader mDexClassLoader = new DexClassLoader( loadPath, // 加载路径 optimizedDirectory.getAbsolutePath(), // apk 解压缓存目录 null, context.getClassLoader() // DexClassLoader 加载器的父类加载器 ); // 加载资源 try { // 通过反射创建 AssetManager AssetManager assetManager = AssetManager.class.newInstance(); // 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法 Method addAssetPathMethod = assetManager. getClass(). getDeclaredMethod("addAssetPath", String.class); // 调用反射方法 addAssetPathMethod.invoke(assetManager, loadPath); // 获取资源 mResources = new Resources( assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration() ); // 获取插件包中的 Activity 类信息 mPackageInfo = context.getPackageManager().getPackageArchiveInfo( loadPath, // 加载的插件包路径 PackageManager.GET_ACTIVITIES); // 获取的包信息类名 } 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 DexClassLoader getmDexClassLoader() { return mDexClassLoader; } /** * 获取插件包中的 Package 信息 * @return */ public PackageInfo getmPackageInfo() { return mPackageInfo; } /** * 获取插件包中的资源 * @return */ public Resources getmResources() { return mResources; } }
4、插件管理器
package com.example.plugin_core; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import dalvik.system.DexClassLoader; /** * 插件化框架核心类 */ public class PluginManager { /** * 类加载器 * 用于加载插件包 apk 中的 classes.dex 文件中的字节码对象 */ private DexClassLoader mDexClassLoader; /** * 从插件包 apk 中加载的资源 */ private Resources mResources; /** * 插件包信息类 */ private PackageInfo mPackageInfo; /** * 加载插件的上下文对象 */ private Context mContext; /** * PluginManager 单例 */ private static PluginManager instance; private PluginManager(){ } /** * 获取单例类 * @return */ public static PluginManager getInstance(){ if (instance == null) { instance = new PluginManager(); } return instance; } /** * 加载插件 * @param context 加载插件的应用的上下文 * @param loadPath 加载的插件包地址 */ public void loadPlugin(Context context, String loadPath) { this.mContext = context; // DexClassLoader 的 optimizedDirectory 操作目录必须是私有的 // ( 模式必须是 Context.MODE_PRIVATE ) File optimizedDirectory = context.getDir("cache_plugin", Context.MODE_PRIVATE); // 创建 DexClassLoader mDexClassLoader = new DexClassLoader( loadPath, // 加载路径 optimizedDirectory.getAbsolutePath(), // apk 解压缓存目录 null, context.getClassLoader() // DexClassLoader 加载器的父类加载器 ); // 加载资源 try { // 通过反射创建 AssetManager AssetManager assetManager = AssetManager.class.newInstance(); // 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法 Method addAssetPathMethod = assetManager. getClass(). getDeclaredMethod("addAssetPath", String.class); // 调用反射方法 addAssetPathMethod.invoke(assetManager, loadPath); // 获取资源 mResources = new Resources( assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration() ); // 获取插件包中的 Activity 类信息 mPackageInfo = context.getPackageManager().getPackageArchiveInfo( loadPath, // 加载的插件包路径 PackageManager.GET_ACTIVITIES); // 获取的包信息类名 } 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 DexClassLoader getmDexClassLoader() { return mDexClassLoader; } /** * 获取插件包中的 Package 信息 * @return */ public PackageInfo getmPackageInfo() { return mPackageInfo; } /** * 获取插件包中的资源 * @return */ public Resources getmResources() { return mResources; } }
七、博客资源
博客资源 :
GitHub : https://github.com/han1202012/Plugin