0%

插件化 - VirtualApk浅析

VirtualApk跟其他的插件化框架有何区别?

官方介绍

  • 支持几乎所有的Android特性,支持加载加载四大组件。
  • 兼容性好
    • 兼容市面上几乎所有的Android手机,这一点已经在滴滴出行客户端中得到验证;
    • 资源方面适配小米、Vivo、Nubia等,对未知机型采用自适应适配方案;
    • 极少的Binder Hook,目前仅仅hook了两个Binder:AMSIContentProvider,hook过程做了充分的兼容性适配;
    • 插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行。
  • 入侵性极低
    • 插件开发等同于原生开发,四大组件无需继承特定的基类;
    • 精简的插件包,插件可以依赖宿主中的代码和资源,也可以不依赖;
    • 插件的构建过程简单,通过Gradle插件来完成插件的构建,整个过程对开发者透明。

程序入口在哪?

在Application的attachBaseContext()中。

PluginManager.getInstance(context).init()

PluginManager.getInstance(context)中创建了PluginManager对象

PluginManager构造函数里做了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected PluginManager(Context context) {
if (context instanceof Application) {
this.mApplication = (Application) context;
this.mContext = mApplication.getBaseContext();
} else {
final Context app = context.getApplicationContext();
if (app == null) {
this.mContext = context;
this.mApplication = ActivityThread.currentApplication();
} else {
this.mApplication = (Application) app;
this.mContext = mApplication.getBaseContext();
}
}

mComponentsHandler = createComponentsHandler();
hookCurrentProcess();
}
  1. 创建了ComponentsHandler
  2. hookCurrentProcess()

hookCurrentProcess()做了什么?

1
2
3
hookInstrumentationAndHandler();
hookSystemServices();
hookDataBindingUtil();

hookInstrumentationAndHandler()做了什么?

hook Instrumentation

  • 通过ActivityThread.currentActivityThread()获取当前App进程的ActivityThread对象。
  • 通过ActivityThread获取Instrumentation对象。
  • 创建一个VAInstrumentation对象,把原始的Instrumentation对象传进去。
  • 反射替换ActivityThread的mInstrumentation属性为创建的VAInstrumentation对象。

hook Handler

  • 反射获取ActivityThread的getHandler()。
  • 反射替换Handler的mCallback为创建的VAInstrumentation对象。
  • VAInstrumentation实现了Handler.Callback。

VAInstrumentation

为什么要替换ActivityThread的Instrumentation为自定义的VAInstrumentation?

VAInstrumentation做了以下几件事:

  1. 覆写了execStartActivity()方法,并在执行原逻辑之前,调用了injectIntent(intent)来处理execStartActivity()传递过来的intent参数。
  2. 覆写了newActivity()。
  3. 覆写了callActivityOnCreate(),并在执行原逻辑之前,调用了injectActivity(activity)来修改callActivityOnCreate()传递过来的activity。

Instrumentation的execStartActivity()是做什么的?什么时候被调用?

Instrumentation.execStartActivity()内部会调用ActivityManagerService.startActivity()来启动Activity

调用链:

Activity.startActivity() -> Activity.startActivityForResult() -> Instrumentation.execStartActivity()

startActivity()为什么要交给ActivityManagerService来做?

如果启动是另外一个应用程序的Activity,需要创建新的进程,创建新的进程只能由Zygote来创建,AMS负责把创建进程的工作通过Socket转发给Zygote,AMS相当于是个门面,接管所有四大组件的操作,向应用程序屏蔽内部细节

为什么ActivtyManagerService跟Zygote通信是socket,而不是Binder?

因为Zygote中会fork进程,如果是Binder通信,Zygote代码会运行在Zygote进程的Binder线程池中的某个线程中,fork()一个进程容易产生死锁,并且fork()的子进程只有一个线程,其他线程会消失。

为什么多线程下fork()容易产生死锁?

fork()的行为是复制整个用户空间的数据(copy on write策略,复制会很快),并且fork()进程只会把父进程中当前线程拷贝到子进程。

然后锁的实现一般也是存储在用户空间,如果父进程中某个线程lock了锁,在另外一个线程fork()子进程,会把锁也带到子进程中,但是持有锁的线程蒸发了,子进程中的fork()过来的线程的锁永远不会释放,就发生了死锁。

Binder线程池什么时候创建?

无论是system_server进程,还是app进程,都是在进程fork完成后,便会在新进程中执行onZygoteInit()的过程中,启动binder线程池。

injectIntent()做了什么?

1
2
3
4
5
6
7
8
9
protected void injectIntent(Intent intent) {
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// null component is an implicitly intent
if (intent.getComponent() != null) {
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(), intent.getComponent().getClassName()));
// resolve intent with Stub Activity if needed
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
}
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
// ComponentsHandler.transformIntentToExplicitAsNeeded()
public Intent transformIntentToExplicitAsNeeded(Intent intent) {
ComponentName component = intent.getComponent();
if (component == null
|| component.getPackageName().equals(mContext.getPackageName())) {
ResolveInfo info = mPluginManager.resolveActivity(intent);
if (info != null && info.activityInfo != null) {
component = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
intent.setComponent(component);
}
}

return intent;
}

// PluginManager.resolveActivity()
public ResolveInfo resolveActivity(Intent intent) {
return this.resolveActivity(intent, 0);
}

public ResolveInfo resolveActivity(Intent intent, int flags) {
for (LoadedPlugin plugin : this.mPlugins.values()) {
ResolveInfo resolveInfo = plugin.resolveActivity(intent, flags);
if (null != resolveInfo) {
return resolveInfo;
}
}

return null;
}
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
public void markIntentIfNeeded(Intent intent) {
if (intent.getComponent() == null) {
return;
}

String targetPackageName = intent.getComponent().getPackageName();
String targetClassName = intent.getComponent().getClassName();
// search map and return specific launchmode stub activity
if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
intent.putExtra(Constants.KEY_IS_PLUGIN, true);
intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
dispatchStubActivity(intent);
}
}

private void dispatchStubActivity(Intent intent) {
ComponentName component = intent.getComponent();
String targetClassName = intent.getComponent().getClassName();
LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
ActivityInfo info = loadedPlugin.getActivityInfo(component);
if (info == null) {
throw new RuntimeException("can not find " + component);
}
int launchMode = info.launchMode;
Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
themeObj.applyStyle(info.theme, true);
String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity));
intent.setClassName(mContext, stubActivity);
}

如果startActivity()中的Intent中没有Component说明是隐式的Activity启动,去插件中寻找有没有匹配的Activity,有的话创建一个CompoenentName设置给Intent。这个设置有什么用呢?

接下来在markIntentIfNeed()中用到了,如果要启动的Activity不是宿主的并且是插件中的,给Intent增加extra参数,设置Activity的类名和目标包名。

这里设置有什么用呢?
在VAInstrumentation的newActivity()和injectActivity()中会用到。

如何绕过AMS对Activity是否在AndroidManifest.xml注册过的校验?

然后会再调用dispatchStubActivity()把intent中指定要启动的Activity类名替换为预先在AndroidManifest里占坑的StubActivity的类名,会根据不同的launchMode来选用用哪个占坑的。这样会让AMS以为要启动的是一个正常的Activity,可以绕过安全检查,不会出现ActivityNotFound。

newActivity()常规情况是什么时候被调用的?

Instrumentation.newActivity()的调用链:

ApplicationThread.scheduleTransaction() -> ActivityThread.scheduleTransaction() -> ActivityThread.H.handleMessage(): EXECUTE_TRANSACTION -> TransactionExecutor.execute() -> TransactionExecutor.executeCallbacks() -> LaunchActivityItem.execute() -> ActivityThread.handleLaunchActivity() -> ActivityThread.performLaunchActivity() -> Instrumentation.newActivity()

VAInstrumentation.newActivity()做了什么?

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
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
cl.loadClass(className);
Log.i(TAG, String.format("newActivity[%s]", className));

} catch (ClassNotFoundException e) {
ComponentName component = PluginUtil.getComponent(intent);

if (component == null) {
return newActivity(mBase.newActivity(cl, className, intent));
}

String targetClassName = component.getClassName();
Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName));

LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);

if (plugin == null) {
// Not found then goto stub activity.
boolean debuggable = false;
try {
Context context = this.mPluginManager.getHostContext();
debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
} catch (Throwable ex) {

}

if (debuggable) {
throw new ActivityNotFoundException("error intent: " + intent.toURI());
}

Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class);
return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent));
}

Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);

// for 4.1+
Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());

return newActivity(activity);
}

return newActivity(mBase.newActivity(cl, className, intent));
}

Instrumentation中的newActivity()原逻辑就是用ClassLoader根据指定的类名创建Activity实例并返回。

但如果是插件中的Activity,宿主的ClassLoader就会找不到类,因为Android中的ClassLoader是绑定dex文件位置的,类都存储在dex文件中,那应该怎么办?

用插件的ClassLoader去加载类,插件的ClassLoader中的dex。这就是catch到ClassNotFoundException异常后的逻辑了。

callActivityOnCreate()什么时候被调用?是做什么的?

调用链:

ApplicationThread.scheduleTransaction() -> ActivityThread.scheduleTransaction() -> ActivityThread.H.handleMessage():EXECUTE_TRANSACTION -> TransactionExecutor.execute() -> TransactionExecutor.executeCallbacks() -> LaunchActivityItem.execute() -> ActivityThread.handleLaunchActivity() -> ActivityThread.performLaunchActivity() -> Instrumentation.callActivityOnCreate()

1
2
3
4
5
6
// Instrumentation.callActivityOnCreate()
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}

Instrumentation中的原逻辑会调用Activity的onCreate()。

VAInstrumentation.callActivityOnCreate()做了什么?

1
2
3
4
5
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
injectActivity(activity);
mBase.callActivityOnCreate(activity, icicle);
}

调用了一下injectActivity()。

injectActivity()做了什么?

主要是将Activity中的Resource,Context等对象替换成了插件的相应对象,保证插件Activity在调用涉及到Context的方法时能够正确运行。

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
protected void injectActivity(Activity activity) {
final Intent intent = activity.getIntent();
if (PluginUtil.isIntentFromPlugin(intent)) {
Context base = activity.getBaseContext();
try {
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
Reflector.with(base).field("mResources").set(plugin.getResources());
Reflector reflector = Reflector.with(activity);
reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext()));
reflector.field("mApplication").set(plugin.getApplication());

// set screenOrientation
ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));
if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
activity.setRequestedOrientation(activityInfo.screenOrientation);
}

// for native activity
ComponentName component = PluginUtil.getComponent(intent);
Intent wrapperIntent = new Intent(intent);
wrapperIntent.setClassName(component.getPackageName(), component.getClassName());
activity.setIntent(wrapperIntent);

} catch (Exception e) {
Log.w(TAG, e);
}
}
}

之后Activity的onResume、onStop等生命周期怎么处理?

所有和Activity相关的生命周期函数,系统都会自动调用插件中的Activity的生命周期函数。

原因在于AMS在处理Activity时,通过一个token表示具体Activity对象,而这个token正是和启动Activity时创建的对象对应的,而这个Activity被我们替换成了插件中的Activity,所以之后AMS的所有调用都会传给插件中的Activity。

为什么要替换ActivityThread的Handler的Callback为自定义的VAInstrumentation?

获取LAUNCH_ACTIVITY的msg,获取到ActivityClientRecord,可以:

  1. 给Intent设置插件的ClassLoader。
  2. 反射获取activityInfo,替换Activity主题。

hookSystemServices()做了什么?

hook了ActivtyManager,对其进行动态代理拦截。

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

protected void hookSystemServices() {
try {
Singleton<IActivityManager> defaultSingleton;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get();
} else {
defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get();
}
IActivityManager origin = defaultSingleton.get();
IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
createActivityManagerProxy(origin));

// Hook IActivityManager from ActivityManagerNative
Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy);

if (defaultSingleton.get() == activityManagerProxy) {
this.mActivityManager = activityManagerProxy;
Log.d(TAG, "hookSystemServices succeed : " + mActivityManager);
}
} catch (Exception e) {
Log.w(TAG, e);
}
}

动态代理ActivtyManager干什么?

看动态代理的类:

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/*
* Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.didi.virtualapk.delegate;

import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.IApplicationThread;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.internal.utils.PluginUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* @author johnsonlee
*/
public class ActivityManagerProxy implements InvocationHandler {

private static final String TAG = Constants.TAG_PREFIX + "IActivityManagerProxy";

public static final int INTENT_SENDER_BROADCAST = 1;
public static final int INTENT_SENDER_ACTIVITY = 2;
public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
public static final int INTENT_SENDER_SERVICE = 4;

private PluginManager mPluginManager;
private IActivityManager mActivityManager;

public ActivityManagerProxy(PluginManager pluginManager, IActivityManager activityManager) {
this.mPluginManager = pluginManager;
this.mActivityManager = activityManager;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startService".equals(method.getName())) {
try {
return startService(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Start service error", e);
}
} else if ("stopService".equals(method.getName())) {
try {
return stopService(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Stop Service error", e);
}
} else if ("stopServiceToken".equals(method.getName())) {
try {
return stopServiceToken(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Stop service token error", e);
}
} else if ("bindService".equals(method.getName())) {
try {
return bindService(proxy, method, args);
} catch (Throwable e) {
Log.w(TAG, e);
}
} else if ("unbindService".equals(method.getName())) {
try {
return unbindService(proxy, method, args);
} catch (Throwable e) {
Log.w(TAG, e);
}
} else if ("getIntentSender".equals(method.getName())) {
try {
getIntentSender(method, args);
} catch (Exception e) {
Log.w(TAG, e);
}
} else if ("overridePendingTransition".equals(method.getName())){
try {
overridePendingTransition(method, args);
} catch (Exception e){
Log.w(TAG, e);
}
}

try {
// sometimes system binder has problems.
return method.invoke(this.mActivityManager, args);
} catch (Throwable th) {
Throwable c = th.getCause();
if (c != null && c instanceof DeadObjectException) {
// retry connect to system binder
IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE);
if (ams != null) {
IActivityManager am = ActivityManagerNative.asInterface(ams);
mActivityManager = am;
}
}

Throwable cause = th;
do {
if (cause instanceof RemoteException) {
throw cause;
}
} while ((cause = cause.getCause()) != null);

throw c != null ? c : th;
}

}

protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
IApplicationThread appThread = (IApplicationThread) args[0];
Intent target = (Intent) args[1];
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (null == resolveInfo || null == resolveInfo.serviceInfo) {
// is host service
return method.invoke(this.mActivityManager, args);
}

return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
}

protected Object stopService(Object proxy, Method method, Object[] args) throws Throwable {
Intent target = (Intent) args[1];
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (null == resolveInfo || null == resolveInfo.serviceInfo) {
// is hot service
return method.invoke(this.mActivityManager, args);
}

startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_STOP_SERVICE);
return 1;
}

protected Object stopServiceToken(Object proxy, Method method, Object[] args) throws Throwable {
ComponentName component = (ComponentName) args[0];
Intent target = new Intent().setComponent(component);
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (null == resolveInfo || null == resolveInfo.serviceInfo) {
// is hot service
return method.invoke(this.mActivityManager, args);
}

startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_STOP_SERVICE);
return true;
}

protected Object bindService(Object proxy, Method method, Object[] args) throws Throwable {
Intent target = (Intent) args[2];
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (null == resolveInfo || null == resolveInfo.serviceInfo) {
// is host service
return method.invoke(this.mActivityManager, args);
}

Bundle bundle = new Bundle();
PluginUtil.putBinder(bundle, "sc", (IBinder) args[4]);
startDelegateServiceForTarget(target, resolveInfo.serviceInfo, bundle, RemoteService.EXTRA_COMMAND_BIND_SERVICE);
mPluginManager.getComponentsHandler().remberIServiceConnection((IBinder) args[4], target);
return 1;
}

protected Object unbindService(Object proxy, Method method, Object[] args) throws Throwable {
IBinder iServiceConnection = (IBinder)args[0];
Intent target = mPluginManager.getComponentsHandler().forgetIServiceConnection(iServiceConnection);
if (target == null) {
// is host service
return method.invoke(this.mActivityManager, args);
}

ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_UNBIND_SERVICE);
return true;
}

protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
return mPluginManager.getHostContext().startService(wrapperIntent);
}

protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
// fill in service with ComponentName
target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();

// start delegate service to run plugin service inside
boolean local = PluginUtil.isLocalService(serviceInfo);
Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;
Intent intent = new Intent();
intent.setClass(mPluginManager.getHostContext(), delegate);
intent.putExtra(RemoteService.EXTRA_TARGET, target);
intent.putExtra(RemoteService.EXTRA_COMMAND, command);
intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
if (extras != null) {
intent.putExtras(extras);
}

return intent;
}

protected void getIntentSender(Method method, Object[] args) {
String hostPackageName = mPluginManager.getHostContext().getPackageName();
args[1] = hostPackageName;

Intent target = ((Intent[]) args[5])[0];
int intentSenderType = (int)args[0];
if (intentSenderType == INTENT_SENDER_ACTIVITY) {
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(target);
mPluginManager.getComponentsHandler().markIntentIfNeeded(target);
} else if (intentSenderType == INTENT_SENDER_SERVICE) {
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (resolveInfo != null && resolveInfo.serviceInfo != null) {
// find plugin service
Intent wrapperIntent = wrapperTargetIntent(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
((Intent[]) args[5])[0] = wrapperIntent;
}
} else if (intentSenderType == INTENT_SENDER_BROADCAST) {
// no action
}
}

protected void overridePendingTransition(Method method, Object[] args) {
String hostPackageName = mPluginManager.getHostContext().getPackageName();
args[1] = hostPackageName;
}
}

主要是拦截Service的启动。

以startService为例:

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
protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
IApplicationThread appThread = (IApplicationThread) args[0];
Intent target = (Intent) args[1];
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (null == resolveInfo || null == resolveInfo.serviceInfo) {
// is host service
return method.invoke(this.mActivityManager, args);
}

return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
}

protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
return mPluginManager.getHostContext().startService(wrapperIntent);
}

protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
// fill in service with ComponentName
target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();

// start delegate service to run plugin service inside
boolean local = PluginUtil.isLocalService(serviceInfo);
Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;
Intent intent = new Intent();
intent.setClass(mPluginManager.getHostContext(), delegate);
intent.putExtra(RemoteService.EXTRA_TARGET, target);
intent.putExtra(RemoteService.EXTRA_COMMAND, command);
intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
if (extras != null) {
intent.putExtras(extras);
}

return intent;
}
  • 如果发现是宿主的Service类,直接按原来的方式启动。
  • 如果发现是插件的Service类,启动在AndroidManifest.xml里预埋的LocalService或RemoteService,把插件的Service类名等信息添加到Intent中。

LocalService里做了什么?

在LocalService或RemoteService中,会解析出插件的Service类名,通过反射创建实例,然后调用生命周期方法,实现插件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
public class LocalService extends Service {

@Override
public void onCreate() {
super.onCreate();
mPluginManager = PluginManager.getInstance(this);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) {
return START_STICKY;
}

Intent target = intent.getParcelableExtra(EXTRA_TARGET);
int command = intent.getIntExtra(EXTRA_COMMAND, 0);
if (null == target || command <= 0) {
return START_STICKY;
}

ComponentName component = target.getComponent();
LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);

if (plugin == null) {
Log.w(TAG, "Error target: " + target.toURI());
return START_STICKY;
}
// ClassNotFoundException when unmarshalling in Android 5.1
target.setExtrasClassLoader(plugin.getClassLoader());
switch (command) {
case EXTRA_COMMAND_START_SERVICE: {
ActivityThread mainThread = ActivityThread.currentActivityThread();
IApplicationThread appThread = mainThread.getApplicationThread();
Service service;

if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
service = this.mPluginManager.getComponentsHandler().getService(component);
} else {
try {
service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();

Application app = plugin.getApplication();
IBinder token = appThread.asBinder();
Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);
IActivityManager am = mPluginManager.getActivityManager();

attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);
service.onCreate();
this.mPluginManager.getComponentsHandler().rememberService(component, service);
} catch (Throwable t) {
return START_STICKY;
}
}

service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
break;
}
}
}
}

hookDataBindingUtil()做了什么?

databinding相关,不是主要逻辑,暂不关心。

插件里的BroadcastReceiver是怎么让其生效的?

LoadedPlugin的构造函数里:

  • 读取插件AndroidManifest里所有的广播接收器。
  • 通过插件的ClassLoader加载 广播接收器类,再通过反射创建实例。
  • 用宿主Context动态注册广播接收器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {

this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);

// Register broadcast receivers dynamically
Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity receiver : this.mPackage.receivers) {
receivers.put(receiver.getComponentName(), receiver.info);

BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
this.mHostContext.registerReceiver(br, aii);
}
}
}

插件里的ContentProvider如何运行?

首先得看常规访问ContentProvider是什么手段。然后在访问ContentProvider的路径上看能否找到切入点,加载插件的ContentProvider。

常规访问ContentProvider是通过Context对象调用getContentResolver()获取ContentResolver对象来间接访问ContentProvider的。

以query()为例,ContentResolver.query()调用流程:

ContentResolver.query()函数主要做了两件事:

  • 获取ContentProvider的Binder代理。
  • 远程调用ContentProvider的query方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public final Cursor query(final @RequiresPermission.Read @NonNull Uri uri,  
    @Nullable String[] projection, @Nullable Bundle queryArgs,
    @Nullable CancellationSignal cancellationSignal) {
    // 第一步:获取ContentProvider的Binder代理
    IContentProvider unstableProvider = acquireUnstableProvider(uri);
    //...
    // 第二步:远程调用ContentProvider的query方法
    unstableProvider.query(mPackageName, uri, projection,
    queryArgs, remoteCancellationSignal);
    //...
    }

我们只能在ContentResolver的acquireUnstableProvider上下功夫,因为这一步还是处于App内部的逻辑,到别的进程就没法控制了。

如何拦截处理ContentResolver.acquireUnstableProvider()?

跟Service一个套路,如果发现启动的是插件里的ContentProvider:

  • 就启动一个AndroidManifest里预埋的ContentProvider。
  • 把插件的ContentProvider的类信息、查询的信息,都放在Intent里,转发给预埋的ContentProvider。
  • 在预埋的ContentProvider手动创建插件里的ContentProvider对象,并调用其方法,传递正确的参数,获取返回结果再向上返回。

VirtualApk如何覆写ContentResolver.acquireUnstableProvider()?

VirtualApk定义了一个PluginContentResolver类继承ContentResolver,覆写了acquireUnstableProvider方法。

1
2
3
4
5
6
protected IContentProvider acquireUnstableProvider(Context context, String auth) {
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return super.acquireUnstableProvider(context, auth);
}

PluginContentResolver在什么时候被创建使用?

这个PluginContentResolver在PluginContext类的getContentResolver()方法中创建。

1
2
3
4
@Override
public ContentResolver getContentResolver() {
return new PluginContentResolver(getHostContext());
}

PluginContext在什么时候被使用?

LoadedPlugin的构造函数里会创建,并一直缓存。

在创建插件的Activity、Service、ContentProvider类时,都会传这个Context对象,以保证插件里获取的Context对象都是PluginContext,好让自定义的行为发生作用。

query()时怎么判断是不是要访问插件里的ContentProvider?

query()会传一个Uri参数,然后把Uri传给acquireUnstableProvider(uri)

1
2
3
4
5
6
7
8
9
10
public final IContentProvider acquireUnstableProvider(Uri uri) {
if (!SCHEME_CONTENT.equals(uri.getScheme())) {
return null;
}
String auth = uri.getAuthority();
if (auth != null) {
return acquireUnstableProvider(mContext, uri.getAuthority());
}
return null;
}

Uri中的Authority部分可以唯一标识一个ContentProvider。

PluginContentResolver.acquireUnstableProvider()中,通过判断mPluginManager.resolveContentProvider(auth, 0) != null断定要加载的是不是插件中的ContentProvider。

PluginManager.resolveContentProvider()内部,也就是遍历插件Apk的AndroidManifest,然后把ContentProvider的信息都保存一个集合类里,然后遍历集合对比authority寻找目标ContentProvider。

怎么获取预埋的ContentProvider对象实例?

query()时发现要加载插件里的ContentProvider,然后要把消息转发给预埋的ContentProvider,但是预埋的ContentProvider对象怎么获取?

看上面的源码,可以知道会调用mPluginManager.getIContentProvider(),并返回IContentProvider。

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
public synchronized IContentProvider getIContentProvider() {
if (mIContentProvider == null) {
hookIContentProviderAsNeeded();
}

return mIContentProvider;
}

protected void hookIContentProviderAsNeeded() {
Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));
mContext.getContentResolver().call(uri, "wakeup", null, null);
try {
Field authority = null;
Field provider = null;
ActivityThread activityThread = ActivityThread.currentActivityThread();
Map providerMap = Reflector.with(activityThread).field("mProviderMap").get();
Iterator iter = providerMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
String auth;
if (key instanceof String) {
auth = (String) key;
} else {
if (authority == null) {
authority = key.getClass().getDeclaredField("authority");
authority.setAccessible(true);
}
auth = (String) authority.get(key);
}
if (auth.equals(RemoteContentProvider.getAuthority(mContext))) {
if (provider == null) {
provider = val.getClass().getDeclaredField("mProvider");
provider.setAccessible(true);
}
IContentProvider rawProvider = (IContentProvider) provider.get(val);
IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
mIContentProvider = proxy;
Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
break;
}
}
} catch (Exception e) {
Log.w(TAG, e);
}
}

hookIContentProviderAsNeeded()就干两件事:

  • mContext.getContentResolver().call(uri, "wakeup", null, null)触发AndroidManifest里预埋的RemoteContentProvider启动和创建。
    • RemoteContentProvider创建后必然保存在当前进程的ActivityThread里的mProviderMap里。
  • 去ActivityThread里找到RemoteContentProvider的实例并返回。

RemoteContentProvider.query()做了什么?

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

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
ContentProvider provider = getContentProvider(uri);
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
if (provider != null) {
return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder);
}
return null;
}

private ContentProvider getContentProvider(final Uri uri) {
final PluginManager pluginManager = PluginManager.getInstance(getContext());
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
final String auth = pluginUri.getAuthority();
ContentProvider cachedProvider = sCachedProviders.get(auth);
if (cachedProvider != null) {
return cachedProvider;
}

synchronized (sCachedProviders) {
LoadedPlugin plugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
if (plugin == null) {
try {
pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));
} catch (Exception e) {
Log.w(TAG, e);
}
}

final ProviderInfo providerInfo = pluginManager.resolveContentProvider(auth, 0);
if (providerInfo != null) {
RunUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
LoadedPlugin loadedPlugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance();
contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);
sCachedProviders.put(auth, contentProvider);
} catch (Exception e) {
Log.w(TAG, e);
}
}
}, true);
return sCachedProviders.get(auth);
}
}

return null;
}

代码很清晰,主要两件事:

  • 创建插件里的ContentProvider对象实例并缓存。
  • 调用插件ContentProvider的query()方法,把参数正确的传递。

至此插件的ContentProvider代理过程完成。

参考资料