Service的一些迷思

栏目: Android · 发布时间: 7年前

内容简介:通过之前对Service销毁流程的分析,有两个非常关键的变量:通过全局搜索发现,该字段只有在

通过之前对Service销毁流程的分析, stopServiceunbindService 最终都会进入到 ActiveServices.bringDownServiceIfNeededLocked 方法中,该方法会判断当前的 Service 是否满足销毁条件,其中的核心方法便是 isServiceNeeded

private final boolean isServiceNeeded(ServiceRecord r, boolean knowConn, boolean hasConn) {
    // Are we still explicitly being asked to run?
    if (r.startRequested) {
        return true;
    }

    // Is someone still bound to us keepign us running?
    if (!knowConn) {
        hasConn = r.hasAutoCreateConnections();
    }
    if (hasConn) {
        return true;
    }

    return false;
}
复制代码

有两个非常关键的变量: ServiceRecord.startRequestedhasConn ,前者与 start 有关,后者与 bind 有关,只有两者都为 false 才能销毁一个 Service 。 我们先来看看 startRequested

ServiceRecord.startRequested

通过全局搜索发现,该字段只有在 ActiveServices.startServiceLocked 方法中,也即是 start 流程中会被置为 true 。 在 ActiveServices.stopServiceLockedActiveServices.stopServiceTokenLockedActiveServices.killServicesLocked 这三个方法中会被置为false, ActiveServices.stopServiceTokenLocked 是在 Service 调用 stopSelf 时会触发的,而 ActiveServices.killServicesLocked 则是在清理应用(内存不足等场景)的时候触发。

简单来说 ServiceRecord.startRequested 会在 start 流程中被置为 true ,在 stop 流程中置为 false 。因此,无论你之前调用过多少次 startService ,只要你调了一次 stopService (之后没有再调用 startService ),那么 startRequested 就被置为了 false 。** startRequested 的值取决于最后一次调用的是 startService 还是 stopService

hasConn

该字段的值跟 ServiceRecord.hasAutoCreateConnection 方法的返回值有关

public boolean hasAutoCreateConnections() {
    // XXX should probably keep a count of the number of auto-create
    // connections directly in the service.
    for (int conni=connections.size()-1; conni>=0; conni--) {
        ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
        for (int i=0; i<cr.size(); i++) {
            //这个flags就是调用bindService时使用的flags
            if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) {
                return true;
            }
        }
    }
    return false;
}
复制代码

该方法内部会遍历所有 bind 至当前服务的连接,如果还存在任一连接,其调用 bindService 时使用的 flags 包含 BIND_AUTO_CREATE 标志,则返回 true ,否则返回 false

总结

我们以具体场景来分析怎样才能销毁一个服务:

  1. 只是用了 startService 来启动服务。 这种场景下,只需要调用 stopService 就可以正常销毁服务
  2. 只是用了 bindService 启动服务 这种场景下,只需要调用对应的 unbindService 即可、
  3. 同时使用了 startServicebindService 这种场景想要关闭服务的话,首先要调用 stopService ,其次还需要确保之前使用 BIND_AUTO_CREATE 进行绑定的客户端解绑( unbindService )即可。

2.为啥多次调用bindServcie,而onBind只触发了一次

在Service启动流程中有一个 realStartServiceLocked 方法,在服务进程启动完毕之后,会调用该方法继续服务启动的流程。 realStartServiceLocked 内部调用了一个名为 requestServiceBindingsLocked 的方法处理 bind 请求。重新贴一下该方法代码:

private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
            throws TransactionTooLargeException {
    for (int i=r.bindings.size()-1; i>=0; i--) {
        IntentBindRecord ibr = r.bindings.valueAt(i);
        //该方法内部会通过跨进程调用ApplicationThread.scheduleBindService
        //来回调Service.onBind方法
        if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
            break;
        }
    }
}
复制代码

可以看到这里有一个 for 循环,这说明了 Service.onBind 被多次回调是可能的。那么问题就变成了 ServiceRecord.bindings 什么时候会保存多个值呢? 对 bindings 字段的 put 操作只发生在 retrieveAppBindingLocked 方法中,该方法是在 bind 流程中的 ActiveServices.bindServiceLocked 方法中被调用的。 贴下代码

public AppBindRecord retrieveAppBindingLocked(Intent intent,//客户端发起bind请求所使用的Intent
            ProcessRecord app) {//客户端进程记录
    Intent.FilterComparison filter = new Intent.FilterComparison(intent);
    IntentBindRecord i = bindings.get(filter);
    if (i == null) {
        i = new IntentBindRecord(this, filter);
        bindings.put(filter, i);
    }
    AppBindRecord a = i.apps.get(app);
    if (a != null) {
        return a;
    }
    a = new AppBindRecord(this, i, app);
    i.apps.put(app, a);
    return a;
}
复制代码

可以看到该方法首先将 intent 封装成了一个 FilterComparison 对象作为 key ,然后去 bindings 中检索,如果没有对应的值就会创建一个值。 再来看看 FilterComparison.equals 方法,因为只有创建出不同的 FilterComparison 实例, bindings 中才会保存多个值。

//Intent$FilterComparison.java
public boolean equals(Object obj) {
    if (obj instanceof FilterComparison) {
        Intent other = ((FilterComparison) obj).mIntent;
        return mIntent.filterEquals(other);
    }
    return false;
}

//Intent.java
public boolean filterEquals(Intent other) {
    if (other == null) {
        return false;
    }
    if (!Objects.equals(this.mAction, other.mAction)) return false;
    if (!Objects.equals(this.mData, other.mData)) return false;
    if (!Objects.equals(this.mType, other.mType)) return false;
    if (!Objects.equals(this.mPackage, other.mPackage)) return false;
    if (!Objects.equals(this.mComponent, other.mComponent)) return false;
    if (!Objects.equals(this.mCategories, other.mCategories)) return false;

    return true;
}
复制代码

可以看到, FilterComparison 的比较其实是跟 Intent 密切相关的。 Intent 内部 mActionmDatamTypemPackagemComponentmCategories 中的任意字段发生变化,就会产生两个不同的 FilterComparison 实例。

结论

在调用 bindService 时,改变一下 Intent 内部的一些值,就可以触发多次 Service.onBind

复盘

知道了结论,我们来复盘一下,多次使用同一个 IntentbindService 的问题 通常我们是以下面这种方式来构造 Intent

Intent intent = new Intent(activity, DemoService.class);

//Intent.java
public Intent(Context packageContext, Class<?> cls) {
    mComponent = new ComponentName(packageContext, cls);
}
复制代码

这种方式初始化 Intent ,最终会将构造函数的入参保存成 mComponent

第一次进入 bind 流程之后,调用 retrieveAppBindingLocked 肯定会为 bindings 生成一条新的 IntentBindRecord 记录。 这时候如果服务已经启动,就会马上进入 requestServiceBindingLocked 方法

private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
            boolean execInFg, boolean rebind) throws TransactionTooLargeException {
    //...
    //requested此时为false
    if ((!i.requested || rebind) && i.apps.size() > 0) {
        try {
            //...
            r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                    r.app.repProcState);
            if (!rebind) {
                //触发onBind之后requested被置为了true
                i.requested = true;
            }
            i.hasBound = true;
            i.doRebind = false;
        } catch (TransactionTooLargeException e) {
            //...
        } catch (RemoteException e) {
            //...
        }
    }
    return true;
}
复制代码

由此可见,如果使用相同的 Intent 请求 bind ,那么第二次进来 requested 已经是 true 了,便不会触发 Service.onBind


以上所述就是小编给大家介绍的《Service的一些迷思》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming in Haskell

Programming in Haskell

Graham Hutton / Cambridge University Press / 2007-1-18 / GBP 34.99

Haskell is one of the leading languages for teaching functional programming, enabling students to write simpler and cleaner code, and to learn how to structure and reason about programs. This introduc......一起来看看 《Programming in Haskell》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具