3.7 ClipboardService分析
ClipboardService(CBS)是Android系统中的元老级服务了,自Android 1.0起就支持剪贴功能。在Android 4.0中再遇见它时,此功能已有了长足改进。先来看和剪贴功能有关的类的家族图谱,如图3-4所示。
由图3-4可知:
在Android 4.0中,源码中的content.ClipboardManager类继承自text.ClipboardManager类。从text.ClipboardManager类的命名中也可看出,早期的剪贴功能只支持文本。ClipboardManager由剪贴板服务的客户端使用,在SDK中有相应文档说明。
新增一个ClipData类,它就像一个容器,管理存储在其中的数据信息。具体的数据信息存储在ClipData的成员变量mItems中。该变量是一个Item类型的数组,每一个Item代表一项数据。
ClipDescription类用来描述一个ClipData中的数据类型。目前,Android系统中的剪贴板支持3种类型的数据(Text、Intent,以及URL列表)。
剪贴板的服务端由ClipboardService实现。
图 3-4 和剪贴功能有关的类
下边我们通过一个例子来分析CBS。该例子来源于Android SDK提供的一段示例代码(取自SDK安装目录/sample/android-14/NotePad)。
3.7.1 复制数据到剪贴板
我们截取与复制操作相关的代码,具体如下:
[—>sample]
//首先获取能与CBS交互的ClipboardManager对象
ClipboardManager clipboard=(ClipboardManager)
getSystemService(Context.CLIPBOARD_SERVICE);
//调用setPrimaryClip函数,参数是ClipData.newUri函数的返回值
clipboard.setPrimaryClip(ClipData.newUri(
getContentResolver(),"Note",noteUri));
ClipData的newUri是一个static函数,用于返回一个存储URI数据类型的ClipData,代码如下。根据前文所述可知,ClipData对象装载的就是可保存在剪贴板中的数据。
[—>ClipData.java]
static public ClipData newUri(ContentResolver resolver, CharSequence label,
Uri uri){
Item item=new Item(uri);//创建一个Item,将uri直接传给它的构造函数
String[]mimeTypes=null;
/*
下边代码的功能是获取这个uri代表的数据的MIME类型。先尝试利用ContentResolver
从ContentProvider那查询,如果查询不到,则设置mimeTypes为
MIMETYPES_TEXT_URILIST,它的定义是new String[“text/uri-list”]
*/
if("content".equals(uri.getScheme())){
String realType=resolver.getType(uri);
//查询该uri所指向的数据的mimeTypes
mimeTypes=resolver.getStreamTypes(uri,"/");
if(mimeTypes==null){
if(realType!=null){
mimeTypes=new String[]{
realType, ClipDescription.MIMETYPE_TEXT_URILIST};
}
}else{
……
}
if(mimeTypes==null){
mimeTypes=MIMETYPES_TEXT_URILIST;
}
//创建一个ClipData对象
return new ClipData(label, mimeTypes, item);
}
//ClipData的构造函数
public ClipData(CharSequence label, String[]mimeTypes, Item item){
mClipDescription=new ClipDescription(label, mimeTypes);
……
mIcon=null;
mItems.add(item);//将item对象添加到mItems数组中
}
newUri函数的主要功能在于,获得uri所指向的数据的数据类型。对于使用剪切板服务的程序来说,了解剪切板中数据的数据类型相当重要,因为这样可以判断自己能否处理这种类型的数据。
注意 uri指向数据的位置,这和PC上文件的存储位置类似,例如c:/dfp。MIME则表示该数据的数据类型。在Windows平台上是采用后缀名来表示文件类型的,前面提到的C盘下的DFP文件,后缀是.wav,表示该文件是一个WAV格式音频。
对于剪切板来说,数据源由uri指定,数据类型由MIME表示,两者缺一不可。
获得一个ClipData后,将调用setPrimaryClip函数,将数据传递到CBS。setPrimaryClip的代码如下:
[—>ClibpboardManger.java:setPrimaryClip]
public void setPrimaryClip(ClipData clip){
try{
//跨Binder调用,先要把参数打包。有兴趣的读者可以查看writToParcel函数
getService().setPrimaryClip(clip);
}catch(RemoteException e){
}
}
通过Binder发送setPrimaryClip请求后,由CBS完成实际功能,代码如下:
[—>ClipboardService.java:setPrimaryClip]
public void setPrimaryClip(ClipData clip){
synchronized(this){
……
//权限检查,在3.7.3节中将单独分析
checkDataOwnerLocked(clip, Binder.getCallingUid());
*/
//和权限相关,后续会分析
clearActiveOwnersLocked();
//保存新的clipData到mPrimaryClip中
mPrimaryClip=clip;
/*
mPrimaryClipListeners是一个RemoteCallbackList数组,
当CBS中的ClipData发生变化时,CBS需要向那些监控剪切板的
客户端发送通知。客户端通过addPrimaryClipChangedListener函数
注册回调
*/
final int n=mPrimaryClipListeners.beginBroadcast();
for(int i=0;i<n;i++){
try{
//通知客户端,剪切板的内容发生变化
mPrimaryClipListeners.getBroadcastItem(i).
dispatchPrimaryClipChanged();
}……
}
mPrimaryClipListeners.finishBroadcast();
}
}
setPrimaryClip比较简单。但是由于新增支持URI和Intent这两种数据类型,因此在安全性方面还有一些需要考虑的地方。这部分内容我们放到3.7.3节中分析。
注意 RemoteCallbackList是一个比较重要的常用类,很有必要掌握它的用法。