关于BroadcastReceiver与Service交互时调用peekService返回null的疑问及解析

大家都清楚由于onReceive()函数是由进程的主线程调用的,生命期在10s左右,如果遇上了耗时操作,官网的BroadcastReceiver中onReceive(context,intent)部分建议如下:

in particular, for interacting with services, you should use startService(Intent) instead of bindService(Intent, ServiceConnection, int). If you wish to interact with a service that is already running, you can use peekService(Context, Intent)

就是说涉及到跟service交互的话,不要用bindService方法,因为可能binder对象还没返回回来这个receiver就killed掉了。建议使用peekService(context,intent)方法,这个方法还是比较少用到的,也不清楚这个函数返回的Ibinder对象来自于哪儿。所以下面写了个demo来看看这个peekService怎么用.

从官网提供来看:

Provide a binder to an already-running service. This method is synchronous and will not start the target service if it is not present, so it is safe to call from onReceive(Context, Intent).

可见我们在调用peekService前需要保证intent参数内的service是一个已经启动的service,peekService是一个同步的操作,所以在onReceive函数内部执行时安全的。

写了一个小demo如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
activity{
...
...
//绑定一个button的onClick: sendBroadcast
public void sendBroadcast(View view) {
//先启动一个service
Intent intent1 = new Intent();
intent1.setClass(MainActivity.this, MyService.class);
bindService(intent1, connection, BIND_AUTO_CREATE);
//发送广播
Intent intent = new Intent();
intent.setAction("com.example.zhangxiaang.broadcastreceiverdemo.intent.MyReceiver");
intent.putExtra("name", "zhangxiang");
sendBroadcast(intent);
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//do nothing
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
MyService{
...
...
@Override
public IBinder onBind(Intent intent) {
MyBinder binder = new MyBinder();
return binder;
}
class MyBinder extends Binder {
public void downloadImg() {
//耗时操作开始
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(1000 * 13);
Log.e(TAG, "13s已经过去了,但是onReceive函数依旧可以接收到任务返回的结果");
}
}).start();
}
}
}
MyReceive{
...
...
@Override
public void onReceive(Context context, Intent intent) {
Intent intent1 = new Intent();
intent1.setClass(context, MyService.class);
//确保MyService已经启动了,否则会导致peekService()返回null
MyService.MyBinder binder = (MyService.MyBinder) peekService(context, intent1);
if (binder != null) {
binder.downloadImg();
} else Toast.makeText(context, "binder == null", Toast.LENGTH_LONG).show();
}
}

当我触发sendBroadcast()函数后,打印的内容如下

E/MyService: onCreate()
E/MyService: onBind()…//停止13s
E/MyService: 13s已经过去了,但是onReceive函数依旧可以接收任务返回的结果
可见在onReceive 函数里调用peekService函数并没有再次调用已经启动的service的onBind()函数。那么在peekService函数这个binder是如何不调用onBind()而获取到的呢?
我尝试看源码

1
2
3
4
5
6
7
8
9
10
11
public IBinder peekService(Context myContext, Intent service) {
IActivityManager am = ActivityManagerNative.getDefault();
IBinder binder = null;
try {
service.prepareToLeaveProcess();
binder = am.peekService(service, service.resolveTypeIfNeeded(
myContext.getContentResolver()), myContext.getOpPackageName());
} catch (RemoteException e) {
}
return binder;
}

到了am.peekService()这里就无法继续的深入寻找原理,很惭愧。还请大神们多多指教!!!


更新:good tips for this problem
提示我们需要注意,如果再次启动这个service导致执行的是reStart()那么MyService.MyBinder binder = (MyService.MyBinder) peekService(context, intent1);返回的binder就是一个null对象,因为跳过了onBind()函数,无法生成一个binder.