Service的一些迷思

1.为什么调用stopService/unbindService之后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

猜你喜欢

转载自juejin.im/post/5be53084e51d45709d2efb6f
今日推荐