日期:
来源:鸿洋收集编辑:小木箱
插件化同时能实现bug热修复,由于Davilk虚拟机存在,Java支持动态加载任意类。
插件化本质上是绕过Android系统的管控,让我们的APP自由打开、使用四大组件。
插件化是为了解决类加载和资源加载的问题,资源加载通过反射AssertManager,按照类加载划分,插件化分为静态代理和Hook两种方式,使用插件化是为了解决应用新版本覆盖慢问题。
四大组件可动态加载,意味着用户不需要手动安装新版本的应用,我们可以给用户提供新的功能和页面,或者在用户无感的情况下修复bug。
第一步: 创建 app 主工程作为宿主工程
第二步: 创建plugin_package作为插件工程,负责打插件包
第三步: 创建接口工程 lifecycle_manager ,负责管理四大组件的生命周期
第四步: 安装插件
4.1 把Assets里面的文件复制到/data/data/files目录下
public static void extractAssets(Context context, String sourceName) {
AssetManager am = context.getAssets();
InputStream is = null;
FileOutputStream fos = null;
try {
is = am.open(sourceName);
File extractFile = context.getFileStreamPath(sourceName);
fos = new FileOutputStream(extractFile);
byte[] buffer = new byte[1024];
int count = 0;
while ((count = is.read(buffer)) > 0) {
fos.write(buffer, 0, count);
}
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
closeSilently(is);
closeSilently(fos);
}
}
4.2 通过静态代理构建DexClassLoader
// 获取插件目录下的文件
File extractFile = mContext.getFileStreamPath(mApkName);
// 获取插件包路径
String dexPath = extractFile.getPath();
// 创建Dex输出路径
File fileRelease = mContext.getDir("dex", Context.MODE_PRIVATE);
// 构建 DexClassLoader 生成目录
mPluginClassLoader = new DexClassLoader(dexPath,
fileRelease.getAbsolutePath(), null, mContext.getClassLoader());
4.3 通过反射 AssertManager 实现资源加载
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method method = AssetManager.class.getMethod("addAssetPath", String.class);
method.invoke(assetManager, dexPath);
mPluginResources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(),
mContext.getResources().getConfiguration());
} catch (Exception e) {
Toast.makeText(mContext, "加载 Plugin 失败", Toast.LENGTH_SHORT).show();
}
第五步: 解析插件
public void parserApkAction() {
try {
Class packageParserClass = Class.forName("android.content.pm.PackageParser");
Object packageParser = packageParserClass.newInstance();
Method method = packageParserClass.getMethod("parsePackage", File.class, int.class);
File extractFile = mContext.getFileStreamPath(mApkName);
Object packageObject = method.invoke(packageParser, extractFile, PackageManager.GET_RECEIVERS);
Field receiversFields = packageObject.getClass().getDeclaredField("receivers");
ArrayList arrayList = (ArrayList) receiversFields.get(packageObject);
Class packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Class userHandleClass = Class.forName("android.os.UserHandle");
int userId = (int) userHandleClass.getMethod("getCallingUserId").invoke(null);
for (Object activity : arrayList) {
Class component = Class.forName("android.content.pm.PackageParser$Component");
Field intents = component.getDeclaredField("intents");
// 1.获取 Intent-Filter
ArrayList<IntentFilter> intentFilterList = (ArrayList<IntentFilter>) intents.get(activity);
// 2.需要获取到广播的全类名,通过 ActivityInfo 获取
// ActivityInfo generateActivityInfo(Activity a, int flags, PackageUserState state, int userId)
Method generateActivityInfoMethod = packageParserClass
.getMethod("generateActivityInfo", activity.getClass(), int.class,
packageUserStateClass, int.class);
ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, activity, 0,
packageUserStateClass.newInstance(), userId);
Class broadcastReceiverClass = getClassLoader().loadClass(activityInfo.name);
BroadcastReceiver broadcastReceiver = (BroadcastReceiver) broadcastReceiverClass.newInstance();
for (IntentFilter intentFilter : intentFilterList) {
mContext.registerReceiver(broadcastReceiver, intentFilter);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
第六步,代理 Activity: 在 lifecycle_mananager 构建 ActivityInterface 负责管理插件 Activity 生命周期
public interface ActivityInterface {
// 插入Activity上下文
void insertAppContext(Activity hostActivity);
// Activity各个生命周期方法
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
}
第七步,代理 Activity: 在 plugin_package 构建 BaseActivity 实现 ActivityInterface
public void startActivity(Intent intent) {
Intent newIntent = new Intent();
newIntent.putExtra("ext_class_name", intent.getComponent().getClassName());
mHostActivity.startActivity(newIntent);
}
第八步,代理 Activity: 在 plugin_package 构建 Activity 插件
public class PluginActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_start).setOnClickListener(
v -> startActivity(new Intent(mHostActivity, TestActivity.class))
);
}
}
// 测试插件Activity
public class TestActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
第九步: 启动插件的入口 Activity
// PorxyActivity
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 获取到真正要启动的插件 Activity,然后执行 onCreate 方法
String className = getIntent().getStringExtra(EXT_CLASS_NAME);
try {
Class clazz = getClassLoader().loadClass(className);
ActivityInterface activityInterface = (ActivityInterface) clazz.newInstance();
// 注册宿主的 Context
activityInterface.insertAppContext(this);
activityInterface.onCreate(savedInstanceState);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra(EXT_CLASS_NAME);
Intent proxyIntent = new Intent(this, ProxyActivity.class);
proxyIntent.putExtra(EXT_CLASS_NAME, className);
super.startActivity(proxyIntent);
}
public View findViewById(int layoutId) {
return mHostActivity.findViewById(layoutId);
}
public void setContentView(int resId) {
mHostActivity.setContentView(resId);
}
使用 DexClassLoader 加载插件的 Apk。 通过代理的 Activity 去执行插件中的 Activity,加载对应的生命周期。 通过反射调用 AssetManager 的 addAssetPath 来加载插件中的资源。
1. 找到的 Activity 不在插件包里面
解决方案
2. 资源 Id 类型不匹配 找不到
解决方案
3. 插件包 leakcanary 引发的崩溃
解决方案
宿主和所有插件都依赖 leakcanary 即可。
本文主要是根据我自身实际投产的 插件组件化 实践,分享一些动态加载 SDK 插件 时需要考虑的问题。内容主要包括插件化方案的共同问题、插件包 leakcanary 引发的崩溃、资源 Id 类型不匹配 、宿主 Activity 找不到问题,千言万语汇成一句话:
参考链接
沐小晨曦
https://github.com/Omooo/VirtualApplication
VirtualApk 插件化
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!