Copy and Paste
版本:Android 4.0 r1
- 用于复制粘贴数据的基于剪贴板的框架。
- 同时支持简单和复杂的数据,包括文本串、复杂的数据结构、文本和二进制流数据、程序 asset。
- 直接从剪贴板复制粘贴简单文本。
- 用content provider复制粘贴复杂数据。
- 需要API 11版本。
在本文中
ClipData、ClipDescription和ClipData.Item
关键类
相关示例
参阅
Android为复制和粘贴提供了强大的基于剪贴板的框架。该框架同时支持简单和复杂数据类型,包括文本字符串、复杂数据结构、文本和二进制流数据、甚至程序asset。简单文本数据直接存储于剪贴板内,而复杂数据则保存为一个引用,粘贴应用可利用内容提供器(content provider)进行解析。复制和粘贴可在应用程序内部或多个实现此框架的应用程序之间进行。
因为框架的一部分用到了content provider,本文讨论的内容与Android Content Provider API有些类似,这些API已在Content Provider一章中描述。
使用剪贴板框架时,可把数据放入剪辑(clip)对象,然后把该对象放入系统级剪贴板中。clip对象可以是以下三种形式:
Text
文本串。可以直接把字符串放入clip对象,然后把clip对象放入剪贴板中。需要粘贴字符串时,从剪贴板中获取clip对象,然后把字符串拷贝到应用程序的存储中即可。
URI
Uri对象表示任何形式的URI。这主要用于从content provider复制复杂数据。复制数据时,先将Uri对象放入一个clip对象,再把clip对象放入剪贴板中。需要粘贴数据时,先获取clip对象,再获取Uri对象,再把Uri解析到诸如content provider之类的数据源中,然后就能从此数据源中把数据拷贝到应用程序的存储中了。
Intent
一个Intent。这为复制应用程序快捷方式提供了支持。复制数据时,先创建一个Intent并把它放入一个clip对象,再把clip对象放入剪贴板。需要粘贴数据时,可以获取clip 对象并把Intent对象拷贝到应用程序的内存中。
剪贴板同时仅保存一个clip对象。当应用程序把一个clip对象放入剪贴板时,前一个clip对象将会消失。
如果要允许用户把数据粘贴到应用程序中,没必要对所有类型数据都进行处理。在让用户选择粘贴之前,可以先对剪贴板中的数据进行检测。除了包含指定格式的数据之外,clip对象还包含了元数据,它能说明数据是属于哪种格式或MIME类型。此元数据有助于应用程序确定对剪贴板数据执行合适的操作。比如,假定应用程序主要是处理文本信息的,那就可以忽略包含URI和Intent的clip对象。
还有可能要允许用户只粘贴文本,而不论剪贴板中的数据格式如何。要实现这个目标,可以把剪贴板数据强制转换为文本格式,然后粘贴这些文本。这将在将剪贴板内数据强制转换为文本一节中描述。
本节描述类剪贴板框架中所用到的类。
在Android系统中,系统剪贴板由全局ClipboardManager类表示。此类不需要直接初始化,而是提交getSystemService(CLIPBOARD_SERVICE)来获取一个引用。
ClipData、ClipData.Item和ClipDescription
要把数据加入剪贴板,可以创建一个ClipData对象,它包含了数据描述信息和数据本身。剪贴板每次只保存一个ClipData对象。一个ClipData包含了一个ClipDescription对象和一个以上的ClipData.Item对象。
ClipDescription对象包含了clip相关的元数据信息。特别重要的是,它包含了一个clip数据所对应MIME类型的数组。把clip放入剪贴板后,粘贴应用程序可以利用此数组,程序可以检查此数组以确定其对这些MIME类型的处理能力。
一个ClipData.Item对象包含了文本、URI或Intent数据:
Text
一个CharSequence。
URI
一个Uri。虽然可以是任何URI值,但通常是包含一个content provider URI。提供数据的应用程序把URI放入剪贴板。需要粘贴数据的应用程序从剪贴板中获取URI,并将它用于访问content provider(或者其它数据源)并取回数据。
Intent
一个Intent。本数据类型允许把应用程序的快捷方式复制到剪贴板中。用户可以在后续的使用中把快捷方式粘贴到其它应用程序中。
可以在一个clip中加入多个ClipData.Item对象。这使得用户可以把多个选中值复制为同一个clip。比如,如果有一个列表widget允许用户一次选择多个选项,就可以把所有选中项一次复制到剪贴板中。要实现这一点,为每个列表项创建一个ClipData.Item,然后把这些ClipData.Item对象加入ClipData对象即可。
ClipData常用方法
ClipData类提供了便捷的静态方法来创建一个ClipData对象,附带一个ClipData.Item对象和一个简单的ClipDescription对象作为参数:
返回包含了单个ClipData.Item对象的ClipData对象,此item对象内含一个文本字符串。ClipDescription对象的标签设置为label。ClipDescription的MIME 类型是MIMETYPE_TEXT_PLAIN。
newPlainText()用于创建一个文本字符串clip。
返回一个包含单个ClipData.Item的ClipData对象,此item对象内含一个URI。ClipDescription对象的标签设置为label。如果URI是一个content类型的URI (Uri.getScheme()返回content:),则该方法将用resolver的ContentResolver对象从content provider中获取可用的MIME类型,并把这些类型保存到ClipDescription中。对于不是content:的URI ,该方法把MIME type设置为MIMETYPE_TEXT_URILIST。
newUri()用于创建一个URI的clip,特别是content: URI。
返回一个包含单个ClipData.Item的ClipData对象,此item对象内含一个Intent。ClipDescription对象的标签设置为label。MIME类型置为MIMETYPE_TEXT_INTENT。
newIntent()用于创建一个Intent对象的clip。
如果应用程序仅需处理文本,可用ClipData.Item.coerceToText()方法转换一下,就可以从剪贴板复制非文本数据。
本方法将把ClipData.Item中的数据转换为文本,并且返回一个CharSequence。ClipData.Item.coerceToText()的返回值根据ClipData.Item中的数据格式来确定:
Text
如果ClipData.Item是文本(getText()不为null),则coerceToText()返回文本。
URI
ClipData.Item是个URI(getUri()不为null),则coerceToText()会尝试将其视为content URI:
·
·
·
Intent
如果ClipData.Item是个Intent(getIntent()不为null),则coerceToText()将其转换为Intent URI后返回。该字符串表示形式与Intent.toUri(URI_INTENT_SCHEME)的返回值一致。
剪贴板的整体框架如图1所示。在复制数据时,应用程序将ClipData对象放入全局的ClipboardManager剪贴板中。ClipData内含了一个或多个ClipData.Item对象,以及一个ClipDescription对象。在粘贴数据时,应用程序先获取ClipData,从ClipDescription中读取MIME类型信息,再从ClipData.Item中或ClipData.Item指向的content provider中读取数据。
图1. Android剪贴板框架
如前所述,如果要把数据复制到剪贴板(剪贴板句柄指向全局的ClipboardManager对象),需要创建一个ClipData对象,再把一个ClipDescription和一个以上的ClipData.Item对象加入其中,最后把这个ClipData添加到ClipboardManager对象中去。详细情况描述如下:
1.
Note Pad例程是一个使用content provider复制粘贴数据的示例。NotePadProvider类实现了content provider。NotePad类定义了该provider和其它应用程序的交互方式,包括所用的MIME类型。
2.
...
// 如果用户选中复制
case R.id.menu_copy:
// 获取一个剪贴板服务的句柄
ClipboardManager clipboard =(ClipboardManager)
getSystemService(Context.CLIPBOARD_SERVICE);
3.
- 对于文本
// 创建一个新的文本clip,用于放入剪贴板中
ClipData clip =ClipData.newPlainText("simple text","Hello, World!");
- 对于URI
以下代码段通过构造了一个URI ,把记录ID编入provider用到的content URI中。更多的技术细节在把ID编入URI一节中详述:
// 根据基本的Uri和联系人姓氏的记录ID,创建一个Uri
// 声明基本URI字符串
privatestaticfinalString CONTACTS ="content://com.example.contacts";
// 声明URI的路径字符串,用于复制数据
privatestaticfinalString COPY_PATH ="/copy";
// 声明需粘帖到剪贴板中的Uri
Uri copyUri =Uri.parse(CONTACTS + COPY_PATH +"/"+ lastName);
... // 新建一个URI clip对象。系统使用匿名
// getContentResolver()对象读取provider 的MIME类型。
// clip对象的标签是"URI",数据是上述创建的Uri
ClipData clip =ClipData.newUri(getContentResolver(),"URI",copyUri);
- 对于Intent
以下代码段构造了一个Intent并将其放入clip对象中:
// 创建Intent
Intent appIntent =newIntent(this,com.example.demo.myapplication.class);
...
// 创建一个包含Intent的clip对象,标签是"Intent",
// 数据是上述Intent对象
ClipData clip =ClipData.newIntent("Intent",appIntent);
4.
// 设置剪贴板的主clip.
clipboard.setPrimaryClip(clip);
如上所述,要从剪贴板粘贴数据,需先获得全局剪贴板对象,再获取clip对象,然后查找其中的数据,最后从clip对象中把数据拷贝到自己的存储中。本节详细描述了如何针对三种剪贴板数据的格式进行这些操作。
要粘贴普通文本,首先获得全局剪贴板,并确认能否返回普通文本,然后获取clip对象,用getText()把其中文本拷贝到自己的存储中,步骤如下:
1.
ClipboardManager clipboard =(ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
String pasteData ="";
2.
// 获取“粘贴”菜单项的ID
MenuItem mPasteItem = menu.findItem(R.id.menu_paste);
// 如果剪贴板中没有数据,则禁用“粘贴”菜单项
// 如果包含数据,确定是否能够处理数据
if(!(clipboard.hasPrimaryClip())){
}
3.
// 对用户选中“粘贴”作出响应
case R.id.menu_paste:
// 则表示clip项包含了文本。假定本程序一次仅处理一个数据项
pasteData = item.getText();
if(pasteData !=null){
}else{
}
从content URI粘贴数据
如果ClipData.Item对象包含了一个content URI,程序也确认能处理其中的MIME 类型,则可创建一个ContentResolver并调用合适的content provider方法来获取数据。
以下过程描述了如何根据剪贴板中的content URI从content provider获取数据。程序会先检查MIME类型,确认能够使用该provider提供的数据:
1.
// 声明MIME类型常量,对应provider所提供的MIME类型
publicstaticfinalString MIME_TYPE_CONTACT="vnd.android.cursor.item/vnd.example.contact"
2.
// 获取Clipboard Manager的句柄
ClipboardManager clipboard =(ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
ContentResolver cr = getContentResolver();
3.
// 获取剪贴板数据
ClipData clip = clipboard.getPrimaryClip();
4.
5.
}
粘贴Intent
要粘贴一个意图Intent,首先获取全局剪贴板,再检查ClipData.Item对象是否包含Intent。然后调用getIntent()来把Intent拷贝到程序的存储中。以下代码段演示了这一过程:
// 获取Clipboard Manager句柄
ClipboardManager clipboard =(ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
// 通过判断getIntent()是否为null
Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent();
if(pasteIntent !=null){
}else{
}
利用Content Provider复制复杂数据
Content provider支持对复杂数据的复制,比如数据库记录或文件流之类。在复制数据时,把一个content URI放入剪贴板中。然后粘贴应用程序从剪贴板中获取此URI,并用它读取数据库数据或者文件流的描述符。
由于粘贴应用程序只是将content URI作为数据读取,因此还需要知道应获取数据的哪些部分。可以把所需数据的ID编入URI本身,或者让URI只精确返回所需复制部分的数据。采用哪种方式取决于数据是如何组织在一起的。
以下章节描述了如何创建URI、如何提供复杂数据、如何提供文件流。以下假定开发人员已经熟悉了content provider的一般设计规则。
将ID置入URI编码
利用URI把数据复制到剪贴板时,有一项实用技术就是把数据的ID置入URI编码本身。然后content provider可以从URI得到ID,并用ID来读取数据。粘贴应用程序不必知道ID是否存在,要做的所有操作就是从剪贴板获取“引用”(URI加ID),并把它交给content provider,然后读取数据。
通常的编码方式是把ID附在content URI后面。比如,假定已定义provider URI如下:
"content://com.example.contacts"
如果需要把名称置入URI,应该使用如下代码:
String uriString ="content://com.example.contacts"+"/"+"Smith"
// 现在uriString包含了content://com.example.contacts/Smith
// 根据字符串变量创建uri对象
Uri copyUri =Uri.parse(uriString);
如果程序中已经用到了content provider,只需新增一个指示复制数据的URI路径。比如,假设已存在以下URI路径:
"content://com.example.contacts"/people
"content://com.example.contacts"/people/detail
"content://com.example.contacts"/people/images
下面加入一个用于复制的URI:
"content://com.example.contacts/copying"
可以利用模式匹配来检测到"copy" URI,并用代码进行复制和粘贴处理。
如果是用content provider、内部数据库、内部表来组织数据,通常即可使用以上编码技术。这种情况下会有多块数据需要复制,很可能每块数据都会有一个唯一ID。当粘贴应用程序查询时,可以用此ID查找并返回数据。
如果没有多块数据需要复制,可能就不必把ID进行编码。可以简单地使用能够唯一标识provider的URI即可。查询时,provider将会返回它包含的数据。
Note Pad示例程序中就用ID获取了单条记录,以便从note列表中打开一条note。此示例使用了SQL数据库中的_id字段,不过可以根据需要使用任何数字或字符ID。
为了复制和粘贴复杂数据,应该创建一个content provider,它是ContentProvider组件的子类。还应该将编码后的URI放入剪贴板,此URI指向了需提供的正确记录。此外,还必须考虑应用程序的现状:
·
·
·
在content provider中,至少会需要覆盖以下方法:
粘贴应用程序将会假定,通过此方法能够获取剪贴板中URI指定的数据。为了支持复制功能,应该在本方法中对包含指定“复制”路径的URI进行检测。然后,程序可以创建一个“复制”URI并放入剪贴板中,此URI包含了复制路径和指向实际复制记录的指针。
本方法应该返回MIME类型或者需复制数据的类型。为了把MIME类型放入新建的ClipData对象,newUri()方法将会调用getType()。
复杂数据的MIME类型在Content Providers一节中描述。
注意,其它的content provider方法是没必要用到的,比如insert()或update()。粘贴应用程序只需要获取所用的MIME类型并从provider拷贝数据。如果已经实现了这些方法,那它们也不会影响复制操作。
以下代码段演示了如何建立复制复杂数据的应用程序:
1.
// 声明基本URI字符串
privatestaticfinalString CONTACTS ="content://com.example.contacts";
// 声明用于复制数据的URI路径字符串
privatestaticfinalString COPY_PATH ="/copy";
// 声明需复制数据的MIME类型
publicstaticfinalString MIME_TYPE_CONTACT="vnd.android.cursor.item/vnd.example.contact"
2.
publicclassMyCopyActivityextendsActivity{
// 用户已经选中了姓名并请求复制
case<="" span="">.id.menu_copy:
3.
publicclassMyCopyProviderextendsContentProvider{>{
privatestaticfinalUriMatcher sURIMatcher=newUriMatcher(UriMatcher.NO_MATCH););// 整数,用于URI模式的开关变量。
privatestaticfinalint GET_SINGLE_CONTACT =>=0;.....// 为content URI加入匹配器。
// 匹配"content://com.example.contacts/copy/*"
sUriMatcher.addURI(CONTACTS,"names/*", GET_SINGLE_CONTACT);
4.
// 建立provider的query()方法
publicCursor query(Uri uri,String[] projection,String selection,String[]selectionArgs,
}
5.
// 建立provider的getType()方法
publicString getType(Uri uri){
从content URI粘贴数据一节描述了如何从剪贴板获取content URI,并用它读取并粘贴数据。
可以将大量的文本和二进制数据以流的形式进行复制和粘贴。数据可以具有如下格式:
·
·
·
数据流content provider用文件描述符对象而不是Cursor< span="">AssetFileDescriptor。粘贴应用程序利用此文件描述符来读取数据流。流。
要建立用provider复制数据流的应用程序,请按以下步骤进行:
1.
o
o
o
2.
3.
4.
要粘贴数据流时,应用程序先从剪贴板获取clip,读取URI,然后调用ContentResolver文件描述符方法打开流。ContentResolver方法将调用相应的ContentProvider方法,把content URI传入其中。provider把文件描述符返回给ContentResolver方法。这时粘贴程序就能读取流中的数据了。
以下列表展示了对content provider而言最重要的文件描述符方法。每个方法都有后缀名为“Descriptor< span="">ContentResolver方法与之相对应。比如,模拟openAssetFile()的ContentResolver是openAssetFileDescriptor():
openTypedAssetFile()
仅当给出的MIME类型能被provider支持时,本方法返回一个asset文件描述符。调用方(执行粘贴的应用)提供MIME类型模式。如果能提供此类型MIME的话,content provider(把URI复制到剪贴板的应用)将返回一个AssetFileDescriptor文件句柄,不能提供则抛出异常。常。
本方法用于处理文件的片段,可以用它读取content provider拷入剪贴板的asset。
openAssetFile()
本方法是比openTypedAssetFile()更通用的方法。它不对支持的MIME类型进行判断过滤,但可用于读取文件的片段。
openFile()
这是比openAssetFile()更加通用的格式。它不能读取文件片段。
可以选用openPipeHelper()方法作为文件描述符方法,这让粘贴应用可以用管道在后台读取流数据。使用此方法需要实现ContentProvider.PipeDataWriter接口。在Note Pad示例程序中有相关例程,位于NotePadProvider.java中的openTypedAssetFile()方法。
要为应用程序设计高效的复制与粘贴功能,请记住以下几点:
·
·
·
·