Android为复制粘贴提供了一个强大的基于剪切板的框架,它支持简单和复杂的数据类型,包括纯文本,复杂的数据结构,二进制流,甚至app资源文件。简单的文本数据直接存储在剪切板中,而复杂的数据则存储的是数据的引用,粘贴对象从content provider中获取数据。复制黏贴能够在应用内部和应用之间工做。html
复制黏贴使用content providers,本文假设读者对content provider是熟悉的。java
The Clipboard Frameworkandroid
当你是用剪切板框架的时候,卡发着将数据放入一个剪切对象中,而后将剪切对象放置在系统的剪切板上,剪切对象有如下三种形式:数据库
1)文本:一个文本字符串。开发者能够直接将字符串放进一个剪切对象中,而后放置到剪切板上去。要粘贴字符串,只须要从剪切板上拿到剪切对象,将字符串粘贴到应用的存储上就好;数组
2)任何形式的URI:这对于从content provider中复制复杂的数据来讲很是重要,为了复制数据,你能够将一个Uri对象放入一个剪切对象。粘贴的时候能够解析这个Uri对象。若是是content provider的地址,则解析数据,进行复制;数据结构
3)Intent:支持复制应用的快捷方式,为了复制数据,开发者能够建立一个Intent放入剪切对象。app
剪切板一次只能存储一个剪切对象。框架
若是你要容许用户粘贴数据到你的应用,你没必要处理全部类型的数据,你在容许用户粘贴以前能够去检测剪切板上数据的类型,若是得不到肯定的数据类型,剪切对象也有一个Metadata告诉你是什么类型的数据。Metadata能够帮助你决定你的应用是否是能够用剪切板数据作一些事情。socket
你或许也会容许用户无论数据格式直接粘贴数据,这种状况下你能够强制剪切板数据转化成文本表示,而后粘贴文本。ide
Clipboard Classess
这部分讨论剪切板模块将要用到的类。
ClipboardManager
在Android系统中,系统的剪切板是用全局的ClipboardManager类来替代的,开发者不须要直接实例化这个类,而是使用getSystemService(CLIPBOARD_SERVICE).
ClipData,ClipData.Item and ClipDescription
为了往Clipboard上面添加数据,必须建立一个包含数据描述和数据自己的ClipData对象。Clipboard一次只能容纳一个ClipData对象,一个ClipData包含了一个ClipDescription对象和多个ClipData.Item对象。
一个AclipDescription对象化包含了关于剪切板的Metadata,特殊状况下,是一个MIME类型数组。当你在剪切板上放置数据的时候,粘贴数据的应用是能够获取这个数组来检测本身是否能处理其中数据的。一个ClipData.Item包含文本,Uri和Itent。
开发者能够添加不止一个ClipData.Item对象,这容许用户一次复制粘贴多个选择内容,好比你有一个list对象容许用户进行一次选择多个,就能够一次粘贴多个选项。为此你为每个list item建立一个ClipData.Item对象,而后添加到ClipData对象中去。你可使用newPlainText,newUri和newIntent方便的建立一些ClipData对象。
Coercing the clipboard data to text
开发者可使用ClipData.Item.coerceToText()方法将剪切板上的内容强制转化为字符串,返回一个CharSequence对象。
1)文本:则直接返回文本;
2)URI:若是provider能够返回一个text stream,就直接获取文本流,不然就返回Uri.toString();
3)Intent:转化成Intent URI返回回来,结果和方法Intent.toUri(URI_INTENT_SCHME)同样;
Copying to the Clipboard
下面详细描述一下复制的流程:
1)若是你使用content URI复制数据,创建一个content provider。详见例子:Note Pad。
2)得到ClipManager对象:
1 // Gets a handle to the clipboard service. 2 ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
3)将数据复制到ClipData对象中去:
文本:
1 // Creates a new text clip to put on the clipboard 2 ClipData clip = ClipData.newPlainText("simple text","Hello, World!");
URI:
下面的代码片断式将记录ID编码到content URI中造成一个URI来构造的,详情见:Encoding an identifier on the URI:
1 // Creates a Uri based on a base Uri and a record ID based on the contact's last name 2 // Declares the base URI string 3 private static final String CONTACTS = "content://com.example.contacts"; 4 5 // Declares a path string for URIs that you use to copy data 6 private static final String COPY_PATH = "/copy"; 7 8 // Declares the Uri to paste to the clipboard 9 Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName); 10 11 ... 12 13 // Creates a new URI clip object. The system uses the anonymous getContentResolver() object to 14 // get MIME types from provider. The clip object's label is "URI", and its data is 15 // the Uri previously created. 16 ClipData clip = ClipData.newUri(getContentResolver(),"URI",copyUri);
Intent:
1 // Creates the Intent 2 Intent appIntent = new Intent(this, com.example.demo.myapplication.class); 3 4 ... 5 6 // Creates a clip object with the Intent in it. Its label is "Intent" and its data is 7 // the Intent object created previously 8 ClipData clip = ClipData.newIntent("Intent",appIntent);
4)在剪切板上放置新的剪切对象:
1 // Set the clipboard's primary clip. 2 clipboard.setPrimaryClip(clip);
Pasting form the Clipboard
1)粘贴纯文本:
第一步:获取全局的ClipbaodrManager对象:
1 ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); 2 String pasteData = "";
2)决定是否须要在当前的Activity容许粘贴行为,你须要验证数据的格式:
1 // Gets the ID of the "paste" menu item 2 MenuItem mPasteItem = menu.findItem(R.id.menu_paste); 3 4 // If the clipboard doesn't contain data, disable the paste menu item. 5 // If it does contain data, decide if you can handle the data. 6 if (!(clipboard.hasPrimaryClip())) { 7 8 mPasteItem.setEnabled(false); 9 10 } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) { 11 12 // This disables the paste menu item, since the clipboard has data but it is not plain text 13 mPasteItem.setEnabled(false); 14 } else { 15 16 // This enables the paste menu item, since the clipboard contains plain text. 17 mPasteItem.setEnabled(true); 18 } 19 }
3)粘贴数据,只有当可粘贴的时候该功能才会可用。这时候你不知道剪切板上存在的是纯文本仍是指向纯文本的URI,下面的代码能够测试这个,可是它只显示了如何处理纯文本:
1 // Responds to the user selecting "paste" 2 case R.id.menu_paste: 3 4 // Examines the item on the clipboard. If getText() does not return null, the clip item contains the 5 // text. Assumes that this application can only handle one item at a time. 6 ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); 7 8 // Gets the clipboard as text. 9 pasteData = item.getText(); 10 11 // If the string contains data, then the paste operation is done 12 if (pasteData != null) { 13 return; 14 15 // The clipboard does not contain text. If it contains a URI, attempts to get data from it 16 } else { 17 Uri pasteUri = item.getUri(); 18 19 // If the URI contains something, try to get text from it 20 if (pasteUri != null) { 21 22 // calls a routine to resolve the URI and get data from it. This routine is not 23 // presented here. 24 pasteData = resolveUri(Uri); 25 return; 26 } else { 27 28 // Something is wrong. The MIME type was plain text, but the clipboard does not contain either 29 // text or a Uri. Report an error. 30 Log.e("Clipboard contains an invalid data type"); 31 return; 32 } 33 }
Pasting data from a content URI
若是剪切板上是一个URI,你的应用也会处理它,那就建立一个ContentResolver,而后调用什么时候的content provider方法去获取数据。下面的流程描述了如何去作:
1)声明一个全局变量:
1 // Declares a MIME type constant to match against the MIME types offered by the provider 2 public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
2)获取ClipboardManager和content resolver:
1 // Gets a handle to the Clipboard Manager 2 ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); 3 4 // Gets a content resolver instance 5 ContentResolver cr = getContentResolver();
3)获取URI:
1 // Gets the clipboard data from the clipboard 2 ClipData clip = clipboard.getPrimaryClip(); 3 4 if (clip != null) { 5 6 // Gets the first item from the clipboard data 7 ClipData.Item item = clip.getItemAt(0); 8 9 // Tries to get the item's contents as a URI 10 Uri pasteUri = item.getUri();
4)调用方法getType(Uri)看看URI是否是一个content URI,若是uri不是指向一个有效的content provider,这个方法返回null。
// If the clipboard contains a URI reference if (pasteUri != null) { // Is this a content URI? String uriMimeType = cr.getType(pasteUri);
5)测试content provider是否提供一个当前应用理解的MIME类型,若是是,则查询数据:
1 // If the return value is not null, the Uri is a content Uri 2 if (uriMimeType != null) { 3 4 // Does the content provider offer a MIME type that the current application can use? 5 if (uriMimeType.equals(MIME_TYPE_CONTACT)) { 6 7 // Get the data from the content provider. 8 Cursor pasteCursor = cr.query(uri, null, null, null, null); 9 10 // If the Cursor contains data, move to the first record 11 if (pasteCursor != null) { 12 if (pasteCursor.moveToFirst()) { 13 14 // get the data from the Cursor here. The code will vary according to the 15 // format of the data model. 16 } 17 } 18 19 // close the Cursor 20 pasteCursor.close(); 21 } 22 } 23 } 24 }
Psting an Intent
大体流程和前面描述的一致,下面详述:
1 // Gets a handle to the Clipboard Manager 2 ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); 3 4 // Checks to see if the clip item contains an Intent, by testing to see if getIntent() returns null 5 Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent(); 6 7 if (pasteIntent != null) { 8 9 // handle the Intent 10 11 } else { 12 13 // ignore the clipboard, or issue an error if your application was expecting an Intent to be 14 // on the clipboard 15 }
Using Content Providers to Copy Complex Data
Content Provider支持复杂的数据复制粘贴,好比数据库记录或者文件流。为了复制这样的数据,开发者能够将一个content URI放置在剪切板上,而后粘贴它的应用就可使用Uri去检索数据。
因为粘贴的应用只有数据的Uri,它还须要知道去粘贴数据的哪一部分,你能够在URI自己中提供信息,或者你能够提供一个特殊的URI来指明指定的想要复制的数据,采用什么技术取决你的数据组织类型。
Encoding an identifier on the URI
在数据的URI自己中编码一个识别器(其实就是资源ID)是复制数据比较有用的一种技术。content provider就可使用这个识别器去检索特定的数据,粘贴的应用不须要知道识别器的存在,它只须要获取这个uri,而后将它交给复制应用的content provider而后就能够得到数据。
一般咱们将识别器放置在content URI的后面,假设你的provider URI以下:
1 "content://com.example.contacts"
若是你想在后面编码一个名字,你只须要这样:
1 String uriString = "content://com.example.contacts" + "/" + "Smith" 2 3 // uriString now contains content://com.example.contacts/Smith. 4 5 // Generates a uri object from the string representation 6 Uri copyUri = Uri.parse(uriString);
若是你已经在使用一个content provider,你或许会想添加一个新的URI路径来指明这个URI是用来复制的,举个例子,加入你已经拥有下面这些URI path:
1 "content://com.example.contacts"/people 2 "content://com.example.contacts"/people/detail 3 "content://com.example.contacts"/people/images
你能够添加另一个路径来指明这是专门用来复制的uris:
1 "content://com.example.contacts/copying"
而后你就能够匹配“copy”来肯定它是否是用来复制的。
若是你已经在使用一个content provider,内部数据库或者一个内部的表组织数据,你可使用这种编码技术来复制。在这种状况下,你拥有不少片断的数据能够复制,而且能够为每个片断设置一个ID。而后能够为一个查询根据ID查询并返回须要的数据。
若是你没有不少的数据,就不须要ID,你能够简单的使用一个指向你的provider的URI。你的provider就会查询全部包含的数据来响应查询请求。详情请见NotePad例子。
Copying data structures
为了复制复杂的数据,你能够创建一个content provider。你须要考虑程序当前的状态:
1)若是你已经有了一个content provider,你能够增长它的功能。你或许只须要更改它的query()方法来处理来自其他应用要求粘贴数据的URIs,你或许想要修改处理匹配“copy”URI模式的方法;
2)若是你的应用维护着一个内部的数据库,你或许想要把数据库转换成content provider来简化复制功能;
3)若是你正在使用一个数据库,你能够实现一个简单的content provider专门给应用提供数据;
在content provider中,你至少须要覆写下面的方法:
1)query():粘贴的应用会假设使用uri就能够得到想要粘贴的数据,为了支持复制,你须要该方法能够检测用于复制的uri;
2)getType():该方法须要返回一个MIME类型的数据来指明你想要复制的数据类型,newUri()方法调用getType()来将MIME类型放置到新的ClipData对象中去;
下面的代码片断展现了如何复制复杂的数据:
1)在应用的全局区域,声明一个用于指明复制数据的Uri字符串和路径,也要为要复制的数据声明一个MIME类型:
1 / Declares the base URI string 2 private static final String CONTACTS = "content://com.example.contacts"; 3 4 // Declares a path string for URIs that you use to copy data 5 private static final String COPY_PATH = "/copy"; 6 7 // Declares a MIME type for the copied data 8 public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
2)在用户复制数据段额Activity中,创建复制数据的代码,当须要复制数据的时候,将uri放置到剪切板上去:
public class MyCopyActivity extends Activity { ... // The user has selected a name and is requesting a copy. case R.id.menu_copy: // Appends the last name to the base URI // The name is stored in "lastName" uriString = CONTACTS + COPY_PATH + "/" + lastName; // Parses the string into a URI Uri copyUri = Uri.parse(uriString); // Gets a handle to the clipboard service. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri); // Set the clipboard's primary clip. clipboard.setPrimaryClip(clip);
3)在你的content provider的全局区域,建立一个URI matcher和一个URI pattern来匹配你放置到剪切板上的URIs:
1 public class MyCopyProvider extends ContentProvider { 2 3 ... 4 5 // A Uri Match object that simplifies matching content URIs to patterns. 6 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 7 8 // An integer to use in switching based on the incoming URI pattern 9 private static final int GET_SINGLE_CONTACT = 0; 10 11 ... 12 13 // Adds a matcher for the content URI. It matches 14 // "content://com.example.contacts/copy/*" 15 sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
4)创建query()方法。这个方法能够处理不一样的URI patterns,决定于你怎么去编码,下面写明的是如何处理用于复制操做的URI:
1 // Sets up your provider's query() method. 2 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 3 String sortOrder) { 4 5 ... 6 7 // Switch based on the incoming content URI 8 switch (sUriMatcher.match(uri)) { 9 10 case GET_SINGLE_CONTACT: 11 12 // query and return the contact for the requested name. Here you would decode 13 // the incoming URI, query the data model based on the last name, and return the result 14 // as a Cursor. 15 16 ... 17 18 }
5)创建getType()方法,返回一个正确的MIME类型给复制数据:
1 / Sets up your provider's getType() method. 2 public String getType(Uri uri) { 3 4 ... 5 6 switch (sUriMatcher.match(uri)) { 7 8 case GET_SINGLE_CONTACT: 9 10 return (MIME_TYPE_CONTACT);
Copying data streams
能够复制粘贴大亮的文本和二进制数据,数据能够具备如下的类型:
1)存储在实际设备上的文件;
2)从sockets中接收到的数据流;
3)在数据库系统下存储的大量数据;
一个一个专门提供数据流的content provider经过诸如AssetFileDescriptor的文件描述符而不是Cursor来提供数据访问,粘贴的应用使用文件描述符来读取数据流。
为了让应用可使用provider来复制数据,遵循以下步骤:
1)为要放置到剪切板上的数据流创建一个content provider,能够有如下选择:a)加入一个ID号,就像前面讨论的同样,而后在你的provider表中维护这样一个ID和对应的流的名字;b)直接将流的名字编码在URI中;c)使用一个老是返回当前流的特殊的URI,若是你这样作,你必须记住,你须要不论何时经过剪切板复制数据,你都须要更新你的provider去指向一个不一样的数据流;
2)为每一种类型的数据流提供一个MIME类型,粘贴的应用须要这个恶数据;
3)实现ContentProvider的一个方法来为数据流返回一个文件描述符,若是你在content URI后面编码了一个ID,你就可使用这个方法来肯定要打开哪个文件流;
4)为为了将数据流复制到剪切板,构建一个content URI放置到剪切板上;
为了粘贴一个数据流,应用从剪切板上获取clip,获取URI,调用contentprovider的文件描述符获取方法来打开数据流。ContentProvider方法调用对应的方法,将URI传递给它,你的provider返回文件描述符,而后粘贴应用就负责从数据流中读取数据;
下面的列表展现了content provider中最重要的文件描述符方法,每个方法都有相对应的ContentResolver方法,只须要在后面加上Descriptor,举个例子,在ContentProvider中有方法openAssetFile(),ContentResolver中则有方法openAssetFileDescriptor():
1)openTypeAssetFile():这个方法须要返回一个asset文件描述符,可是只有当provider支持它的MIME类型的时候。调用者(粘贴应用)提供一个MIME类型模式。若是content provider(复制应用)能够提供该类型的文件,九返回一个文件描述符,不然抛出异常。
2)openAssetFile():这个方法是上一个方法更加泛化的形式,它不过滤文件类型,可是读取部分文件;
3)openFile():更加泛化,不能读取部分文件;
你能够选择使用openPipeHelper()方法。它容许应用程序使用一个pipe在后台读取数据流,为了使用该方法,你须要实现ContentProvider.PipeDataWriter接口。一样在NotePad中也有相应的使用:在NotePadProvider.java中的openTypeAssetFile()方法。
Designing Effective Copy/Paste Functionality
为了设计高效的复制粘贴功能,记住如下几点:
1)任什么时候候,剪切板上只有一个剪切数据;
2)ClipData.Item的设计是为了一次剪切多个选择项,而不是为了容纳一次剪切的多个不一样的部分,这就说明,ClipData.Item中容纳的应该所有是同一个类型的数据;
3)当你提供数据,你能够提供不一样的MIME表示,将你支持的MIME类型添加到ClipDescription中,而后在你的content provider中实现你的MIME类型;
4)当你从剪切板复制数据下来的时候,你的应用负责检查数据的类型,决定使用哪个,即便剪切板上有数据,用户也要求粘贴,你的应用也不必定须要执行黏贴操做,你应该只在数据类型匹配的时候执行操做,固然你也能够强制转化为文本。若是你的应用支持不止一种MIME类型,你能够容许用户选择一种。