Android动态部署五(怎样从插件apk中启动Service)

万事须己运,他得非我贤。这篇文章主要讲述Android动态部署五:怎样从插件apk中启动Service相关的知识,希望能为你提供帮助。
转载请注明出处:http://blog.csdn.net/ximsfei/article/details/51072332
github地址:https://github.com/ximsfei/DynamicDeploymentApk
Android动态部署一:Google原生Split APK浅析
Android动态部署二:APK安装及AndroidManifest.xml解析流程分析
Android动态部署三:怎样从插件apk中启动Activity(一)
Android动态部署四:怎样从插件apk中启动Activity(二)
经过前面几篇文章的分析,我们了解到了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中,非常easy的一行代码,就能够启动TargetService了,下图就是调用这行代码后的时序图:
Android动态部署五(怎样从插件apk中启动Service)

文章图片

带着成功启动插件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) { } ... } ... }private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { if (app.thread == null) { throw new RemoteException(); } ... r.app = app; r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); final boolean newService = app.services.add(r); bumpServiceExecutingLocked(r, execInFg, "create"); mAm.updateLruProcessLocked(app, false, null); mAm.updateOomAdjLocked(); boolean created = false; try { if (LOG_SERVICE_START_STOP) { String nameTerm; int lastPeriod = r.shortName.lastIndexOf(‘.‘); nameTerm = lastPeriod > = 0 ? r.shortName.substring(lastPeriod) : r.shortName; EventLogTags.writeAmCreateService( r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid); } synchronized (r.stats.getBatteryStats()) { r.stats.startLaunchedLocked(); } mAm.ensurePackageDexOpt(r.serviceInfo.packageName); app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); // 相似Activity启动流程中的app.thread.scheduleLaunchActivity方法。在这里会new一个Service。而且调用attach以及onCreate方法 r.postNotification(); created = true; } catch (DeadObjectException e) { Slog.w(TAG, "Application dead when creating service " + r); mAm.appDiedLocked(app); throw e; } finally { if (!created) { // Keep the executeNesting count accurate. final boolean inDestroying = mDestroyingServices.contains(r); serviceDoneExecutingLocked(r, inDestroying, inDestroying); // Cleanup. if (newService) { app.services.remove(r); r.app = null; }// Retry. if (!inDestroying) { scheduleServiceRestartLocked(r, false); } } }requestServiceBindingsLocked(r, execInFg); updateServiceClientActivitiesLocked(app, null, true); // If the service is in the started state, and there are no // pending arguments, then fake up one so its onStartCommand() will // be called. if (r.startRequested & & r.callStart & & r.pendingStarts.size() == 0) { r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), null, null)); }sendServiceArgsLocked(r, execInFg, true); //第一次启动的Service,会在这里调用onStartCommand方法if (r.delayed) { if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r); getServiceMap(r.userId).mDelayedStartList.remove(r); r.delayed = false; }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 (from start): " + r); stopServiceLocked(r); } } }

【Android动态部署五(怎样从插件apk中启动Service)】ActivityThread.java
//ActivityThread$ApplicationThread public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId, int flags ,Intent args) { ... sendMessage(H.SERVICE_ARGS, s); }public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) { ... sendMessage(H.CREATE_SERVICE, s); }//ActivityThread$H public void handleMessage(Message msg) { switch (msg.what) { case CREATE_SERVICE: { handleCreateService((CreateServiceData)msg.obj); } break; case SERVICE_ARGS: { handleServiceArgs((ServiceArgsData)msg.obj); } break; } }//ActivityThread private void handleCreateService(CreateServiceData data) { ...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(); //这里和Activity的启动流程有些许区别 } 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(); //这里直接调用了onCreate,而没有相似的先调用callActivityOnCreate方法 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); } } }private void handleServiceArgs(ServiceArgsData data) { Service s = mServices.get(data.token); if (s != null) { try { if (data.args != null) { data.args.setExtrasClassLoader(s.getClassLoader()); data.args.prepareToEnterProcess(); } int res; if (!data.taskRemoved) { res = s.onStartCommand(data.args, data.flags, data.startId); } else { s.onTaskRemoved(data.args); res = Service.START_TASK_REMOVED_COMPLETE; }QueuedWork.waitToFinish(); try { ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_START, data.startId, res); } catch (RemoteException e) { // nothing to do. } ensureJitEnabled(); } catch (Exception e) { if (!mInstrumentation.onException(s, e)) { throw new RuntimeException( "Unable to start service " + s + " with " + data.args + ": " + e.toString(), e); } } } }

看到这里。原生Service也启动起来了,我们发现Service的启动流程和Activity的相似。但又不全然一样,正是由于这些许区别。让我们的开发工作陷入了困境,遇到了以下这些“坑”:
1. Service不像Activity的标准模式,能够一直实例化,当某个Service启动后,从上面的代码能够看到,再次调用startService方法。源代码中并不会去又一次创建Service。调用onCreate,而是直接调用onStartCommand方法,所以我们不能通过StubService的方式来启动插件Service。
2. Service的handleCreateService方法不像在Activity启动流程中的performLaunchActivity方法中获取类名后,通过Instrumentation类的newActivity方法实例化,通过callActivityOnCreate方法间接调用Activity的onCreate,这样我们有机会通过重写DynamicInstrumentation类来扩展插件功能。而Service却直接在ActivityThread类中实例化。而且在attach方法结束后直接调用onCreate方法。
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... Activity a = performLaunchActivity(r, customIntent); ... }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); } ... Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); ... } catch (Exception e) { ... }try { ... if (activity != null) { ... activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor); ... if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } ... } } catch (Exception e) { ... } return activity; }

上面那两个坑,恰恰是我们在实现从插件apk中启动Activity时所关注的点。似乎这些点在这里一个都用不上,前面跨出的一大步,感觉也是被打了回去,在接下来的几天里。我重复阅读源代码,发现了当中的一个关键点。插件中的Service是使用插件的ClassLoader通过类名来载入的。那我们能够在ClassLoader上做一些“手脚”。

private void handleCreateService(CreateServiceData data) { ... 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) { ... } ... }public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { return getPackageInfo(ai, compatInfo, null, false, true, false); }private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { ... synchronized (mResourcesManager) { WeakReference< LoadedApk> ref; if (differentUser) { // Caching not supported across users ref = null; } else if (includeCode) { ref = mPackages.get(aInfo.packageName); //宿主apk的LoadApk保存在mPackages中 } else { ref = mResourcePackages.get(aInfo.packageName); }LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null & & !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ?
"Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ?
mBoundApplication.processName : null) + ")"); packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode & & (aInfo.flags& ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); if (mSystemThread & & "android".equals(aInfo.packageName)) { packageInfo.installSystemApplicationInfo(aInfo, getSystemContext().mPackageInfo.getClassLoader()); }if (differentUser) { // Caching not supported across users } else if (includeCode) { mPackages.put(aInfo.packageName, new WeakReference< LoadedApk> (packageInfo)); } else { mResourcePackages.put(aInfo.packageName, new WeakReference< LoadedApk> (packageInfo)); } } return packageInfo; } }

看到这个点之后,感觉像是抓住一个救命稻草那样兴奋,我们能够在每次安装一个插件apk的同一时候将插件apk的ClassLoader安装到mPackages.get(“host package name”)中。这样就能够不做其它的改动。依据插件apk中的Service类名就能够载入TargetService了,同一时候细心的读者也会发现,事实上Activity,BroadcastReceiver,ContentProvider也是通过这种方式载入的。真是一劳永逸呢。

DynamicClassLoaderWrapper.java
package com.ximsfei.dynamic.app; import java.util.ArrayList; /** * Created by pengfenx on 3/15/2016. */ public class DynamicClassLoaderWrapper extends ClassLoader { private final ClassLoader mBase; private final ArrayList< ClassLoader> mDynamicLoaders = new ArrayList< > (); protected DynamicClassLoaderWrapper(ClassLoader base) { super(); mBase = base; }public void addClassLoader(ClassLoader cl) { if (!mDynamicLoaders.contains(cl)) { mDynamicLoaders.add(cl); } }@Override protected Class< ?> findClass(String className) throws ClassNotFoundException { try { return mBase.loadClass(className); } catch (ClassNotFoundException e) { }int N = mDynamicLoaders.size(); for (int i=0; i< N; i++) { try { return mDynamicLoaders.get(i).loadClass(className); } catch (ClassNotFoundException e) { }//这里用try catch来推断该插件中是否存在TargetService不是非常好 } throw new ClassNotFoundException(className); }}

安装ClassLoader:
public synchronized void installClassLoader(ClassLoader classLoader) { Object loadedApk = ((WeakReference) getPackages().get(getHostPackageName())).get(); try { ClassLoader cl = Reflect.create(loadedApk.getClass()) .setMethod("getClassLoader").invoke(loadedApk); if (!(cl instanceof DynamicClassLoaderWrapper)) { DynamicClassLoaderWrapper dclw = new DynamicClassLoaderWrapper(cl); dclw.addClassLoader(classLoader); Reflect.create(loadedApk.getClass()).setField("mClassLoader") .set(loadedApk, dclw); } else { ((DynamicClassLoaderWrapper) cl).addClassLoader(classLoader); } } catch (Exception e) { } }private synchronized Map getPackages() { if (mPackages == null) { try { mPackages = mActivityThreadReflect.setField("mPackages").get(currentActivityThread()); } catch (Exception e) { } } return mPackages; }

stopService, bindService, unbindService的流程与startService流程相似,而且不须要做过多的改动,在这里就不再分析了,有兴趣的读者能够自己去看一下源代码,分析一下。

遗留问题第一个坑中,Service仅仅能启动一次,所以我们不能通过伪装成StubService的方式。来“骗过”AndroidManifest的检測,我也没有想到更好的方法来实现,临时仅仅能将要使用的Service类名注冊到宿主apk的AndroidManifest中来实现,假设读者有什么好的方法,能够分享出来一起学习一下。

    推荐阅读