1、前言
这篇文章来讲一下Android插件化的原理和大概的一个运行流程,最后将一个demo呈现出来。
2、分析
插件说到底就是一个apk文件,我们要做的事情是从宿主中加载该apk文件的类对象(比如启动Activity)和使用该apk文件的资源等操作。我们知道系统是不会安装apk插件的,所以宿主是不知道我们的插件的任何信息。我们之前分析了Activity的启动过程,其实就是在ActivityThread的performLaunchActivity方法中创建了Activity对象,现在再看一下其中的逻辑:
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
activity的创建是使用反射的方式进行的,而ClassLoader是由r.packageInfo提供的,这里的r.packageInfo是一个LoadedApk对象,LoadedApk对象存储了Apk文件的相关信息,而该对象的赋值是在H类的handleMessage中:
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
可以看到r.packageInfo(LoadedApk对象)是通过getPackageInfoNoCheck()方法获取的,我们进入到getPackageInfoNoCheck()方法中:
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
它又调用了getPackageInfo方法:
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
WeakReference ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
? mBoundApplication.processName : null)
+ ")");
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
if (mSystemThread && "android".equals(aInfo.packageName)) {
packageInfo.installSystemApplicationInfo(aInfo,
getSystemContext().mPackageInfo.getClassLoader());
}
if (differentUser) {
// Caching not supported across users
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference(packageInfo));
}
}
return packageInfo;
}
}
它会从mPackages的这样的一个ArrayMap中去获取,如果获取到则返回,没有获取到则new了一个LoadedApk对象,然后将其存入到mPackages中。
所以实现插件化我们可以这样做(方法一):
由于插件的加载需要一个ClassLoader,而这个ClassLoader是自定义的,并且从上面的源码可以看到系统的ClassLoader是从LoadedApk对象中获取的,所以我们需要一个自定义的LoadedApk对象,从上面的源码可知,LoadedApk对象是从mPackages这样的一个ArrayMap中获取的,所以我们可以将我们自定义的LoadedApk对象存入到这个mPackages这个map中,key为插件的packagename,这样在要获取LoadedApk对象时就直接从map中返回了,然后得到的是我们自定义的LoadedApk对象,然后从该对象中得到自定义的ClassLoader对象,从而使用该ClassLoader可以加载外部插件了。
这种方法是可行的,但是过程比较复杂。因为在上面的过程中需要hook住比较多的系统类和方法,并且在构建一个LoadedApk对象时,还需要一个ApplicationInfo对象,该对象需要解析插件的Manifest文件获取,所以我们还需要手动去解析Manifest文件,这就增加了该方法的复杂程度。
那是否还有其他方法来实现插件化呢(方法二)?从Dex的加载过程这篇文章中可以看到,类的加载最终是调用了DexPathList的findClass方法,而在DexPathList中维护了一份Element类型的dexElement数组,这个数组存放的就是dex文件的信息,所以我们可以将插件的dex文件信息也存放到该数组中,然后在加载插件类的时候,自然就能够从插件的dex文件中进行加载插件的类。
这种方法相比于上述的第一种方法,显然是比较简洁并且便捷。但是也是存在着问题的。对于四大组件中的Activity、Service、ContentProvider是需要在Manifest注册的,否则会报错误异常。从Activity的启动过程这篇文章可以看出,我们可以hook住startActivity这个方法,使用一个占坑的Activity在Manifest中注册,从而解决问题。原理是这样的:
首先在启动Activity的时候,我们将要启动的Activity替换为占坑的Activity,用这个占坑的Activity去进行系统的合法性验证,当验证通过的时候,在生成Activity对象时,再次hook住Activity的创建方法,将真正要启动的Activity替换回来,让系统创建一个真正需要启动的Activity对象。
3、实现
根据上述的分析,我们选择第二种方法进行demo实现,下面详细介绍实现的步骤:
3.1 加载插件apk
首先需要创建一个自定义的ClassLoader去加载外部插件,而加载外部插件的ClassLoader类型必须是DexClassLoader,具体实现如下:
//插件的外部路径
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "plugin_demo.apk";
//插件优化后的路径
String cachePath = getCacheDir().getAbsolutePath();
//自定义的ClassLoader
DexClassLoader pluginLoader = new DexClassLoader(apkPath, cachePath, cachePath, getClassLoader());
3.2 合并宿主和插件的Element[]数组
拿到宿主的ClassLoader,通过ClassLoader获取pathList,通过pathList获取Element[]数组;拿到插件的ClassLoader,插件和宿主做同样的操作获取Element[]数组,然后将这两个数组合并,最后将合并的Element[]数组设置给宿主的pathList。
public static void mergePathFileElement(ClassLoader pluginLoader) {
//拿到宿主的ClassLoader
ClassLoader originPathLoader = MainApplication.getContext().getClassLoader();
//获取宿主的pathList
Object originPathList = getPathList(originPathLoader);
//获取插件的pathList
Object pluginPathList = getPathList(pluginLoader);
//获取宿主的Element[]数组
Object originElements = getElement(originPathList);
//获取插件的Element[]数组
Object pluginElements = getElement(pluginPathList);
//将宿主和插件的Element[]数组进行合并
Object combineElements = combineElements(originElements, pluginElements);
//将合并的Elements设置给宿主的pathList
setDexElements(originPathList, combineElements);
}
上面的getPathList和getElement都是通过反射获取的,我们主要看一下数组合并的逻辑:
private static Object combineElements(Object originElements, Object pluginElements) {
Class> arrayType = originElements.getClass().getComponentType();
int originLength = Array.getLength(originElements);
int pluginLength = Array.getLength(pluginElements);
int lengths = originLength + pluginLength;
Object newArray = Array.newInstance(arrayType, lengths);
for (int i = 0; i < lengths; i++) {
if (i < originLength) {
Array.set(newArray, i, Array.get(originElements, i));
} else {
Array.set(newArray, i, Array.get(pluginElements, i - originLength));
}
}
return newArray;
}
合并的逻辑也很简单,首先通过反射获取数组的类型并使用Array.newInstance()新建一个数组,然后将原有的两个数组设置到新建的那个数组中。
3.3 代理系统启动Activity的方法
首先我们从Activity的启动过程这篇文章可以看出,Activity的启动最终会走到Instrumentation类的execStartActivity方法,在该方法中调用了ActivityManager.getService().startActivity方法,我们看一下这里的逻辑:
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
ActivityManager.getService()方法返回了一个IActivityManager对象,而该对象是通过IActivityManagerSingleton.get()产生的,我们再看一下IActivityManagerSingleton.get()方法:
private static final Singleton IActivityManagerSingleton =
new Singleton() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
public abstract class Singleton {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
IActivityManagerSingleton是一个Singleton对象,get()方法返回了的是mInstance对象,而该对象是通过create()方法生成的,从create()方法中可以看到生成了一个IActivityManager类型的对象,该对象是一个远程代理类。
因此我们要hook住startActivity方法,那么我们要生成一个IActivityManager对象的一个代理类,因此我们需要获取现有的IActivityManager对象,也就是说要获取ActivityManager.getService()方法返回的对象。
Class> activityManagerClass = Class.forName("android.app.ActivityManager");
Field singletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
Object singletonValue = singletonField.get(null);
//singletonValue是一个 android.util.Singleton对象;取出这个单例里面的字段
Class> singletonClass = Class.forName("android.util.Singleton");
Field mInstance = singletonClass.getDeclaredField("mInstance");
mInstance.setAccessible(true);
//取出了AMS代理对象,这里的AMS代理对象就是singletonValue对象的值
Object iActivityManager = mInstance.get(singletonValue);
Class> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
, new Class>[]{iActivityManagerInterface}, new IActivityManagerHandler(iActivityManager));
//将singletonValue对象的值(即上面的iActivityManager对象)设置为(AMS代理对象的)代理对象的值
mInstance.set(singletonValue, proxy);
利用反射的原理,获取了ActivityManager.getService()方法返回的对象,然后利用该对象创建了一个代理对象,最后将这个代理对象设置给ActivityManager.getService()方法返回的对象。
3.4 iActivityManager对象的代理对象
public class IActivityManagerHandler implements InvocationHandler {
private Object iActivityManagerHandler;
public IActivityManagerHandler(Object iActivityManagerHandler) {
this.iActivityManagerHandler = iActivityManagerHandler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("startActivity")) {
Log.d("ABC", "startActivity 被拦截了");
Intent rawIntent = null;
int intentIndex = 0;
// 找到参数里面的第一个Intent对象
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
rawIntent = (Intent) args[i];
intentIndex = i;
break;
}
}
String packageName = MainApplication.getContext().getPackageName();
//创建一个新的Intent
Intent newIntent = new Intent();
if (rawIntent != null) {
//把启动的Activity替换为占坑Activity
ComponentName componentName = new ComponentName(packageName, PitActivity.class.getName());
newIntent.setComponent(componentName);
//将我们真正要启动的Activity保存起来
newIntent.putExtra(ActivityHookHelper.RAW_INTENT, rawIntent);
//替换掉intent,已达到欺骗系统的作用
args[intentIndex] = newIntent;
Log.d("ABC", "startActivity hook 成功");
}
return method.invoke(iActivityManagerHandler, args);
}
return method.invoke(iActivityManagerHandler, args);
}
}
创建一个iActivityManager对象的代理对象,并拦截startActivity方法,在其内部将要启动的Activity替换成占坑的Activity。
3.5 拦截创建Activity的方法
在系统检查完毕后,在创建Activity对象的时候,需要再次拦截创建Activity的方法,并创建真正要启动的Activity对象。
系统检查完毕后,会回调ActivityThread里的scheduleLaunchActivity方法,这个方法发送了一个消息到ActivityThread的内部类H里,而H类是一个Handler,如下所示:
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
而Handler是通过dispatchMessage(Message msg)方法来分发消息的,我们先来看一下其中逻辑:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到如果Handler设置了mCallback对象的话,则会走mCallback对象的handleMessage方法,而且如果mCallback对象的handleMessage方法返回为true则不会走到下面的handleMessage方法。我们再看一下H类,可以看到它并没有设置mCallback对象,因此我们可以利用反射给它设置一个mCallback对象,并在handleMessage方法中处理LAUNCH_ACTIVITY(msg.what = 100)的情况:
public static void hookHandler() {
try {
Class> activityThreadClass = Class.forName("android.app.ActivityThread");
//ActivityThread有一个静态方法返回了自己,这里可以获取activityThread对象
Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
Object activityThread = currentActivityThread.invoke(null);
Field mH = activityThreadClass.getDeclaredField("mH");
mH.setAccessible(true);
Handler mHValue = (Handler) mH.get(activityThread); //获取activityThread对象中mH变量的值,其中mH的类型是Handler类型
Field callback = Handler.class.getDeclaredField("mCallback");
callback.setAccessible(true);
callback.set(mHValue, new ActivityCallbackHandler(mHValue)); //将一个自定义的Callback设置给Handler
} catch (Exception e) {
e.printStackTrace();
}
}
上面就是利用反射给ActivityThread对象的内部类H设置了一个mCallback对象,其mCallback对象如下:
public class ActivityCallbackHandler implements Handler.Callback {
private Handler handler;
private int launchActivity = -1;
public ActivityCallbackHandler(Handler handler) {
this.handler = handler;
try {
Class> innerClass = Class.forName("android.app.ActivityThread$H");
Field field = innerClass.getDeclaredField("LAUNCH_ACTIVITY");
field.setAccessible(true);
launchActivity = (int) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == launchActivity) {
handleLaunchActivity(msg);
}
handler.handleMessage(msg);
return true;
}
private void handleLaunchActivity(Message msg) {
Object activityClientRecord = msg.obj;
try {
Field intent = activityClientRecord.getClass().getDeclaredField("intent");
intent.setAccessible(true);
Intent intentValue = (Intent) intent.get(activityClientRecord);
Intent rawIntent = intentValue.getParcelableExtra(ActivityHookHelper.RAW_INTENT);
if (rawIntent != null) {
intentValue.setComponent(rawIntent.getComponent());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
在msg.what = LAUNCH_ACTIVITY时,msg.obj是一个ActivityClientRecord类型,其中有一个字段是intent,该intent存储的信息就是我们要启动Activity时的一些信息,所以在该intent中拿到真正需要启动的Intent(之前通过putExtra存起来了),然后改变当前的intent的component,将其设置为真正要启动的intent的component,已达到创建真正启动类的结果。
4、总结
经过了上述的分步骤实现,已经可以将一个插件的Activity启动起来了。具体的demo可以在github上下载到:
宿主:https://github.com/hwldzh/pluginTestDemo
插件:https://github.com/hwldzh/plugin