zoukankan      html  css  js  c++  java
  • 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了,下图就是调用这行代码后的时序图:
    这里写图片描写叙述

    带着成功启动插件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);
            }
        }
    }
    

    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中来实现,假设读者有什么好的方法,能够分享出来一起学习一下。

  • 相关阅读:
    20200209 ZooKeeper 3. Zookeeper内部原理
    20200209 ZooKeeper 2. Zookeeper本地模式安装
    20200209 Zookeeper 1. Zookeeper入门
    20200206 尚硅谷Docker【归档】
    20200206 Docker 8. 本地镜像发布到阿里云
    20200206 Docker 7. Docker常用安装
    20200206 Docker 6. DockerFile解析
    20200206 Docker 5. Docker容器数据卷
    20200206 Docker 4. Docker 镜像
    Combining STDP and Reward-Modulated STDP in Deep Convolutional Spiking Neural Networks for Digit Recognition
  • 原文地址:https://www.cnblogs.com/llguanli/p/8456407.html
Copyright © 2011-2022 走看看