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類型,你能夠容許用戶選擇一種。