android四大组件之二-service实现原理分析

前言:

一开始的目标是解决各种各样的ANR问题的,但是我们知道,ANR总体上分有四种类型,这四种类型有三种是和四大组件相对应的,所以,如果想了解ANR发生的根因,对安卓四大组件的实现流程是必须要了解的。

所以会写一系列的文章,来分析四大组件的实现原理,同时也会写文章来讲解四种类型的ANR是如何发生的。

学完了Service的启动原理,如果你想有更深一步的了解,就可以移步查看下面的文章。

ANR系列之五:Service类型ANR原理讲解_失落夏天的博客-CSDN博客

一.APP侧启动Service

其实启动service和启动Activity是很相似的,都是APP通知系统侧,由系统侧完成的整个流程,其实四个组件的启动流程都是类似这样的流程。

1.1 前台和后台启动

无论是Activity,还是service,还是Application,都继承自Context的抽象类,而Context的代理对象是ContextImpl,所以Activity可以使用ContextImpl中各种功能,就比如这里要介绍的启动前台/后台service。

Context在安卓中,使用了一种典型的代理模式,我们调用的startService或者startForegroundService方法,最终都会委托给ContextImpl中的startService和startForegroundService来处理的。我们就来看下ContextImpl中的这两个方法:

@Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, false, mUser);
    }

    @Override
    public ComponentName startForegroundService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, true, mUser);
    }

无论前台还是后台启动,其实最终都会走到一个方法中,只是配置参数的区别而已,最终都会走执行startServiceCommon方法。

1.2 startServiceCommon

该方法中,通过binder通知系统的AMS完成对应的service的启动操作:

private ComponentName startServiceCommon(Intent service, boolean requireForeground,
            UserHandle user) {
...
ComponentName cn = ActivityManager.getService().startService(
                    mMainThread.getApplicationThread(), service,
                    service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
                    getOpPackageName(), getAttributionTag(), user.getIdentifier());
}

接下来,通过AMS提供的binder引用,调用binder方法startServic通知到AMS,接下来,我们就看下系统侧是如何处理Service启动流程的。

二.系统侧分发处理Service的启动逻辑

系统侧的处理我主要分为3块来讲:

1.系统接受APP侧的通知并转发(2.1节);

2.系统侧委托ActiveServices负责完成的处理流程(2.2节);

3.收到APP侧执行完成的回调,进行收尾操作(2.3节)。

2.1 AMS接受启动service的通知

APP侧持有system_server进程的binder,上面讲到,它会通过binder方法startService完成对系统侧的通知。所以AMS的startService会收到这个通知。

我们看下代码,代码如下:

public class ActivityManagerService {   
    ...
    public ComponentName startService(IApplicationThread caller, Intent service,
                String resolvedType, boolean requireForeground, String callingPackage, int userId)
                throws TransactionTooLargeException {
                ...
                try {
                    res = mServices.startServiceLocked(caller, service,
                            resolvedType, callingPid, callingUid,
                            requireForeground, callingPackage, callingFeatureId, userId);
                } finally {
                    Binder.restoreCallingIdentity(origId);
                }
    }
}

public final class ActiveServices {    
    ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,...){
        ...
        final ComponentName realResult = startServiceInnerLocked(r, service, callingUid, callingPid, fgRequired, callerFg, allowBackgroundActivityStarts, backgroundActivityStartsToken);
        return realResult;
    }
    
    private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,...){
        ...
        String error = bringUpServiceLocked(r, service.getFlags(), callerFg, ...);
    }        
}

1.AMS会把整个service启动的逻辑交由ActiveServices来处理,调用到其startServiceLocked方法;

2.startServiceLocked方法中,会做很多位语句判断,处理异常的逻辑。而核心流程则在方法的尾部来处理,调用startServiceInnerLocked方法去负责具体的启动业务。

3.startServiceInnerLocked中做一些准备工作后,就把最终的启动任务交给了bringUpServiceLocked方法。这个方法的逻辑,我们下一小节来讲解。

2.2 分场景处理service启动

bringUpServiceLocked方法中,我们分成三种场景来讲:

1.service已经存在,并且已经绑定过,这时候直接唤起对应的service即可。

2.对应的应用进程存在,但是service未创建,这时候自然是创建对应的service。

3.对应的应用进程不存在,或者配置要求service单独进程。那这时候自然要先创建进程。

private String bringUpServiceLocked(...)
            throws TransactionTooLargeException {
        //第一种场景
        if (r.app != null && r.app.getThread() != null) {
            sendServiceArgsLocked(r, execInFg, false);
            return null;
        }
        //第二种场景
        if (!isolated) {
            if (app != null) {
                realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg,
                                enqueueOomAdj);
                return null;
            }
        }
        //第三种场景
        if (app == null && !permissionsReviewRequired && !packageFrozen) {
            if (r.isSdkSandbox) {
                ...
            }else{
                app = mAm.startProcessLocked(...);
            }
        }        
}

我们分开来一一讲解。

2.3 唤起已存在service的场景

bringUpServiceLocked方法中,首先就会对记录r进行判断,如果绑定了进程和binder,则说明对应的service已经存在了,则调用sendServiceArgsLocked发送通知。

//ActiveServices的bringUpServiceLocked方法
private String bringUpServiceLocked(...){
    if (r.app != null && r.app.getThread() != null) {
          sendServiceArgsLocked(r, execInFg, false);
          return null;
    }
}

然后sendServiceArgsLocked方法中,通知到APP,并且结束超时等待流程。

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
            boolean oomAdjusted) throws TransactionTooLargeException {
    ...
    Exception caughtException = null;
    try {
        r.app.getThread().scheduleServiceArgs(r, slice);
    }catch(Exception e){
        caughtException = e;
    }
    
    if (caughtException != null) {
        for (int i = 0, size = args.size(); i < size; i++) {
            serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
        }
    }

}

这里sendServiceArgsLocked方法是binder方法,会通知到ApplicationThread中的sendServiceArgsLocked方法。

如果通知成功没有发生异常,则调用serviceDoneExecutingLocked则会结束service超时计算流程。有没有觉得奇怪,这个流程好像并没有开启超时检测流程啊,确实是,但是不开启一样可以结束,只是没有效果而已。另外sendServiceArgsLocked流程其实是复用的,其它场景下会存在这种先开启检查,然后在调用serviceDoneExecutingLocked方法的流程。

2.4 创建service的场景

创建service的场景对应的是realStartServiceLocked方法,我总结为三个流程:

1.bumpServiceExecutingLocked,开启超时检查。

2.thread.scheduleCreateService通知APP一侧去创建Service。

3.通过sendServiceArgsLocked方法通知APP执行Service的生命流程。

private void realStartServiceLocked(ServiceRecord r, ProcessRecord app,
            IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg,
            boolean enqueueOomAdj) throws RemoteException {
        //1.启动超时检查
        bumpServiceExecutingLocked(r, execInFg, "create", null /* oomAdjReason */);
        ...
        //2.通知APP创建service
            thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                    app.mState.getReportedProcState());
            r.postNotification();
            created = true;
        ...
        //3.通知执行service生命流程
        sendServiceArgsLocked(r, execInFg, true);

       ...
    }

bumpServiceExecutingLocked方法我们这里就不细讲了,具体内容会在service的ANR原理一篇进行详细的讲解,这里只要知道,会开始一个超时检测即可。

调用binder方法scheduleCreateService则会通知到APP一侧去进行对应的创建。

sendServiceArgsLocked方法则在上一小节有介绍。

2.5 创建进程的场景

如果service对应的进程都未创建,则首先需要创建进程。

首先通过AMS创建进程,看进程是否创建成功。

如果失败,则记录日志并返回。

如果成功,则通过AMS创建进程,创建成功则返回进程对象并记录到对象r上;

最后成功的情况下,把启动记录对象加入到mPendingServices集合中,以供后续使用。

app = mAm.startProcessLocked(...);
if (app == null) {
    String msg = "Unable to launch app "...
    return msg;
}
if (isolated) {
   r.isolationHostProc = app;
}
//加入等待队列
if (!mPendingServices.contains(r)) {
    mPendingServices.add(r);
}

则AMS会请求zygote创建对应的应用进程,具体流程就不细讲了,有兴趣的可以看我另外一篇文章:android源码学习- APP启动流程(android12源码)_apk启动流程

当应用进程创建完成后,会在ActivityThread的main方法中会通过attachApplication通知系统进程已创建。attachApplication中会完成很多件事,其中有两件事分别为通知应用去创建application以及调用ActiveServices中的attachApplicationLocked方法,而后面这个方法,就是继续之前未完成的service启动流程,相关代码如下:

boolean attachApplicationLocked(ProcessRecord proc, String processName)
            throws RemoteException {
    if (mPendingServices.size() > 0) {
        ServiceRecord sr = null;
        for (int i=0; i<mPendingServices.size(); i++) {
            realStartServiceLocked(sr, proc, thread, pid, uidRecord,sr.createdFromFg,true);
        }
    }    

}

attachApplicationLocked中,会遍历之前添加启动对象的mPendingServices集合,然后依次调用realStartServiceLocked方法进行启动。

其实,目标进程不存在时,service的启动流程和发送静态广播以及启动Activity的流程是类似的,都是先创建进程,进程创建后再去继续未完成的启动流程。我们理解了其中之一后,再去理解其它的就会变得很容易。

2.6 小节

所以我们这里走一个总结,进入bringUpServiceLocked流程后,分成三种场景。

1.如果service已经存在,那么就直接走sendServiceArgsLocked流程通知service执行onStartCommand周期即可;

2.如果service不存在,但是进程存在,这时候就走scheduleCreateService流程先创建service,然后在执行1中流程,这里有一点值得注意的是,哪怕onCreate的时候阻塞了,onStartCommand的任务仍会到达,但不会执行。

3.如果进程都不存在,则先去创建进程,然后进程创建后绑定的时候,在去执行2中创建service的流程。

整体的流程图如下:

 

三.APP侧响应Service的流程

上面有介绍,系统侧通知APP进行service相关的操作,会有两种binder方法:

1.scheduleCreateService,执行Service的创建。

2.scheduleServiceArgs,执行Service生命流程,通知Service执行onStartCommand方法。

我们依次来讲:

3.1 执行Service创建流程

系统侧持有APP侧的binder,会通过scheduleCreateService这个binder方法通知APP一侧进行相应的操作。而APP侧,完成这个工作接收的就是ApplicationThread中的scheduleCreateService方法。该方法收到通知后,通过handler切换到主线程处理:

 public final void scheduleCreateService(IBinder token,
                ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
            updateProcessState(processState, false);
            CreateServiceData s = new CreateServiceData();
            s.token = token;
            s.info = info;
            s.compatInfo = compatInfo;

            sendMessage(H.CREATE_SERVICE, s);
        }

handle中,会切换到主线程执行ActivityThread的handleCreateService方法。

主要执行了如下的几段逻辑:

1.如果是首次创建App进程的话,则需要重新创建Application;

2.创建Service对象;

3.调用service的attach方法进行关联;

4.调用service的onCreate生命周期方法;

5.创建完成后,通过serviceDoneExecuting通知系统侧创建完成。

try {
            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

            Application app = packageInfo.makeApplication(false, mInstrumentation);

            final java.lang.ClassLoader cl;
            if (data.info.splitName != null) {
                cl = packageInfo.getSplitClassLoader(data.info.splitName);
            } else {
                cl = packageInfo.getClassLoader();
            }
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
            ContextImpl context = ContextImpl.getImpl(service
                    .createServiceBaseContext(this, packageInfo));
            if (data.info.splitName != null) {
                context = (ContextImpl) context.createContextForSplit(data.info.splitName);
            }
            if (data.info.attributionTags != null && data.info.attributionTags.length > 0) {
                final String attributionTag = data.info.attributionTags[0];
                context = (ContextImpl) context.createAttributionContext(attributionTag);
            }
            // Service resources must be initialized with the same loaders as the application
            // context.
            context.getResources().addLoaders(
                    app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

            context.setOuterContext(service);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
            service.onCreate();
            mServicesData.put(data.token, data);
            mServices.put(data.token, service);
            try {
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

3.2 执行Service生命周期流程

同样的,这里完成接受的是,仍然是ApplicationThread中的方法。这个流程中的接受方法是scheduleServiceArgs方法。

ApplicationThread中,收到通知后,通过handler把任务转交到主线程。

 public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) {
            List<ServiceStartArgs> list = args.getList();

            for (int i = 0; i < list.size(); i++) {
                ServiceStartArgs ssa = list.get(i);
                ServiceArgsData s = new ServiceArgsData();
                s.token = token;
                s.taskRemoved = ssa.taskRemoved;
                s.startId = ssa.startId;
                s.flags = ssa.flags;
                s.args = ssa.args;

                sendMessage(H.SERVICE_ARGS, s);
            }
        }

接下来handler中切换到主线程会执行ActivityThread的handleServiceArgs方法。

handleServiceArgs方法主要会完成以下几件事:

1.找到对应的service,调用起onStartCommand方法;

2.通知系统侧回调完成。

private void handleServiceArgs(ServiceArgsData data) {
        CreateServiceData createData = mServicesData.get(data.token);
        Service s = mServices.get(data.token);
        if (s != null) {
            try {
                if (data.args != null) {
                    data.args.setExtrasClassLoader(s.getClassLoader());
                    data.args.prepareToEnterProcess(isProtectedComponent(createData.info),
                            s.getAttributionSource());
                }
                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 {
                    ActivityManager.getService().serviceDoneExecuting(
                            data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to start service " + s
                            + " with " + data.args + ": " + e.toString(), e);
                }
            }
        }
    }

发我们发现,不论是创建service,还是通知执行service的生命流程,最终都执行了一个完成的通知,这有何意图呢?这个意图就是和ANR相关的,我们会在前言中介绍的另一篇文章service中ANR原理中来讲。

四.总结

前面一一讲了实现的原理,我们最后再来做一个总结,尽量用一张图+几句话的方式来概括。

 

1.无论前台启动还是后台启动,最终都会走到ContextImpl这个最终实现类中的方法,完成和AMS的交互。

2.AMS中ActiveServices类负责完成service的相关流程,它会判断service所处的状态。

流程1:如果service已创建,则直接通知APP执行其生命周期;

流程2:如果进程已创建service未创建,则通知APP创建,然后通知其执行生命周期;

流程3:如果进程都未创建,则先创建进程,再执行流程

3.APP收到创建或者生命周期的通知后,完成任务后通知回系统侧。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值