【源码解析】Service的onStartCommand返回值

一、引言

Service作为四大组件之一,在平时的开发中使用的频率仅次于Activity。但是,我们生成一个Service时,一般是不会重写它的onStartCommand方法的。
究竟这个方法的返回值有什么意义,Android系统为什么要给我们提供这样的一个方法呢?下面我们还是从源码中寻找答案。

二、源码分析

在之前【源码解析】Service的工作过程 的文章中我们分析过了Service的工作过程,知道Service的创建会走到ActiveService的realStartServiceLocked方法,我们就从该方法入手:

private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
    ...省略

    boolean created = false;
    try {
        ...省略
        mAm.notifyPackageUse(r.serviceInfo.packageName,
                             PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
        app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
        r.postNotification();
        created = true;
    } catch (DeadObjectException e) {
        Slog.w(TAG, "Application dead when creating service " + r);
        mAm.appDiedLocked(app);
        throw e;
    } finally {
        ...省略
    }

    if (r.whitelistManager) {
        app.whitelistManager = true;
    }

    requestServiceBindingsLocked(r, execInFg);

    updateServiceClientActivitiesLocked(app, null, true);

    if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                null, null, 0));
    }

    sendServiceArgsLocked(r, execInFg, true);

    ...省略
}

该方法通过app.thread.scheduleCreateService(app.thread是ActivityThread对象)创建了Service,然后调用了ActiveService的sendServiceArgsLocked方法,代码如下:

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
        boolean oomAdjusted) throws TransactionTooLargeException {
    final int N = r.pendingStarts.size();
    if (N == 0) {
        return;
    }

    ArrayList<ServiceStartArgs> args = new ArrayList<>();

    ...省略

    ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
    slice.setInlineCountLimit(4);
    Exception caughtException = null;
    try {
        r.app.thread.scheduleServiceArgs(r, slice);
    } catch (TransactionTooLargeException e) {
        ...省略
    } catch (RemoteException e) {
       ...省略
    } catch (Exception e) {
        ...省略
    }
    ...省略
}

sendServiceArgsLocked方法最终调用了r.app.thread.scheduleServiceArgs方法,也就是ActivityThread的scheduleServiceArgs方法,代码如下:

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

scheduleServiceArgs方法通过消息发送机制,发送一个标志位为H.SERVICE_ARGS的消息给到ActivityThread的内部类H处理,ActivityThread内部类H对于H.SERVICE_ARGS的处理逻辑是:

case SERVICE_ARGS:
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
    handleServiceArgs((ServiceArgsData)msg.obj);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    break;

在handleMessage中对于SERVICE_ARGS的情况,调用了handleServiceArgs方法,handleServiceArgs的代码如下:

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 {
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            ensureJitEnabled();
        } catch (Exception e) {
            if (!mInstrumentation.onException(s, e)) {
                throw new RuntimeException(
                        "Unable to start service " + s
                        + " with " + data.args + ": " + e.toString(), e);
            }
        }
    }
}

在上面的方法中显式的调用了Service的onStartCommand方法,得到一个整型返回值res,而我们本文需要分析的就是这个返回值。可以看到这个返回值res最终是作为了ActivityManager.getService().serviceDoneExecuting方法的参数,而ActivityManager.getService()就是ActivityManagerService对象,其中的serviceDoneExecuting方法如下所示:

public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
    synchronized(this) {
        if (!(token instanceof ServiceRecord)) {
            Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
            throw new IllegalArgumentException("Invalid service token");
        }
        mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res);
    }
}

在serviceDoneExecuting方法中又调用了ActiveServices的serviceDoneExecutingLocked方法,如下所示:

void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
    boolean inDestroying = mDestroyingServices.contains(r);
    if (r != null) {
        if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
            // This is a call from a service start...  take care of
            // book-keeping.
            r.callStart = true;
            switch (res) {
                case Service.START_STICKY_COMPATIBILITY:
                case Service.START_STICKY: {
                    // We are done with the associated start arguments.
                    r.findDeliveredStart(startId, true);
                    // Don't stop if killed.
                    r.stopIfKilled = false;
                    break;
                }
                case Service.START_NOT_STICKY: {
                    // We are done with the associated start arguments.
                    r.findDeliveredStart(startId, true);
                    if (r.getLastStartId() == startId) {
                        // There is no more work, and this service
                        // doesn't want to hang around if killed.
                        r.stopIfKilled = true;
                    }
                    break;
                }
                case Service.START_REDELIVER_INTENT: {
                    // We'll keep this item until they explicitly
                    // call stop for it, but keep track of the fact
                    // that it was delivered.
                    ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);
                    if (si != null) {
                        si.deliveryCount = 0;
                        si.doneExecutingCount++;
                        // Don't stop if killed.
                        r.stopIfKilled = true;
                    }
                    break;
                }
                case Service.START_TASK_REMOVED_COMPLETE: {
                    // Special processing for onTaskRemoved().  Don't
                    // impact normal onStartCommand() processing.
                    r.findDeliveredStart(startId, true);
                    break;
                }
                default:
                    throw new IllegalArgumentException(
                            "Unknown service start result: " + res);
            }
            if (res == Service.START_STICKY_COMPATIBILITY) {
                r.callStart = false;
            }
        } 
        ...省略
        final long origId = Binder.clearCallingIdentity();
        serviceDoneExecutingLocked(r, inDestroying, inDestroying);
        Binder.restoreCallingIdentity(origId);
    } else {
        Slog.w(TAG, "Done executing unknown service from pid "
                + Binder.getCallingPid());
    }
}

在serviceDoneExecutingLocked方法中,对于参数res的值做了不同的处理:

  • START_STICKY_COMPATIBILITY
    调用了ServiceRecord的findDeliveredStart方法将当前的Intent去掉,并将stopIfKilled置为false
    查看START_STICKY_COMPATIBILITY注释,说明如下:

其功能和START_STICKY一样,是START_STICKY的兼容版本,但是不保证onStartCommand方法会被调用。

  • START_STICKY
    调用了ServiceRecord的findDeliveredStart方法将当前的Intent去掉,并将stopIfKilled置为false
    查看START_STICKY注释,说明如下:

在Service启动后,如果该Service被系统杀死了,系统会重新创建该Service,onStartCommand方法会被调用,但是传给Service的intent为null。这种模式适合在用Service播放在后台播放音乐的场景。

  • START_NOT_STICKY
    调用了ServiceRecord的findDeliveredStart方法将当前的Intent去掉,并将stopIfKilled置为true
    查看START_NOT_STICKY注释,说明如下:

在Service启动后,如果该Service被系统杀死了,系统不会重新创建该Service。除非开发者明确地使用startService的方式启动该Service。这种模式适用在使用Service存储一些数据的操作,在系统杀死Service时,操作停止也可以接受的场景。

  • START_REDELIVER_INTENT
    调用了ServiceRecord的findDeliveredStart方法,但不去掉当前Intent,并将stopIfKilled置为true
    查看START_REDELIVER_INTENT注释,说明如下:

在Service启动后,如果该Service被系统杀死了,系统会重新创建该Service,onStartCommand方法会调用,并且将上次的Intent通过onStartCommand方法传递进来。除非开发者明确使用stopSelf方法停止该Service,否则其不会停止。

在对参数res的值做了不同的处理后,调用了serviceDoneExecutingLocked方法,全局搜索了一下ServiceRecord的stopIfKilled值的引用,只有ActiveService的killServicesLocked方法对其进行了引用,代码如下:

final void killServicesLocked(ProcessRecord app, boolean allowRestart) {
    ...省略
    for (int i=app.services.size()-1; i>=0; i--) {
        ServiceRecord sr = app.services.valueAt(i);

        // Unless the process is persistent, this process record is going away,
        // so make sure the service is cleaned out of it.
        if (!app.persistent) {
            app.services.removeAt(i);
        }

        // Sanity check: if the service listed for the app is not one
        // we actually are maintaining, just let it drop.
        final ServiceRecord curRec = smap.mServicesByName.get(sr.name);
        if (curRec != sr) {
            if (curRec != null) {
                Slog.wtf(TAG, "Service " + sr + " in process " + app
                        + " not same as in map: " + curRec);
            }
            continue;
        }

        // Any services running in the application may need to be placed
        // back in the pending list.
        if (allowRestart && sr.crashCount >= mAm.mConstants.BOUND_SERVICE_MAX_CRASH_RETRY
                && (sr.serviceInfo.applicationInfo.flags
                    &ApplicationInfo.FLAG_PERSISTENT) == 0) {
            Slog.w(TAG, "Service crashed " + sr.crashCount
                    + " times, stopping: " + sr);
            EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
                    sr.userId, sr.crashCount, sr.shortName, app.pid);
            bringDownServiceLocked(sr);
        } else if (!allowRestart
                || !mAm.mUserController.isUserRunningLocked(sr.userId, 0)) {
            bringDownServiceLocked(sr);
        } else {
            boolean canceled = scheduleServiceRestartLocked(sr, true);

            // Should the service remain running?  Note that in the
            // extreme case of so many attempts to deliver a command
            // that it failed we also will stop it here.
            if (sr.startRequested && (sr.stopIfKilled || canceled)) {
                if (sr.pendingStarts.size() == 0) {
                    sr.startRequested = false;
                    if (sr.tracker != null) {
                        sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
                                SystemClock.uptimeMillis());
                    }
                    if (!sr.hasAutoCreateConnections()) {
                        // Whoops, no reason to restart!
                        bringDownServiceLocked(sr);
                    }
                }
            }
        }
    }

    if (!allowRestart) {
        app.services.clear();

        // Make sure there are no more restarting services for this process.
        for (int i=mRestartingServices.size()-1; i>=0; i--) {
            ServiceRecord r = mRestartingServices.get(i);
            if (r.processName.equals(app.processName) &&
                    r.serviceInfo.applicationInfo.uid == app.info.uid) {
                mRestartingServices.remove(i);
                clearRestartingIfNeededLocked(r);
            }
        }
        for (int i=mPendingServices.size()-1; i>=0; i--) {
            ServiceRecord r = mPendingServices.get(i);
            if (r.processName.equals(app.processName) &&
                    r.serviceInfo.applicationInfo.uid == app.info.uid) {
                mPendingServices.remove(i);
            }
        }
    }

    // Make sure we have no more records on the stopping list.
    int i = mDestroyingServices.size();
    while (i > 0) {
        i--;
        ServiceRecord sr = mDestroyingServices.get(i);
        if (sr.app == app) {
            sr.forceClearTracker();
            mDestroyingServices.remove(i);
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "killServices remove destroying " + sr);
        }
    }

    app.executingServices.clear();
}

killServicesLocked方法对于ServiceRecord的stopIfKilled的逻辑看不懂,和Service中的注释对不上。后面有时间继续研究,如果有小伙伴知道的话,也请赐教,万分感谢!

三、总结

本文从源码的角度并结合源码注释解析了Service的onStartCommand的返回值,分析了不同的返回值对Service的重建有不同的效果。
针对该结论,写了一个小Demo进行验证。文章传送门:【Demo验证】Service的onStartCommand返回值

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容