IBigerBiger的成长之路

插件化实现(四)Service插件化

在前一篇文章中,对于Activity的启动原理与对应的插件化方案做了相关介绍,相信大家对于插件化现在会有更多的认识,同时对于Hook相关的知识一定也有更深刻的认识。本文将探讨Android四大组件之一Service组件的插件化方式。

根据上一篇的文章,我们可以知道要实现Service插件化,首先是要知道Service相关的启动原理,所以首先我们对于Service相关的启动原理源码分析,而在分析的过程中,可以思考下与Activity相比,Service组件的不同点在哪里呢?我们能否用与之相同的方式实现Service的插件化?如果不行,它们的差别在哪里,应该如何实现Service的插件化?

Service启动原理

关于Service启动分为两种方式如下

  • Started:其他组件调用startService()方法启动一个Service。一旦启动,Service将一直运行在后台(run in the background indefinitely)即便启动Service的组件已被destroy。通常,一个被start的Service会在后台执行单独的操作,也并不给启动它的组件返回结果。比如说,一个start的Service执行在后台下载或上传一个文件的操作,完成之后,Service应自己停止。

  • Bound:其他组件调用bindService()方法绑定一个Service。通过绑定方式启动的Service是一个client-server结构,该Service可以与绑定它的组件进行交互。一个bound service仅在有组件与其绑定时才会运行(A bound service runs only as long as another application component is bound to it),多个组件可与一个service绑定,service不再与任何组件绑定时,该service会被destroy。

由于bindService会比startService稍微复杂一点,所以这里我们选择Bound方式来解析。

绑定一个Service通过Context类的bindService完成的,这个方法需要三个参数:第一个参数代表想要绑定的Service的Intent,第二个参数是一个ServiceConnetion,我们可以通过这个对象接收到Service绑定成功或者失败的回调;第三个参数则是绑定时候的一些FLAG。

Context的具体实现则在ContextImpl类,ContextImpl中的bindService方法调用了bindServiceCommon方法,此方法源码如下:

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
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
}
if (mPackageInfo != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
mMainThread.getHandler(), flags);
} else {
throw new RuntimeException("Not supported in system context");
}
validateServiceIntent(service);
try {
IBinder token = getActivityToken();
if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null
&& mPackageInfo.getApplicationInfo().targetSdkVersion
< android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
flags |= BIND_WAIVE_PRIORITY;
}
service.prepareToLeaveProcess();
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(), service,
service.resolveTypeIfNeeded(getContentResolver()),
sd, flags, getOpPackageName(), user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
}
return res != 0;
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
}

我们有看到熟悉的东西了,可以看到这里的调用了ActivityManagerNative类的bindService方法,而ActivityManagerNative实际上就是ActivityManagerService这个远程对象的Binder代理对象;每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用。

所以我们可以去ActivityManagerService中寻找bindService方法,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int bindService(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags, String callingPackage,
int userId) throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
if (callingPackage == null) {
throw new IllegalArgumentException("callingPackage cannot be null");
}
synchronized(this) {
return mServices.bindServiceLocked(caller, token, service,
resolvedType, connection, flags, callingPackage, userId);
}
}

bindService方法只是做了一些参数校检之后直接调用了ActivityServices类的bindServiceLocked方法,如下:

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
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags,
String callingPackage, int userId) throws TransactionTooLargeException {
final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
...
ActivityRecord activity = null;
if (token != null) {
int aindex = mMainStack.indexOfTokenLocked(token);
......
activity = (ActivityRecord)mMainStack.mHistory.get(aindex);
}
...
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType, callingPackage,
Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
...
ServiceRecord s = res.record;
final long origId = Binder.clearCallingIdentity();
try {
...
mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
s.appInfo.uid, s.name, s.processName);
AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
ConnectionRecord c = new ConnectionRecord(b, activity,
connection, flags, clientLabel, clientIntent);
IBinder binder = connection.asBinder();
ArrayList<ConnectionRecord> clist = s.connections.get(binder);
...
clist.add(c);
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
s.lastActivity = SystemClock.uptimeMillis();
if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
return 0;
}
}
...
} finally {
Binder.restoreCallingIdentity(origId);
}
return 1;
}

bindServiceLocked方法较长,将不必要地方省略了,主要做了几件事情:

1.根据传进来的参数token是MainActivity在ActivityManagerService里面的一个令牌,通过这个令牌就可以将这个代表MainActivity的ActivityRecord取回来

2.通过retrieveServiceLocked函数,得到一个ServiceRecord,这个ServiceReocrd描述的是一个Service对象,这里就是CounterService了,这是根据传进来的参数service的内容获得的。

3.传进来的参数connection封装成一个ConnectionRecord对象。

4.最后如果启动的FLAG为BIND_AUTO_CREATE,那么调用bringUpServiceLocked开始创建Service,

接下来看一下bringUpServiceLocked方法,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private final boolean bringUpServiceLocked(ServiceRecord r,
int intentFlags, boolean whileRestarting) {
...
final String appName = r.processName;
ProcessRecord app = getProcessRecordLocked(appName, r.appInfo.uid);
if (app != null && app.thread != null) {
try {
realStartServiceLocked(r, app);
return true;
} catch (RemoteException e) {
...
}
}
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (startProcessLocked(appName, r.appInfo, true, intentFlags,
"service", r.name, false) == null) {
...
}
...
}

这里如果Service所在的进程已经启动,那么直接调用realStartServiceLocked方法来真正启动Service组件;如果Service所在的进程还没有启动,那么先在AMS中记下这个要启动的Service组件,然后通过startProcessLocked启动新的进程。

首先看Service进程已经启动的情况,即realStartServiceLocked方法,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app) throws RemoteException {
...
r.app = app;
...
app.services.add(r);
...
try {
...
app.thread.scheduleCreateService(r, r.serviceInfo);
...
} finally {
...
}
requestServiceBindingsLocked(r);
...
}

这个方法首先调用了app.thread的scheduleCreateService方法,这个是不是很熟悉,在上一篇文章中也有说到,它的实现是在ActivityThread的scheduleCreateService里,如下

1
2
3
4
5
6
7
8
9
10
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
sendMessage(H.CREATE_SERVICE, s);
}

ActivityThread中的Handler会对发送的CREATE_SERVICE消息进行处理,处理如下

1
2
3
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceCreate");
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

这里调用了ActivityThread类的handleCreateService方法,如下:

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
private void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate();
mServices.put(data.token, service);
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
// nothing to do.
}
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to create service " + data.info.name
+ ": " + e.toString(), e);
}
}
}

这里应该也很熟悉,首先通过ClassLoader创建这个Service,然后创建Context对象,最后调用这个Service的onCreate方法,最后将这个Service与对应的token存储下来,作为缓存。

scheduleCreateService这个Binder调用过程结束,代码又回到了AMS进程的realStartServiceLocked方法,从上面代码可以看到,会接下来执行了一个requestServiceBindingsLocked方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
boolean execInFg, boolean rebind) throws TransactionTooLargeException {
if (r.app == null || r.app.thread == null) {
return false;
}
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
bumpServiceExecutingLocked(r, execInFg, "bind");
r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
r.app.repProcState);
...
}
return true;
}

这里很熟悉,就不说了,后面调用ActivityThread的handleBindService方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void handleBindService(BindServiceData data) {
Service s = mServices.get(data.token);
if (s != null) {
try {
data.intent.setExtrasClassLoader(s.getClassLoader());
data.intent.prepareToEnterProcess();
try {
if (!data.rebind) {
IBinder binder = s.onBind(data.intent);
ActivityManagerNative.getDefault().publishService(
data.token, data.intent, binder);
} else {
s.onRebind(data.intent);
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
ensureJitEnabled();
} catch (RemoteException ex) {
}
} catch (Exception e) {
}
}
}

这里则完成了Bind相关的绑定工作,就不做过多的介绍了

Service插件化

关于Service的插件化,其实通过上一篇关于Activity插件化的文章,可以发现他们的启动流程很像,那么是否可以通过Activity插件化一样通过占位Service来实现呢?

答案是否定的,为什么?最主要的一个因素就是Service对象只能创建一个实例化对象,所以如果有多个要启动的Service的话,就会出问题,那么我们只能想别的问题去解决了。

由于和前面Activity一样,Service也是有验证过程的,所以我们也还是需要占位Service的,由于Service是不需要生命周期的,所以我们其实可以自己去创建插件中的Service,那么就可以想到通过占位Service来创建插件中的Service,占位Service在onStartCommand等方法里面进行分发,而插件中的Service则通过占位Service的分发来执行onStartCommond等对应的方法,那么这就是『代理分发技术了』。

那么现在需要做的事情就是如下了:

1.Hook住ActivityManagerService,监听到了它的startServie方法,然后替换为占位分发Service,并将启动插件Service相关的信息传递过去。

怎么hook住ActivityManagerService这个方法在前面一篇文章已经有提到过了,就不介绍了,我们来完成监听startServie方法,然后替换为占位分发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
if ("startService".equals(method.getName())){
Intent raw;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
raw = (Intent) args[index];
Intent newIntent = new Intent();
String stubPackage = HookApplication.getContext().getPackageName();
ComponentName componentName = new ComponentName(stubPackage, ProxyService.class.getName());
newIntent.setComponent(componentName);
newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);
args[index] = newIntent;
Log.d(TAG, "hook Service success");
return method.invoke(mBase, args);
}

2.占位分发Service在onStartCommand分发,通过获取的插件Service相关信息创建插件中的Service对象,然后执行创建好的Service对象的onStart方法,创建并执行插件Service。

怎么在onStartCommand中分发就暂时不介绍

我们来看下怎么自己创建一个Service,从前面关于Service分析的过程来看,可以通过handleCreateService方法来创建,这个方法有一个参数CreateServiceData,所以要是想自己创建一个Service的话需要我们来构建一个
CreateServiceData对象。

CreateServiceData类如下:

1
2
3
4
5
6
7
8
9
10
11
static final class CreateServiceData {
IBinder token;
ServiceInfo info;
CompatibilityInfo compatInfo;
Intent intent;
public String toString() {
return "CreateServiceData{token=" + token + " className="
+ info.name + " packageName=" + info.packageName
+ " intent=" + intent + "}";
}
}

这个CreateServiceData类包含四个参数,其中intent就是我们传递的Service信息,这个我们可以直接获取到,token前面有提到过,主要是将这个token与对应创建的service存储下来,所以这个token我们可以自己创建即可,CompatibilityInfo我们使用默认即可,那么接下来就只差ServiceInfo这个参数了,这个ServiceInfo是指关于Service信息相关的类。

那么我们怎么获取插件中的Service的ServiceInfo呢?

前面有提到PackageParser这个类来获取ApplicationInfo的信息,同样这个类也有提供获取ServiceInfo的信息的方法,如下:

1
public static final ServiceInfo generateServiceInfo(Service s, int flags,PackageUserState state, int userId)

使用这个方法需要四个参数,这里state我们可以直接新建一个PackageUserState,userId通过PackageParser代码发现可以通过UserHandle.getCallingUserId()获取,flags直接取0就好了,而第一个参数关于Service获取,通过PackageParser代码发现,可以通过parsePackage方法获取,代码如下

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
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);
Object packageParser = packageParserClass.newInstance();
Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_SERVICES);
Field servicesField = packageObj.getClass().getDeclaredField("services");
//获取service
List services = (List) servicesField.get(packageObj);
Class<?> packageParser$ServiceClass = Class.forName("android.content.pm.PackageParser$Service");
Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Class<?> userHandler = Class.forName("android.os.UserHandle");
Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");
int userId = (Integer) getCallingUserIdMethod.invoke(null);
Object defaultUserState = packageUserStateClass.newInstance();
Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateServiceInfo",
packageParser$ServiceClass, int.class, packageUserStateClass, int.class);
ServiceInfo info = (ServiceInfo) generateReceiverInfo.invoke(packageParser, service, 0, defaultUserState, userId);
}

获取到ServiceInfo,我们就可以去构建CreateServiceData,然后创建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
IBinder token = new Binder();
Class<?> createServiceDataClass = Class.forName("android.app.ActivityThread$CreateServiceData");
Constructor<?> constructor = createServiceDataClass.getDeclaredConstructor();
constructor.setAccessible(true);
//构建CreateServiceData对象
Object createServiceData = constructor.newInstance();
//添加CreateServiceData的IBinder信息
Field tokenField = createServiceDataClass.getDeclaredField("token");
tokenField.setAccessible(true);
tokenField.set(createServiceData, token);
//添加CreateServiceData的ServiceInfo信息
Field infoField = createServiceDataClass.getDeclaredField("info");
infoField.setAccessible(true);
infoField.set(createServiceData, serviceInfo);
Class<?> compatibilityClass = Class.forName("android.content.res.CompatibilityInfo");
Field defaultCompatibilityField = compatibilityClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
Object defaultCompatibility = defaultCompatibilityField.get(null);
Field compatInfoField = createServiceDataClass.getDeclaredField("compatInfo");
compatInfoField.setAccessible(true);
//添加CreateServiceData的CompatibilityInfo信息
compatInfoField.set(createServiceData, defaultCompatibility);
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
Method handleCreateServiceMethod = activityThreadClass.getDeclaredMethod("handleCreateService", createServiceDataClass);
handleCreateServiceMethod.setAccessible(true);
//创建Service
handleCreateServiceMethod.invoke(currentActivityThread, createServiceData);
Field mServicesField = activityThreadClass.getDeclaredField("mServices");
mServicesField.setAccessible(true);
//获取创建的Service缓存集合
Map mServices = (Map) mServicesField.get(currentActivityThread);
//获取刚刚创建的Service对象
Service service = (Service) mServices.get(token);
//从系统的Service缓存集合移除我们创建的Service对象,自己管理
mServices.remove(token);

创建好了Service怎么去启动的话,就不做说明了。

3.监听到了ActivityManagerService的stopServie方法,由于占位分发Service只有一个,所以我们调用上面创建的Service对象的onDestroy方法即可。

监听stopServie方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if ("stopService".equals(method.getName())){
Intent raw;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
raw = (Intent) args[index];
return ServiceManager.getInstance().stopService(raw);
}

这里的其实就调用上面创建的Service的onDestroy方法即可。

写在后面的话

关于Service插件化到这里就结束了,其实我们通过Activity与Service插件化对比,就会发现Service其实更多是我们自己去创建的,而不走系统的流程,为什么?因为Service的生命周期没有额外的因素影响,因此我们选择了手动控制其生命周期的方式。对于Activity来说,由于他的生命周期受用户交互影响,只有系统本身才能对这种交互有全局掌控力,所以我们必须去完成一整套的Hook流程来欺骗系统。所以说到底就是要给予插件中组件的”生命”。