Android动态部署五:如何从插件apk中启动Service
经过前面几篇文章的分析,我们了解到了Google原生是如何拆分apk的,并且我们自己可以通过解析manifest文件,通过创建插件ClassLoader,Resources对象来启动插件APK中的Activity,上一篇文章关于资源的问题,有一点遗漏,在插件中开发者有可能通过如下代码获取资源Id
getIdentifier("xxx", "layout", getPackageName());
此时调用getPackageName()方法返回的是宿主apk的包名,所以我们需要在DynamicContextImpl类中重写getPackageName()方法,返回从插件apk的manifest中解析出来的的包名,接下来我们通过分析Service启动流程来看看宿主apk如何启动Android四大组件之Service。
Service启动流程
startService(new Intent(this, TargetService.class));
在Activity中,很简单的一行代码,就可以启动TargetService了,下图就是调用这行代码后的时序图:
带着成功启动插件Activity的经验,我们继续通过分析Service启动流程,试图从中找到hook点从而将我们对插件Service的扩展操作,通过类似的重写DynamicInstrumentation类,替换进ActivityThread中。
在时序图中我们发现在调用startService方法后,最终都会调到ContextImpl中的startService。
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, mUser);
}
private ComponentName startServiceCommon(Intent service, UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess();
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), getOpPackageName(), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException(
"Not allowed to start service " + service
+ " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException(
"Unable to start service " + service
+ ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
}
在看了源码之后我们发现,这个方法的功能,其实跟Activity启动流程中Instrumentation类中的execStartActivity方法类似,看到这就感觉启动插件Service已经十拿九稳了,我们已经跨出了很大的一步,我们继续来深入研究Service的启动流程的源码:
ActiveServices.java
private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting) throws TransactionTooLargeException {
if (r.app != null && r.app.thread != null) {
sendServiceArgsLocked(r, execInFg, false);//如果Service已启动,在这里会直接调用Service的onStartCommand方法
return null;
}
if (!whileRestarting && r.restartDelay > 0) {
// If waiting for a restart, then do nothing.
return null;
}
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent);
// We are now bringing the service up, so no longer in the
// restarting state.
if (mRestartingServices.remove(r)) {
r.resetRestartCounter();
clearRestartingIfNeededLocked(r);
}
// Make sure this service is no longer considered delayed, we are starting it now.
if (r.delayed) {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (bring up): " + r);
getServiceMap(r.userId).mDelayedStartList.remove(r);
r.delayed = false;
}
// Make sure that the user who owns this service is started. If not,
// we don't want to allow it to run.
if (mAm.mStartedUsers.get(r.userId) == null) {
String msg = "Unable to launch app "
+ r.appInfo.packageName + "/"
+ r.appInfo.uid + " for service "
+ r.intent.getIntent() + ": user " + r.userId + " is stopped";
Slog.w(TAG, msg);
bringDownServiceLocked(r);
return msg;
}
// Service is now being launched, its package can't be stopped.
try {
AppGlobals.getPackageManager().setPackageStoppedState(
r.packageName, false, r.userId);
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ r.packageName + ": " + e);
}
final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
final String procName = r.processName;
ProcessRecord app;
if (!isolated) {
app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
+ " app=" + app);
if (app != null && app.thread != null) {
try {
app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
realStartServiceLocked(r, app, execInFg); //这里会真正启动Service
return null;
} catch (TransactionTooLargeException e) {
throw e;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting service " + r.shortName, e);
}
// If a dead object exception was thrown -- fall through to
// restart the application.
}
} else {
// If this service runs in an isolated process, then each time
// we call startProcessLocked() we will get a new isolated
// process, starting another process if we are currently waiting
// for a previous process to come up. To deal with this, we store
// in the service any current isolated process it is running in or
// waiting to have come up.
app = r.isolatedProc;
}
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (app == null) {
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
"service", r.name, false, isolated, false)) == null) {
String msg = "Unable to launch app "
+ r.appInfo.packageName + "/"
+ r.appInfo.uid + " for service "
+ r.intent.getIntent() + ": process is bad";
Slog.w(TAG, msg);
bringDownServiceLocked(r);
return msg;
}
if (isolated) {
r.isolatedProc = app;
}
}
if (!mPendingServices.contains(r)) {
mPendingServices.add(r);
}
if (r.delayedStop) {
// Oh and hey we've already been asked to stop!
r.delayedStop = false;
if (r.startRequested) {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
"Applying delayed stop (in bring up): " + r);
stopServiceLocked(r);
}
}
return null;
}
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
boolean oomAdjusted) throws TransactionTooLargeException {
...
while (r.pendingStarts.size() > 0) {
...
try {
...
r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
} catch (Exception e) {
}
...
}