当前位置: 代码迷 >> Android >> Android动态部署五:怎么从插件apk中启动Service
  详细解决方案

Android动态部署五:怎么从插件apk中启动Service

热度:341   发布时间:2016-04-24 11:15:49.0
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中,很简单的一行代码,就可以启动TargetService了,下图就是调用这行代码后的时序图:
这里写图片描述

带着成功启动插件Activity的经验,我们继续通过分析Service启动流程,试图从中找到hook点从而将我们对插件Service的扩展操作,通过类似的重写DynamicInstrumentation类,替换进ActivityThread中。
在时序图中我们发现在调用startService方法后,最终都会调到ContextImpl中的startService。

@Overridepublic 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);        }    }}

ActivityThread.java

//ActivityThread$ApplicationThreadpublic 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$Hpublic void handleMessage(Message msg) {    switch (msg.what) {        case CREATE_SERVICE: {            handleCreateService((CreateServiceData)msg.obj);        } break;        case SERVICE_ARGS: {            handleServiceArgs((ServiceArgsData)msg.obj);        } break;    }}//ActivityThreadprivate 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中来实现,如果读者有什么好的方法,可以分享出来一起学习一下。

  相关解决方案