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.7 ClipboardService分析 - 图1

图 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是一个比较重要的常用类,很有必要掌握它的用法。