注:安卓设备7.0 support-v4-25.3.1
想写一个音乐播放器,看google15年推荐的用MediaBrowserService 和 MediaBrowser,所以想着用一下对我而言新的技术,写完了才发现有坑啊。
bug描述:在音乐列表页启动之前,按下home键切换到后台,再切换回来,启动列表页,发现没数据啊!MediaBrowserService的onLoadChildren根本没执行。
原因:
在Activity进入后台的时候,在onStop时MediaBrowser.disconnect(),调用远程的unregisterCallbacks,会将MediaBrowserServiceCompat.mConnection中的ConnectionRecord清除掉。
Activity回到前台,在onStart中判断时候连接,进行MediaBrowser.connect(),而这个时候,是不会像第一次启动时候,调用registerCallbacks的,因为MediaBrowser.onServiceConnected()中的bundle返回为空了,所以无法在
MediaBrowserImplApi21::onConnected()的时候registerCallbackMessenger,就不能在Service端中添加ConnectionRecord,MediaBrowser.addSubscription的时候,onLoadChildren前,查不到相应的ConnectionRecord,所以就无法onLoadChildren()
我的解决办法:
我是参考谷歌的Demo写的(地址),在我写博客的这个时间<2017年05月22日11:59:29>之前,它的demo也是存在这个问题的,所以我建议在onDesytroy中,进行MediaBrowser.disconnect(),在onCreate中,进行MediaBrowser.connect()。
2017年05月22日15:00:33 更新:
在代码里溜达了一圈,原来是在onGetRoot方法中,返回的了new BrowserRoot("***", null),第二个参数不能为null,这里要返回服务端的MessengerImpl,MediaBrowserCompat.onConnected()这里要拿到第二个参数,取出serviceBinder不为空才可以注册成功,注册成功才能回调onLoadChildren(),但是要怎么拿到MessengerImpl呢?除非你能拿到ServiceHandler mHandler = new ServiceHandler() 这个mHandler对象,所以还是按照上面的办法来做吧。
新的问题:第二个参数为null时,为什么第一次启动的时候可以注册成功呢?
第一次创建MediaBrowser的时候,mRootHint中有个参数{EXTRA_CLIENT_VERSION,1},
这个参数在MediaBrowserServiceCompatApi21.BrowserRoot onGetRoot中判断EXTRA_CLIENT_VERSION不为0时候,会在bundle中添加{EXTRA_MESSENGER_BINDER,iBinder},
public MediaBrowserServiceCompatApi21.BrowserRoot onGetRoot(
String clientPackageName, int clientUid, Bundle rootHints) {
Bundle rootExtras = null;
//第一次进来的时候EXTRA_CLIENT_VERSION为1,
//所以rootExtras中有mMessenger.getBinder()这样一个binder
//从后台切换回来后,rootHints是一个空的bundle,还没找到为空的原因,所以这里不会添加binder
if (rootHints != null && rootHints.getInt(EXTRA_CLIENT_VERSION, 0) != 0) {
rootHints.remove(EXTRA_CLIENT_VERSION);
mMessenger = new Messenger(mHandler);
rootExtras = new Bundle();
rootExtras.putInt(EXTRA_SERVICE_VERSION, SERVICE_VERSION_CURRENT);
BundleCompat.putBinder(rootExtras, EXTRA_MESSENGER_BINDER, mMessenger.getBinder());
}
//这个回调的是你自己实现的onGetRoot方法,很多时候可能你返回的不是null
BrowserRoot root = MediaBrowserServiceCompat.this.onGetRoot(
clientPackageName, clientUid, rootHints);
if (root == null) {
return null;
}
//第一次进来的时候rootExtras不为null,所以第一次返回了带有binder的BrowserRoot
//在MediaBrowser.onServiceConnected()中接受到赋值给mExtras了,所以可以拿到binder,
//在MediaBrowserCompat.onConnected()中通过MediaBrower拿到了binder
//binder不为空,才能new Messenger(binder),通发送注册CallBack的消息
//从后台切换回来后,rootExtras为null,就返回了你自己实现onGetRoot中的内容
if (rootExtras == null) {
rootExtras = root.getExtras();
} else if (root.getExtras() != null) {
rootExtras.putAll(root.getExtras());
}
return new MediaBrowserServiceCompatApi21.BrowserRoot(
root.getRootId(), rootExtras);
}
我可能发现了这个包的一个bug,为什么从后台切换回来后rootHint就为空了呢?
MediaBrowser.java
private class MediaServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(final ComponentName name, final IBinder binder) {
postOrRun(new Runnable() {
@Override
public void run() {
//注意mRootHints的地址
mServiceBinder.connect(mContext.getPackageName(), mRootHints,
mServiceCallbacks);
}
}
});
}
MediaBrowserService.java
private class ServiceBinder extends IMediaBrowserService.Stub {
@Override
public void connect(final String pkg, final Bundle rootHints,
final IMediaBrowserServiceCallbacks callbacks) {
mHandler.post(new Runnable() {
@Override
public void run() {
//这里的地址是和上面穿过来的地址是一样的
connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints);
}
}
}
MediaBrowserServiceCompat.java
public MediaBrowserServiceCompatApi21.BrowserRoot onGetRoot(
String clientPackageName, int clientUid, Bundle rootHints) {
if (rootHints != null && rootHints.getInt(EXTRA_CLIENT_VERSION, 0) != 0) {
//这里SeriveBinder是MediaBrowserServiceCompatApi21的内部类,
//所以这个rootHints的地址相当于是MediaBrowser里的mRootHint
//这里相当于把MediaBrowser里的mRootHint的值给清除了
//所以从后台切回来你根本拿不到ServiceHandler的Messager
rootHints.remove(EXTRA_CLIENT_VERSION);
}
}
有其他看法的同学,欢迎留言讨论
2017年05月26日08:10:05
https://issuetracker.google.com/issues/37122290
这个问题确实存在,但是看2017年March月关闭了,但是没解决这个问题
看官方的26.0.0并未提到该问题的修复,所以目前我的App的解决办法connect写在onCreate里,disconnect写在onDestroy里,因为我设计的只有一个MainActiviy。或者你再次connect的时候,new一个新的MediaBrowserCompat,再或者你可以把Service端放在另一个进程中
2018年01月11日17:31:24:
使用API 26 和对应的SupportV4 26.0.2以上版本可以解决
代码里加了对mCurConnection这样一个东西。