8.2.4 数据更新通知机制总结和深入探讨
总结上面所描述的数据更新通知机制的流程,如图8-2所示。
图 8-2 数据更新通知的流程图
从前面的代码介绍和图8-2所示的流程来看,Android平台中的数据更新通知机制较为简单。不过此处尚有几个问题想和读者一起探讨。
问题一:由图8-2可知,客户端2调用ContentProvider的update函数将间接触发客户端1的ContentObserver的onChange函数被调用。如果客户端1在onChange函数中耗时过长,会不会导致客户端2阻塞在update函数中呢?
想到这个问题的读者应该是非常细致和认真的了。确实,从前面所示的代码和流程图来看,这个情况几乎是必然会发生的,但是实际上该问题并不存在,原因在下面这一段代码中:
[—>IContentObserver. java:Proxy:onChange]
private static class Proxy implements android.database.IContentObserver{
private android.os.IBinder mRemote;
……
public void onChange(boolean selfUpdate)
throws android.os.RemoteException{
android.os.Parcel_data=android.os.Parcel.obtain();
try{
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(((selfUpdate)?(1):(0)));
//调用客户端1的ContentObserver Bn端的onChange函数
mRemote.transact(Stub.TRANSACTION_onChange,_data, null,
android.os.IBinder.FLAG_ONEWAY);
}finally{
_data.recycle();
}
}
……
}
以上代码告诉我们,ContentService在调用客户端注册的IContentObserver的onChange函数时,使用了FLAG_ONEWAY标志。根据第2章对该标志的介绍(参见2.2.1节),使用该标志的Binder调用只需将请求发给Binder驱动即可,无须等待客户端onChange函数的返回。因此,即使客户端1在onChange中恶意浪费时间,也不会阻塞客户端2的update函数了。
问题二:假设服务端有一项功能,需要客户端通过某种方式来控制它的开闭(即禁止或使用该功能),考虑一下有几种方式能实现这个控制机制?这是一个开放性问题,需要集思广益。
Android平台上至少有3种方法可以实现这个控制机制。
第一种:服务端实现一个API函数,客户端直接调用这个函数来控制。
第二种:客户端发送指定的广播,而服务端注册该广播的接收者,然后在这个广播接收者的onReceive函数中处理。
第三种:服务端输出一个ContentProvider,并为这个功能输出一个uri地址,然后注册一个ContentObserver。客户端可通过更新数据的方式来触发服务端ContentObserver的onChange函数,服务端在该函数中做对应处理即可。
在Android代码中,这三种方法都有使用。下面将以Settings应用中和USB相关的功能设置为例来观察第一种和第三种方法的使用情况。
第一个实例和Android 4.0中新支持的USB MTP/PTP功能有关,相关代码如下:
[—>UsbSettings. java:onPreferenceTreeClick]
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
Preference preference){
……
if(preference==mMtp){
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true);
updateToggles(UsbManager.USB_FUNCTION_MTP);
}else if(preference==mPtp){
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true);
updateToggles(UsbManager.USB_FUNCTION_PTP);
}
return true;
}
由以上代码可知,如果用户从Settings界面中选择了使能MTP,将直接调用UsbManager的setCurrentFunction来使能MTP功能。这个函数的Bn端实现在UsbService中。
不过,同样是USB相关的功能控制,ADB的开关控制却采用了第三种方法,相关代码为:
[—>DevelopmentSettings. java:onClick]
public void onClick(DialogInterface dialog, int which){
if(which==DialogInterface.BUTTON_POSITIVE){
mOkClicked=true;
//设置Settings数据库ADB对应的数据项值为1
Settings.Secure.putInt(getActivity().getContentResolver(),
Settings.Secure.ADB_ENABLED,1);
}else
mEnableAdb.setChecked(false);//界面更新
}
上面的数据项更新操作将导致UsbDeviceManager做对应处理,其相关代码如下:
[—>UsbDeviceManager. java:onChange]
private class AdbSettingsObserver extends ContentObserver{
……
public void onChange(boolean selfChange){
//从数据库中取出对应项的值
boolean enable=(Settings.Secure.getInt(mContentResolver,
Settings.Secure.ADB_ENABLED,0)>0);
//发送MSG_ENABLE_ADB消息,UsbDeviceManager将处理此消息
mHandler.sendMessage(MSG_ENABLE_ADB, enable);
}
}
同样是USB相关的功能,Settings应用却采用了两种截然不同的方法来处理它们。这种做法为笔者目前所从事的项目中USB扩展功能的实现带来了极大困扰,因为我们想采用统一的方法来处理USB相关功能。到底应采用哪种方法比较合适呢?第一种方法和第三种方法各自的适用场景是什么?读者不妨仔细思考并将结论与大家分享。
我们在第7章中分析Cursor query函数时曾看到过ContentObserver的身影,但是并没有对其进行详细分析。如果现在回过头去分析query函数流程中和ContentObserver相关的部分,则所涉及的流程可能比本节内容还要多。