什么是插件化
也可以说插件化是有什么效果。
我们平时开发当中的app,主apk,称之为宿主APP。
下载下来的APK,称之为插件app
了解使用反射
反射耗性能,那么是耗在什么地方,我们能放心使用吗?
1、产生大量的临时对象。
2、检查可见性需要多耗性能。
3、当大量反射—至少也得上千,会编译成机器码(jvm会把部分大量使用的代码编译成机器码直接插入到底层运行,加快运行),这部分代码是没有经过优化。
4、类型转换,大量的类型装箱拆箱。
一般而言,反射该用还是得用,我们也不会用到那么庞大,注意项目混淆的问题即可。
类加载器
在之前的一篇文章中我就介绍过相关信息:虚拟机与类加载器
PathClassLoader 和 DexClassLoader
在8.0(API 26)之前,它们二者的唯一区别是 第二个参数 optimizedDirectory,这个参数的意 思是生成的 odex(优化的dex)存放的路径。 在8.0(API 26)及之后,二者就完全一样了。
1、避免重复加载,当父加载器已经加载了该类的 时候,就没有必要子ClassLoader再加载一次。
2、安全性考虑,防止核心API库被随意篡改。
bootClassLoader的findClass最终调用ClassForName是native方法我们也不再跟下去了。
parent找不到才会自己找findClass。
classLoader里面的findClass没有实现,直接抛了个异常。
看它的子类BaseDexClassLoader里面的实现:
用的PathList里面的findClass
element的findClass
dexFile就是dex文件的抽象类
一个Element 代表这一个dex文件,整个apk所有的文件都在Element[]数组里面。dexElement
1、获取宿主的dexElement
2、获取插件的dexElement
3、合并两个dexElement
4、将新的dexElement赋值到宿主dexElement
经过上面四部,就能将插件的类全部加载了。
说到这里热修复其实也是差不多同样的操作,将插件的dex放前面加载。
目标:dexElements --> DexPathList类的对象 --> BaseDexClassLoader的对象,类加载器
Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = clazz.getDeclaredField("pathList");
pathListField.setAccessible(true);
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
// 宿主的 类加载器
ClassLoader pathClassLoader = context.getClassLoader();
// DexPathList类的对象
Object hostPathList = pathListField.get(pathClassLoader);
// 宿主的 dexElements
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
// 插件的 类加载器
ClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(),
null, pathClassLoader);
// DexPathList类的对象
Object pluginPathList = pathListField.get(dexClassLoader);
// 插件的 dexElements
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
// 宿主dexElements = 宿主dexElements + 插件dexElements
// Object[] obj = new Object[]; // 不行
// 创建一个新数组
Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),
hostDexElements.length + pluginDexElements.length);
System.arraycopy(hostDexElements, 0, newDexElements,
0, hostDexElements.length);
System.arraycopy(pluginDexElements, 0, newDexElements,
hostDexElements.length, pluginDexElements.length);
// 赋值
// hostDexElements = newDexElements
dexElementsField.set(hostPathList, newDexElements);
怎么启动四大组件
普通类的启动我们直接new对象即可,但是四大组件的启动就不行了,它必须是在清单文件中注册过才行。类似activity这种我们不仅需要它像一个普通类那样启动,我们还需要它的生命周期等,所以必须要在清单文件中注册。
APK的安装是通过PackageManagerService的扫描安装,所有的信息都被该服务持有,但是AMS是我们服务的大管家,实际上我们大部分的操作都是通过binder跨进程到AMS,然后AMS根据需要调用其他的服务来验证的,四大组件的启动也是同样的流程。
思路:我们需要在宿主APP的清单文件中注册一个对应的四大组件,例如注册一个Activity,在发送给AMS检测前Hook一下,变成该Activity,用来通过给AMS检测通过,当它返回的时候我们再改成启动我们需要的对应组件,这就需要我们对四大组件的启动流程熟悉了。
四大组件的启动都差不多,我们就Activity的启动为例。
找Hook点
查找Hook点的原则:
1、尽量静态变量或者单例对象
2、尽量Hook public的对象和方法
好获取,不容易改变,如果一个对象被实例化了多个,确认是哪个就比较麻烦了。
首先我们不可能夸进程去hook AMS,而且不是替换完对象就完事,Activity的启动不是一次性买卖,其他正常的启动我们不能替换,那么就需要类似观察者模式这样的回调,不仅可以让我知道启动了Activity且我们还需要拿到启动时候传递过去的参数,我们需要动态代理,既有回调也有传递的参数。
我们知道app进程跟AMS是通过binder夸进程通讯的,两边都有同样的接口实现类,直接找到这里。
我们启动一个Activity是通过Intent传递信息,最终都会走到这里交给Instrumentation.ActivityResul
跟进execStartActivity方法
public void startActivityForResult(
String who, Intent intent, int requestCode, @Nullable Bundle options) {
Uri referrer = onProvideReferrer();
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, who,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, who, requestCode,
ar.getResultCode(), ar.getResultData());
}
cancelInputsAndStartExitTransition(options);
}
最终来到:ActivityTaskManager.getService().startActivity
try {
intent.migrateExtraStreamToClipData(who);
intent.prepareToLeaveProcess(who);
int result = ActivityTaskManager.getService().startActivity(whoThread,
who.getBasePackageName(), who.getAttributionTag(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()), token,
target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
ActivityTaskManager.getService()是实现类,我们需要动态代理它的接口,并反射替换它,那么每次执行该对象的方法都会回调到我们动态代理的回调方法里。
public static IActivityTaskManager getService() {
return IActivityTaskManagerSingleton.get();
}
ActivityTaskManager.getService()对象实现了IActivityTaskManager
它是通过IActivityTaskManagerSingleton的get方法获取
private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
new Singleton<IActivityTaskManager>() {
@Override
protected IActivityTaskManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
return IActivityTaskManager.Stub.asInterface(b);
}
};
IActivityTaskManagerSingleton 是Singleton
我们看下Singleton
public abstract class Singleton<T> {
@UnsupportedAppUsage
public Singleton() {
}
@UnsupportedAppUsage
private T mInstance;
protected abstract T create();
@UnsupportedAppUsage
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
IActivityTaskManager可以通过Singleton来修改
那么接下来的代码需要代理IActivityTaskManager接口,并修改它的实现类 private T mInstance;
try {
// 获取 singleton 对象
Field singletonField = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // 小于8.0
Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
singletonField = clazz.getDeclaredField("gDefault");
} else {
Class<?> clazz = Class.forName("android.app.ActivityManager");
singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
}
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
// 获取 系统的 IActivityManager 对象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstance = mInstanceField.get(singleton);
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
// 创建动态代理对象
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// do something
// Intent的修改 -- 过滤
/**
* IActivityManager类的方法
* startActivity(whoThread, who.getBasePackageName(), intent,
* intent.resolveTypeIfNeeded(who.getContentResolver()),
* token, target != null ? target.mEmbeddedID : null,
* requestCode, 0, null, options)
*/
// 过滤
if ("startActivity".equals(method.getName())) {
int index = -1;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 启动插件的
Intent intent = (Intent) args[index];
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.enjoy.leo_plugin",
"com.enjoy.leo_plugin.ProxyActivity");
proxyIntent.putExtra(TARGET_INTENT, intent);
args[index] = proxyIntent;
}
// args method需要的参数 --- 不改变原有的执行流程
// mInstance 系统的 IActivityManager 对象
return method.invoke(mInstance, args);
}
});
// ActivityManager.getService() 替换成 proxyInstance
mInstanceField.set(singleton, proxyInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
8.0之前case是100,后面变成了159。
前面的几个方法调用有兴趣的朋友可以去跟下,这里就不介绍了,每个版本的代码都有些差异。
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}
从AMS出来到ActivityThread这里就容易找多了。
所有的信息都会跑到sendMessage,然后调用ActivityThread.H
class H extends Handler
随便看下几个case的处理:
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
类似:AppBindData data = (AppBindData)msg.obj;,将Object强转转为对应的类型
8.0之前直接是ActivityClientRecord里面的intent
之后是在mActivityCallbacks
H 是一个Handler,它直接new对象出来的,然后重写了handleMessage
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在handleMessage(msg);之前还会根据mCallback 是否为空进行
if (mCallback.handleMessage(msg)) {
return;
}
这个Callback 是个接口,有一个唯一的实现类,这简直天生符合我们的需求,能动态代理。
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
但是实际上我们在这里不需要动态代理,检查 H 这个类的创建,里面没有实现Callback,也就是说它是空的对象,我们直接创建一个给它即可。
关键代码:
try {
// 获取 ActivityThread 类的 Class 对象
Class<?> clazz = Class.forName("android.app.ActivityThread");
// 获取 ActivityThread 对象
Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(null);
// 获取 mH 对象
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(activityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// 创建的 callback
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
// 通过msg 可以拿到 Intent,可以换回执行插件的Intent
// 找到 Intent的方便替换的地方 --- 在这个类里面 ActivityClientRecord --- Intent intent 非静态
// msg.obj == ActivityClientRecord
switch (msg.what) {
case 100:
try {
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
// 启动代理Intent
Intent proxyIntent = (Intent) intentField.get(msg.obj);
// 启动插件的 Intent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
intentField.set(msg.obj, intent);
}
} catch (Exception e) {
e.printStackTrace();
}
break;
case 159:
try {
// 获取 mActivityCallbacks 对象
Field mActivityCallbacksField = msg.obj.getClass()
.getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
for (int i = 0; i < mActivityCallbacks.size(); i++) {
if (mActivityCallbacks.get(i).getClass().getName()
.equals("android.app.servertransaction.LaunchActivityItem")) {
Object launchActivityItem = mActivityCallbacks.get(i);
// 获取启动代理的 Intent
Field mIntentField = launchActivityItem.getClass()
.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);
// 目标 intent 替换 proxyIntent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
mIntentField.set(launchActivityItem, intent);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
// 必须 return false
return false;
}
};
// 替换系统的 callBack
mCallbackField.set(mH, callback);
} catch (Exception e) {
e.printStackTrace();
}
资源加载
raw : Android会自动的为这目录中的所有资源文件生成一个ID,这意味着很容易就可以访问到这个资源,甚至在xml 中都是可以访问的,使用ID访问速度是最快的。
assets : 不会生成ID,只能通过AssetManager访问,xml中不能访问,访问速度会慢些,不过操作更加方便。
AssetManager相关的这里就不再细讲了,在前面有说明:插件化换肤思路
private final static String apkPath = "/sdcard/plugin-debug.apk";
private static Resources mResources;
public static Resources getResources(Context context) {
if (mResources == null) {
mResources = loadResource(context);
}
return mResources;
}
private static Resources loadResource(Context context) {
// assets.addAssetPath(key.mResDir)
try {
AssetManager assetManager = AssetManager.class.newInstance();
// 让 这个 AssetManager对象 加载的 资源为插件的
Method addAssetPathMethod = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPathMethod.invoke(assetManager, apkPath);
// 如果传入的是Activity的 context 会不断循环,导致崩溃
Resources resources = context.getResources();
// 加载插件的资源的 resources
return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
在BaseActivity里面调用使用,但是还有一个问题,我们插件中如果是继承自AppCompatActivity,那么启动的时候也会需要一些资源,这些资源存在跟宿主APP的资源冲突的可能。
所以我们需要宿主的context里面的资源是加载我们上面getResources
在baseActivity的onCreate里
Resources resources = LoadUtil.getResources(getApplication());
mContext = new ContextThemeWrapper(getBaseContext(), 0);
Class<? extends Context> clazz = mContext.getClass();
try {
Field mResourcesField = clazz.getDeclaredField("mResources");
mResourcesField.setAccessible(true);
mResourcesField.set(mContext, resources);
} catch (Exception e) {
e.printStackTrace();
}
对应宿主Activity实例化的时候必须是使用宿主中我们自己设置的context:
View view = LayoutInflater.from(mContext).inflate(R.layout.activity_main, null);
setContentView(view);
大概了解插件化的原理,扩展知识面,自己写一个插件化工具出来明显要关注的点会非常多,每个版本的代码差异都需要研究,目前市面开源能直接用的也很多,了解对应的原理对我们也有好处。