湖畔镇

Android插件化

现在的应用功能越来越多,代码膨胀,应用体积太大,为解耦模块方便开发,插件化有其必要性

插件化

背景

应用功能越来越多,代码膨胀,触及65535方法数的上限,应用体积太大

实时更新独立模块的需求越来越强

功能模块的解耦,方便维护

H5和Hybrid可以解决一些问题,但是体验比不上Native代码,因此插件化有其必要性

代理

代理可以用来进行方法增强或拦截

静态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface IAct {
void act();
}

class Agent implements IAct {
IAct base;

class Agent(IActor agent) {
this.base = agent;
}

void act() {
// 干点啥
base.act();
}
}

class Actor implements IAct {
void book() {
...
}
}

动态代理

传统的静态代理模式需要为每一个需要代理的类写一个代理类,比较麻烦,为了更优雅地实现代理模式,JDK提供了动态代理方式,可以简单理解为JVM可以在运行时帮我们动态生成一系列的代理类

1
2
IAct impl = new Actor();
IAct proxy = (IAct) Proxy.newProxyInstance(IAct.class.getClassLoader(), new Class[] { IAct.class }, new ActHandler(impl));

动态代理主要处理InvocationHandlerProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
class ActHandler implements InvocationHandler {
IAct base;

public ActHandler(ActImpl impl) {
this.base = impl;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 干点啥
return method.invoke(base);
}
}

Hook

通过创建代理对象替代原始对象,进行功能增强,称为Hook,以startActivity()为例

找到被Hook的对象,称为Hook点,静态变量和单例比较适合,因为相对不容易变化

Context.startActivity()实际调用了ActivityThread类的mInstrumentationexecStartActivity() 方法,ActivityThread是主线程,一个程序只有一个,因此是个良好的Hook点

mInstrumentation替换成代理对象,首先需要获得主线程对象,可以通过ActivityThread.currentActivityThread()获得,ActivityThread是一个隐藏类,需要反射获取

HookInstrumentation.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class HookInstrumentation extends Instrumentation {
private Instrumentation mBase;

public HookInstrumentation(Instrumentation base) {
this.mBase = base;
}

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
intent.setComponent(new ComponentName(MyApplication.APP, HookActivity.class));
try {
Method method = Instrumentation.class.getDeclaredMethod("execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
method.setAccessible(true);
return (ActivityResult) method.invoke(mBase, who, contextThread, token, target, intent, requestCode, options);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}

我们加了一句打印,并把目标换成了HookActivity,反射获取Instrumentation类的execStartActivity()方法并调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try {
Class<?> clazz = Class.forName("android.app.ActivityThread");
Method method = clazz.getDeclaredMethod("currentActivityThread");
method.setAccessible(true);
Object currentActivityThread = method.invoke(null);
Field instrumentationField = currentActivityThread.getClass().getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);

Instrumentation instrumentation = (Instrumentation) instrumentationField.get(currentActivityThread);
HookInstrumentation hookInstrumentation = new HookInstrumentation(instrumentation);
instrumentationField.set(currentActivityThread, hookInstrumentation);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}

获得ActivityThread类,找到currentActivityThread()方法,调用方法获得ActivityThread对象,然后找到mInstrumentation字段,获取对象,用代理对象替代并设置进去,就完成了Hook

Binder Hook

Hook系统服务的机制称之为Binder Hook,因为本质上这些服务提供者都是存在于系统各个进程的Binder对象

1
2
3
4
// 获取原始的IBinder对象
IBinder b = ServiceManager.getService("activity");
// 转换为Service
IActivityManager in = asInterface(b);
1
2
3
4
5
6
7
8
9
10
public static android.content.IClipboard asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof android.content.IClipboard))) {
return ((android.content.IClipboard) iin);
}
return new android.content.IClipboard.Stub.Proxy(obj);
}

可以通过修改queryLocalInterface()来替换

1
2
3
4
5
6
7
8
9
10
11
12
13
public static IBinder getService(String name) {    
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class BinderHookHandler implements InvocationHandler {
private Object mBase;

public BinderHookHandler(IBinder base, Class<?> stubClass) {
Method method = stubClass.getDeclaredMethod("asInterface", IBinder.class);
this.mBase = method.invoke(null, base);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getPrimaryClip")) {
return ClipData.newPlainText(null,"你被黑了");
} else if (method.getName().equals("hasPrimaryClip")) {
return true;
}
return method.invoke(mBase, args);
}
}

剪贴板接口IClipboard的动态代理,替换了getPrimaryClip()hasPrimaryClip()两个方法,构造方法中调用Clipboard$StubasInterface()方法,把传入的IBinder转为IClipboard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BinderProxyHookHandler implements InvocationHandler {
private Class<?> mIInterface;
private Class<?> mStub;
private IBinder mBase;

public BinderProxyHookHandler(IBinder base) {
this.mBase = base;
try {
this.mStub = Class.forName("android.content.IClipboard$Stub");
this.mIInterface = Class.forName("android.content.IClipboard");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("queryLocalInterface")) {
// 第二个参数表示代理类需要实现的接口
return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{IBinder.class, IInterface.class, this.mIInterface}, new BinderHookHandler(mBase, mStub));
}
return method.invoke(mBase, args);
}
}

代理替换IBinder,修改其queryLocalInterface()方法,使其返回Hook后的剪贴板管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获得ServiceManager类 从中获得getService方法
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getDeclaredMethod("getService", String.class);

// 调用getService方法 获得剪贴板服务的IBinder Hook之改写其queryLocalInterface()方法 使其返回一个实际被重写的剪贴板服务
IBinder rawBinder = (IBinder) getService.invoke(null, "clipboard");
IBinder hookBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(), new Class[] { IBinder.class }, new BinderProxyHookHandler(rawBinder));

// 获得sCache字段 往里面填入Hook过的剪贴板IBinder
Field cacheField = serviceManager.getDeclaredField("sCache");
cacheField.setAccessible(true);
Map<String, IBinder> cache = (Map<String, IBinder>) cacheField.get(null);
cache.put("clipboard", hookBinder);

AMS

ActivityManagerNative实际上是ActivityManagerService这个远程对象的Binder代理对象,因为比较常用,所以有一个gDefault的全局变量保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获得ActivityManagerNative 获得其gDefault字段
Class<?> activityManagerNative = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerNative.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);

// gDefault是一个Sigleton 获得其mInstance字段
Class<?> singleton = Class.forName("android.util.Singleton");
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);

// 获得ActivityManagerProxy
// 代理Hook掉并设置回去
Object rawActivityManager = mInstanceField.get(gDefault);
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{ iActivityManagerInterface }, new ActivityManagerHandler(rawActivityManager));
mInstanceField.set(gDefault, proxy);

PMS

1
2
3
4
5
6
7
8
9
10
11
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}

IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}

可见由Activity.getPackageManager()获得,并经由ApplicationPackagemanager 包装

1
2
3
4
5
6
7
8
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
sPackageManager = IPackageManager.Stub.asInterface(b);
return sPackageManager;
}

可见可以Hook掉这个静态对象sPackageManager

1
2
3
4
5
6
7
8
9
10
11
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);

Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object sPackageManager = sPackageManagerField.get(currentActivityThread);

Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(), new Class[] { iPackageManagerInterface }, new PackageManagerHandler(sPackageManager));
sPackageManagerField.set(currentActivityThread, proxy);

使用同样的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class PackageManagerHandler implements InvocationHandler {
private Object mBase;

public PackageManagerHandler(Object base) {
this.mBase = base;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getPackageInfo")) {
PackageInfo info = (PackageInfo) method.invoke(mBase, args);
info.packageName = "hook";
return info;
} else if (method.getName().equals("getInstalledApplications")) {
Object result = method.invoke(mBase, args);

Class<?> parceledListSliceClass = Class.forName("android.content.pm.ParceledListSlice");
Field mListField = parceledListSliceClass.getDeclaredField("mList");

mListField.setAccessible(true);
List mList = (List) mListField.get(result);

List<ApplicationInfo> apps = (List<ApplicationInfo>) mList;
for (ApplicationInfo info : apps) {
info.packageName += ".hook";
}

return result;
}
return method.invoke(mBase, args);
}
}

Hook掉了getPackageInfo()方法,修改了包名,Hook掉getInstalledApplications()方法,在每个包名后面加了.hook

Activity生命周期管理

Activity必须在清单中显示声明,而清单不可能预先声明插件中的Activity

解决:可以预先注册一个中间Activity欺骗系统,然后替换成真正的Activity

首先处理ActivityManagerNative

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ActivityManagerHandler implements InvocationHandler {
private Object mBase;

public ActivityManagerHandler(Object object) {
this.mBase = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("startActivity")) {
// 拿到Intent参数
Intent rawIntent;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
rawIntent = (Intent) args[index];

// 伪装一个Intent 目标Activity设置为StubActivity
Intent hookIntent = new Intent();
ComponentName componentName = new ComponentName(MyApplication.APP, StubActivity.class.getCanonicalName());
hookIntent.setComponent(componentName);
// 把真正的Intent保存下来
hookIntent.putExtra(Constants.EXTRA_TARGET_INTENT, rawIntent);
args[index] = hookIntent;
return method.invoke(mBase, args);
}
return method.invoke(mBase, args);
}
}

这样AMS侧就会认为启动了一个清单中注册的Activity,接下来在AMS对应用的回调里替换实际的Activity,所有的操作会在ActivityThread.H里分发处理,因为Handler的处理顺序是先Message.handleMessage(),再使用全局mCallbackhandleMessage(),最后调用Handler.handleMessage(),所以可以Hook这个全局mCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class HookHandlerCallback implements Handler.Callback {
private Class<?> mActivityClientRecordClass;
private int LAUNCH_ACTIVITY;

public HookHandlerCallback() {
// 先反射拿到需要的类和变量
try {
Class<?> HClass = Class.forName("android.app.ActivityThread$H");
Field LAUNCH_ACTIVITY_FIELD = HClass.getDeclaredField("LAUNCH_ACTIVITY");
LAUNCH_ACTIVITY_FIELD.setAccessible(true);
LAUNCH_ACTIVITY = (int) LAUNCH_ACTIVITY_FIELD.get(null);
mActivityClientRecordClass = Class.forName("android.app.ActivityThread$ActivityClientRecord");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
try {
// 拿到ActivityClientRecord的intent字段 获得intent
Field intentField = mActivityClientRecordClass.getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(msg.obj);

// 获得实际的intent 并设置到intent字段里
Intent realIntent = intent.getParcelableExtra(Constants.EXTRA_TARGET_INTENT);
if (realIntent != null) {
intentField.set(msg.obj, realIntent);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return false;
}
}

因为AMS和应用之间并不传递具体的Activity信息,而是通过一个token关联,所以AMS侧仍然操作着伪造的Activity,应用侧则操作着真实的Activity,并且拥有正常的生命周期

ClassLoader

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校检、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制,是运行时动态加载的

Android通过DexClassLoader直接加载dex或apk

多ClassLoader方案

Activity的实例是在ActivityThread.performLaunchActivity()中创建的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE);
}

ComponentName component = r.intent.getComponent();
if (component == null) {
component = r.intent.resolveActivity(mInitialApplication.getPackageManager());
r.intent.setComponent(component);
}

if (r.activityInfo.targetActivity != null) {
component = new ComponentName(r.activityInfo.packageName, r.activityInfo.targetActivity);
}

ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
......
}
...
}

可见需要拿到一个ClassLoader和类名

这里的ClassLoader是由r.packageInfogetClassLoader()获得

r.packageInfo是一个LoadedApk对象,代表一个当前加载了的apk的本地状态,是apk在内存中的表示

ActivityThread.H.handleMessage()里,可以看到r.pakcageInfo 的来源

1
2
3
4
5
6
case LAUNCH_ACTIVITY: {
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
break;
}

最后调用了getPackageInfo()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) {
// 判断调用方和自己是不是一个UserId,是的话可以共享缓存数据
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
WeakReference<LoadedApk> ref;
if (differentUser) {
ref = null;
} else if (includeCode) {
// 调用进来传的includeCode是true 所以走这里
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
// 从缓存拿LoadedApk
LoadedApk packageInfo = ref != null ? ref.get() : null;
// 未命中或者需要更新资源则创建一个
if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) {
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) {

} else if (includeCode) {
mPackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo));
}
}
}
}

这里的mPackages 就是一个Hook点,可以构造自己的LoadedApk添加进去

LoadedApk又可以通过getPackageInfoNoCheck()获得

1
2
3
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}

需要构造ApplicationInfoCompatibilityInfo两个参数

前者描述了由清单的application标签描述的信息,可以使用PackageParsergenerateApplication()方法获得,然而这个类的兼容性非常差

1
2
3
public static ApplicationInfo generateApplicationInfo(Package p, int flags, PackageUserState state) {
......

参数Package需要分析apk文件,可使用PackageParser.parsePackage()获得

1
2
3
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
......

需要传入apk文件和标志位

后者代表不同用户中包的信息,这里用默认的即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 获得当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);

// 获得ActivityThread中的mPackages字段
ArrayMap<String, WeakReference<Object>> mPackages = (ArrayMap<String, WeakReference<Object>>) ReflectUtil.getField("android.app.ActivityThread", "mPackages", currentActivityThread);

// 获得PackageParser.generateApplicationInfo方法
Method generateApplication = ReflectUtil.getMethod("android.content.pm.PackageParser", "generateApplicationInfo", "android.content.pm.PackageParser$Package", int.class, "android.content.pm.PackageUserState");

// 获得PackageUserState类
Class<?> packageUserStateClass = ReflectUtil.getClass("android.content.pm.PackageUserState");
Object packageUserState = packageUserStateClass.newInstance();

// 获得CompatibilityInfo的DEFAULT_COMPATIBILITY_INFO字段 即默认实现
Class<?> compatibilityInfoClass = ReflectUtil.getClass("android.content.res.CompatibilityInfo");
Object compatibilityInfo = ReflectUtil.getField("android.content.res.CompatibilityInfo", "DEFAULT_COMPATIBILITY_INFO", null);

// 获得PackageParser类 并构造一个对象
Class<?> packageParserClass = ReflectUtil.getClass("android.content.pm.PackageParser");
Object packageParser = packageParserClass.newInstance();

// 获得PackageParser.parsePackage()方法并调用
File apkFile = new File(dstDir, "plugin.apk");
Method parsePackage = ReflectUtil.getMethod("android.content.pm.PackageParser", "parsePackage", File.class, int.class);
Object pack = parsePackage.invoke(packageParser, apkFile, 0);

// 用前面拿到的方法和构造的参数得到ApplicationInfo
ApplicationInfo applicationInfo = (ApplicationInfo) generateApplication.invoke(packageParser, pack, 0, packageUserState);

// 写入插件Apk路径信息
applicationInfo.sourceDir = apkFile.getPath();
applicationInfo.publicSourceDir = apkFile.getPath();

// 获得ActivityThread.getPackageInfoNoCheck()方法并调用得到LoadedApk
Method getPackageInfoNoCheck = ReflectUtil.getMethod("android.app.ActivityThread", "getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass);
Object loadedApk = getPackageInfoNoCheck.invoke(currentActivityThread, applicationInfo, compatibilityInfo);

// 构造一个自定义的ClassLoader
String optimizedDirectory = getFilesDir() + File.separator + "plugin";
File optimizedFile = new File(optimizedDirectory);
if (!optimizedFile.exists()) {
optimizedFile.mkdir();
}
ClassLoader classLoader = new HookClassLoader(apkFile.getPath(), optimizedDirectory, null, this.getClassLoader());

// 修改LoadedApk的mClassLoader字段
ReflectUtil.setField("android.app.LoadedApk", "mClassLoader", classLoader, loadedApk);

// 强引用保存一份 防止被回收
sLoadedApk.put(applicationInfo.packageName, loadedApk);

// 以包名为键存放到mPackages字段中
mPackages.put(applicationInfo.packageName, new WeakReference<>(loadedApk));

这样调用

1
2
3
4
Intent intent = new Intent();
// 填入想要调用的插件包名和类名
intent.setComponent(new ComponentName("com.example.liuhan.subproject", "com.example.liuhan.subproject.MainActivity"));
startActivity(intent);

按照教程这么做,后来一直找不到对应的类,堆栈中显示ClassLoader并不是我们提供的自定义ClassLoader,而mPackages中确实加入了插件信息

所以只可能是传入的键有问题,实际使用的是ActivityInfo.applicationInfo.packageName,通过拦截AMS回调发现这个值仍然是宿主包名,在Hook后的ActivityThread.H 中修改之

1
2
3
4
5
6
7
8
9
10
// 拿到真实包名
String packageName = realIntent.getPackage();

// 修改一个ActivityInfo并保存起来 后用
Field activityInfoField = mActivityClientRecordClass.getDeclaredField("activityInfo");
activityInfoField.setAccessible(true);
ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(msg.obj);
activityInfo.packageName = packageName;
activityInfo.applicationInfo.packageName = packageName;
activityInfoField.set(msg.obj, activityInfo);

前面也要传一下真实包名

1
2
3
4
Intent intent = new Intent();
intent.setPackage("com.example.liuhan.subproject");
intent.setComponent(new ComponentName("com.example.liuhan.subproject", "com.example.liuhan.subproject.MainActivity"));
startActivity(intent);

再次启动会遇到无法实例化Application,,追踪发现系统会向PMS查询包信息,因为没有而抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
...
if (!mPackageName.equals("android")) {
initializeJavaContextClassLoader();
}
...
}

private void initializeJavaContextClassLoader() {
IPackageManager pm = ActivityThread.getPackageManager();
android.content.pm.PackageInfo pi;
try {
pi = pm.getPackageInfo(mPackageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.myUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
if (pi == null) {
throw new IllegalStateException("Unable to get package info for " + mPackageName + "; is package not installed?");
}
...
}

public static String getParentActivityName(Context context, ComponentName componentName) throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
...
}

需要Hook一下PMS做处理

1
2
3
4
5
6
if (method.getName().equals("getPackageInfo")) {
String packageName = (String) args[0];
if (packageName.equals("com.example.liuhan.subproject")) {
return new PackageInfo();
}
}

拦截getPackageInfo()命令,改为在本地查找,找到则返回,否则交给系统做,这样就骗过去了

最后碰到一个问题,堆栈涉及AppCompatActivity,把插件Activity的基类改为Activity就好了,尚不清楚为什么

单ClassLoader方案

对宿主的ClassLoader做修改,使其可以找到我们插件中的类

在Application类中有一个成员变量mLoadedApk,表示宿主的LoadedApk,而这个变量是从ContextImpl中获取的;ContextImpl重写了getClassLoader方法,因此我们在Context环境中getClassLoader()获取到的就是宿主程序的ClassLoader

LoadedApk.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null);
}
return mClassLoader;
}
}

private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
......
mClassLoader = ApplicationLoaders.getDefault().getClassLoader("", mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader);
......
}

ApplicationLoaders.java

1
2
3
4
5
private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String cacheKey) {
...
PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(zip, librarySearchPath, libraryPermittedPath, parent, targetSdkVersion, isBundled);
...
}

PathClassLoader.java

1
2
3
public class PathClassLoader extends BaseDexClassLoader {
...
}

BaseDexClassLoader.java

1
2
3
4
5
6
7
8
9
10
11
12
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}

可以看到它在一个pathList里面查找类,是一个DexPathList对象

DexPathList.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;

if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}

会遍历它的dexElements数组来查找类,可以在这里做修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 获得DexClassLoader的pathList字段
Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathList = pathListField.get(loader);

// 获得DexList的dexElements字段
Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object[] dexElements = (Object[]) dexElementsField.get(pathList);

// 创建新Element数组
Class<?> elementClass = dexElements.getClass().getComponentType();
Object[] newDexElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);

// 构造插件的Element
Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
Object element = constructor.newInstance(apk, false, apk, DexFile.loadDex(apk.getCanonicalPath(), optDex.getAbsolutePath(), 0));

// 插件Element数组
Object[] toAdd = new Object[] { element };

// 将原数组和插件数组都拷贝到新数组
System.arraycopy(dexElements, 0, newDexElements, 0, dexElements.length);
System.arraycopy(toAdd, 0, newDexElements, newDexElements.length - 1, 1);

// 设置回去
dexElementsField.set(pathList, newDexElements);

多ClassLoader构架,每一个插件都有一个自己的ClassLoader,因此类的隔离性非常好——如果不同的插件使用了同一个库的不同版本,它们相安无事

单ClassLoader方案,插件和宿主程序的类全部都通过宿主的ClasLoader加载,虽然代码简单,但是鲁棒性很差;无法避免插件之间甚至插件与宿主之间使用的类库有冲突

多ClassLoader还有一个优点可以真正完成代码的热加载,如果插件需要升级,直接重新创建一个自定的ClassLoader加载新的插件,然后替换掉原来的版本即可(Java中,不同ClassLoader加载的同一个类被认为是不同的类)

多ClassLoader架构在大多数情况下是明智之举

插件BroadcastReceiver

静态注册的接收器在清单文件里,解析后保存在PMS中,以供AMS使用

1
2
3
4
5
6
7
8
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
}
if (intent.getComponent() == null) {
registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, userId);
}

receivers里就是从PMS获得的静态注册的接收器,registeredReceivers是动态注册的接收器

动态注册接收器

只需要反射即可使用

静态注册接收器

分析插件清单文件中的receiver然后集中动态注册

缺点是无法在应用未运行时接收到广播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void registerPluginReceiver(File apkFile) throws IllegalAccessException, InstantiationException, InvocationTargetException, ClassNotFoundException {
// 拿到PackageParser类 调用其parsePackage方法分析apk文件
Class<?> packageParserClass = ReflectUtil.getClass("android.content.pm.PackageParser");
Object packageParser = packageParserClass.newInstance();
Method parsePackage = ReflectUtil.getMethod("android.content.pm.PackageParser", "parsePackage", File.class, int.class);
Object pack = parsePackage.invoke(packageParser, apkFile, 0);

// 取出Package的receivers字段
ArrayList receivers = (ArrayList) ReflectUtil.getField("android.content.pm.PackageParser$Package", "receivers", pack);
for (Object receiver : receivers) {
// 取出className字段 创建一个广播接收器
String className = (String) ReflectUtil.getField("android.content.pm.PackageParser$Component", "className", receiver);
Class<?> receiverClass = Class.forName(className);
BroadcastReceiver r = (BroadcastReceiver) receiverClass.newInstance();

// 取出intents字段 里面保存的是IntentFilter 注册广播接收器
ArrayList intents = (ArrayList) ReflectUtil.getField("android.content.pm.PackageParser$Component", "intents", receiver);
for (Object intent : intents) {
registerReceiver(r, (IntentFilter) intent);
}
}
}

在插件的ClassLoader信息添加之后执行上面的方法,动态注册所有静态的广播接收器

插件Service

Service和Activity一样,都需要通过AMS管理,但Service不能简单的套用Activity的插件化方案,因为Activity有栈管理,有限的StubActivity可以满足需求,而Service的数量可能无法被有限的StubService满足

可以使用手动管理Service:拦截到startService()bindService(),没有创建Service则创建并调用onCreateonStartCommand(),如果创建了则调用onStartCommand(),拦截到stopService()则调用onDestroy()

为了仍能使用Service的独立进程特性,可以在清单中声明一些不同进程中的Service,启动代理Service,在其onStartCommand()中分发执行插件Service中的onStartCommand()方法

启动Service

1
<service android:name=".ProxyService" />

清单中定义代理Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ProxyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
ServiceManager.getInstance().onStartCommand(intent, flags, startId);
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
super.onDestroy();
}
}

代理Service的onStartCommand()里分发到真实Service里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public void onStartCommand(Intent intent, int flags, int startId) {
// 取得真实Intent
Intent realIntent = intent.getParcelableExtra(Constants.EXTRA_TARGET_INTENT);

// 根据Intent信息找到ServiceInfo
ServiceInfo serviceInfo = selectPluginService(realIntent);
if (serviceInfo == null) {
return;
}

// 从Service表中查找对应类名的Service
if (!sServiceMap.containsKey(serviceInfo.name)) {
// 没找到就创建
createService(intent, serviceInfo);
}
Service service = sServiceMap.get(serviceInfo.name);

// 调用Service的onStartCommand()
service.onStartCommand(intent, flags, startId);
}

private void createService(Intent intent, ServiceInfo serviceInfo) {
try {
// 获得CreateServiceData类 构造之
Class<?> createServiceDataClass = ReflectUtil.getClass("android.app.ActivityThread$CreateServiceData");
Constructor<?> constructor = createServiceDataClass.getDeclaredConstructor();
constructor.setAccessible(true);
Object createServiceData = constructor.newInstance();

// 填写token/info/intent/compatInfo字段
IBinder token = new Binder();
ReflectUtil.setField("android.app.ActivityThread$CreateServiceData", "token", token, createServiceData);

// 因为使用的单ClassLoader方案 是找不到插件对应的ClassLoader的 所以需要把其应用名改成宿主的 使用宿主ClassLoader
serviceInfo.applicationInfo.packageName = MyApplication.APP.getPackageName();
ReflectUtil.setField("android.app.ActivityThread$CreateServiceData", "info", serviceInfo, createServiceData);
Object compatibilityInfo = ReflectUtil.getField("android.content.res.CompatibilityInfo", "DEFAULT_COMPATIBILITY_INFO", null);
ReflectUtil.setField("android.app.ActivityThread$CreateServiceData", "compatInfo", compatibilityInfo, createServiceData);
ReflectUtil.setField("android.app.ActivityThread$CreateServiceData", "intent", intent, createServiceData);

// 调用ActivityThread.handleCreateService()方法
Method handleCreateService = ReflectUtil.getMethod("android.app.ActivityThread", "handleCreateService", createServiceDataClass);

Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);

handleCreateService.invoke(currentActivityThread, createServiceData);

// 因为创建Service方法无返回 而是把Service保存在了ActivityThread的mServices中 故取出并找到对应Service 保存在我们自己的表中
ArrayMap mServices = (ArrayMap) ReflectUtil.getField("android.app.ActivityThread", "mServices", currentActivityThread);
Service service = (Service) mServices.get(token);
sServiceMap.put(serviceInfo.name, service);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

private ServiceInfo selectPluginService(Intent pluginIntent) {
for (String className: sServiceInfoMap.keySet()) {
if (className.equals(pluginIntent.getComponent().getClassName())) {
return sServiceInfoMap.get(className);
}
}
return null;
}

分发onStartCommand()给实际Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (method.getName().equals("startService")) {
Intent rawIntent;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
rawIntent = (Intent) args[index];

Intent hookIntent = new Intent();
hookIntent.setPackage(rawIntent.getPackage());
ComponentName componentName = new ComponentName(MyApplication.APP, ProxyService.class.getName());
hookIntent.setComponent(componentName);
hookIntent.putExtra(Constants.EXTRA_TARGET_INTENT, rawIntent);
args[index] = hookIntent;
return method.invoke(mBase, args);
}

Hook掉AMS,拦截startService(),修改Intent的目标Service为代理Service,并将真实Intent保存起来

1
2
3
4
5
6
ArrayList services = (ArrayList) ReflectUtil.getField("android.content.pm.PackageParser$Package", "services", pack);
for (Object service : services) {
String className = (String) ReflectUtil.getField("android.content.pm.PackageParser$Component", "className", service);
ServiceInfo info = (ServiceInfo) ReflectUtil.getField("android.content.pm.PackageParser$Service", "info", service);
ServiceManager.sServiceInfoMap.put(className, info);
}

解析插件apk,把清单中的Service的信息保存起来

1
2
intent.setComponent(new ComponentName("com.example.liuhan.subproject", "com.example.liuhan.subproject.PluginService"));
startService(intent);

填入包名和类名,启动Service

绑定Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
ProxyService s = ((ProxyService.MyBinder) service).getService();
int result = (int) s.invoke("com.example.liuhan.subproject.PluginService", "add", 3, 4);
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.liuhan.subproject", "com.example.liuhan.subproject.PluginService"));
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

这里绑定了指定包名和类名的服务,在绑定成功后通过传递包名、方法名和参数调用其方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ProxyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
// 因为不会调用onStartCommand() 所以在这里把实际的Intent保存起来
Intent realIntent = intent.getParcelableExtra(Constants.EXTRA_TARGET_INTENT);
if (realIntent != null) {
ServiceManager.getInstance().addIntent(realIntent);
}
return new MyBinder();
}

public class MyBinder extends Binder {
public ProxyService getService(){
return ProxyService.this;
}
}

// 提供一个调用函数的方法
public Object invoke(String className, String methodName, Object... args) {
return ServiceManager.getInstance().invoke(className, methodName, args);
}
}

在代理服务里添加相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public Object invoke(String className, String methodName, Object... args) {
// 在服务表里面查包名 没查找就创建服务
if (!sServiceMap.containsKey(className)) {
Intent intent = null;
// 从Intent表里面查对应Intent
if (sIntentMap.containsKey(className)) {
intent = sIntentMap.get(className);
}

// 根据Intent查ServiceInfo 根据ServiceInfo和Intent创建服务
if (intent != null) {
ServiceInfo serviceInfo = selectPluginService(intent);
if (serviceInfo != null) {
createService(intent, serviceInfo);
}
}
}
Service service = sServiceMap.get(className);

// 获得传入参数的类信息
Class<?>[] argClass = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
argClass[i] = args[i].getClass();
}

// 反射调用服务的函数
Method method = ReflectUtil.getMethod(className, methodName, argClass);
try {
Object result = method.invoke(service, args);
return result;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}

实际服务只是一个普通的对象,反射调用其方法即可,至于跨进程的Binder机制其实由代理服务完成了,这里还是只做了一个分发而已

插件ContentProvider

ContentProvider的初始化在Application启动时,比onCreate()还要早,所以可以在attachBaseContext()中进行插件ContentProvider的安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void installContentProvider(File apkFile) {
try {
// 获得PackageParser类 调用parsePackage()方法获得Package
Class<?> packageParserClass = ReflectUtil.getClass("android.content.pm.PackageParser");
Object packageParser;
packageParser = packageParserClass.newInstance();
Method parsePackage = ReflectUtil.getMethod("android.content.pm.PackageParser", "parsePackage", File.class, int.class);
Object pack = parsePackage.invoke(packageParser, apkFile, 0);

// 获得providers字段
ArrayList providers = (ArrayList) ReflectUtil.getField("android.content.pm.PackageParser$Package", "providers", pack);
List<ProviderInfo> providerInfos = new ArrayList<>();
for (Object provider : providers) {
// 获得info字段
ProviderInfo providerInfo = (ProviderInfo) ReflectUtil.getField("android.content.pm.PackageParser$Provider", "info", provider);
// 需要修改这个 否则会出现安全异常 追踪代码即指导
providerInfo.applicationInfo = getApplicationInfo();
providerInfos.add(providerInfo);
}

// 获得ActivityThread的installContentProviders()方法
// 调用之安装插件ContentProvider
Method installContentProviders = ReflectUtil.getMethod("android.app.ActivityThread", "installContentProviders", Context.class, List.class);

Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);

installContentProviders.invoke(currentActivityThread, this, providerInfos);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}

仍然可以使用分发代理的方法

1
2
3
4
5
6
7
8
9
10
11
public class ProxyContentProvider extends ContentProvider {
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
// 构造实际的Uri
String newUri = "content:/" + uri.getPath();
// 实际的查询
return getContext().getContentResolver().query(Uri.parse(newUri), projection, selection, selectionArgs, sortOrder);
}
......
}

在清单中注册代理ContentProvider

1
2
3
4
<provider
android:authorities="com.example.liuhan.plugin.ProxyContentProvider"
android:name=".ProxyContentProvider"
android:exported="true" />

插件资源

不做处理无法加载插件资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void initResource() {
String apkPath = getExternalFilesDir(null) + File.separator + "plugin" + File.separator + "plugin.apk";

try {
mAssetManager = AssetManager.class.newInstance();
Method addAssetPathMethod = mAssetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.setAccessible(true);
addAssetPathMethod.invoke(mAssetManager, apkPath);

Method ensureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
ensureStringBlocks.setAccessible(true);
ensureStringBlocks.invoke(mAssetManager);

Resources supResource = getResources();
mResource = new Resources(mAssetManager, supResource.getDisplayMetrics(), supResource.getConfiguration());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}

在插件的Activity内,处理资源

1
2
3
4
5
6
7
8
9
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}

@Override
public Resources getResources() {
return mResource == null ? super.getResources() : mResource;
}

插件Activity如果继承自AppCompatActivity,主题还是有问题……

分享