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

  • 相关阅读:
    vue生命周期
    vue input 循环渲染问题
    Node express post 大小设置
    webpack 好文章
    知识点的总结
    jsplumb 使用总结
    理解es6 中 arrow function的this
    分块编码(Transfer-Encoding: chunked)
    CGI的工作原理
    JS数组循环的性能和效率分析(for、while、forEach、map、for of)
  • 原文地址:https://www.cnblogs.com/llguanli/p/8456407.html
Copyright © 2011-2022 走看看