转载请注明出处: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了,下图就是调用这行代码后的时序图:
Android动态部署五:如何从插件apk中启动Service_第1张图片

带着成功启动插件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 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(packageInfo));            } else {                mResourcePackages.put(aInfo.packageName,                        new WeakReference(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 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; itry {                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中来实现,如果读者有什么好的方法,可以分享出来一起学习一下。

更多相关文章

  1. Android Market google play store帐号注册方法流程 及发布应用
  2. android studio 2.1 preview4 之 gradle插件问题
  3. Android 实现全屏显示的几种方法整理
  4. 【Android热修复与插件化 三】ClassLoader详解
  5. Android中TextView中内容不换行的解决方法
  6. Android模拟、实现、触发系统按键事件的方法
  7. android 2.2+ 完全退出程序的方法
  8. android MediaPlayer出现RuntimeException: failure code: -38崩
  9. android 设置壁纸几种方法

随机推荐

  1. android中如何屏蔽长按menu出现的输入法
  2. android 性能分析之常用adb命令adb shell
  3. Android adb命令选择设备
  4. 解决 Could not find com.android.tools.
  5. Android(安卓)短信发送
  6. android camera相机相关参数
  7. Android中如何获取屏幕宽度和高
  8. Android中handler的作用与线程
  9. Android Studio引用远程依赖包时下载不了
  10. Android RoboGuice 使用指南(19):发送接