2013年4月2日 星期二

[Android] Android Developer Note Text and Input 1

Text and Input

使用文字服務來加上方便的功能到你的app,
像是複製/貼上,以及拼字檢查等。
你也可以開發你自己的文字服務來提供客製的IMEs(輸入法)、
字典,以及拼字檢查器,作為可以發布給使用者的apps。

Copy and Paste:

Android提供了一個強大的基於剪貼簿的複製/貼上的framework。
它同時提供了簡單和複雜兩種資料型態,
包括了文字strings、複雜的資料結構、
文字及二元串流資料,甚至是app assets。
簡單的文字資料是直接存在剪貼簿裡的,
而複雜的資料是以貼上的app用content provider來解析,
作為一個參照(reference)來儲存的。
複製和貼上可以在一個app或多個app之間來做操作,
只要那些apps有實作這個framework。

因為一部分的framework使用了content providers,
這個標題會假定您已經對於Android Content Provider API有所熟悉了。
(我就是要跳著看怎樣不行歐= =++++)
相關的資料是在Content Providers裡所描述的。

The Clipboard Framework

當你使用了剪貼簿的framework,你將資料放進了一個clip物件,
然後將這個clip物件放到系統廣域(system-wide)的剪貼簿。
clip物件可以選用下列三種格式之一:
Text:
    一個文字string。你將string直接放進clip物件裡,
然後放到剪貼簿上。要貼上string時,從剪貼簿取得clip物件,
然後將string複製到你的app的儲存的地方。

URI:
    一個Uri物件代表任何格式的URI,這是主要作為
從content provider作為複製複雜的資料的用途的。
要複製資料,將一個Uri物件放到clip物件,
然後將clip物件放到剪貼簿上。要貼上資料,
先取得clip物件,拿到Uri物件,將其解析成資料source,
像是content provider,並且從source複製資料進你的app儲存空間。

Intent:
    一個Intent。這支援了複製app捷徑。
要複製資料的話,新增一個Intent,將其放到clip物件,
然後將clip物件放到剪貼簿上。
要貼上資料的話,拿到clip物件後,
將Intent物件複製進app的記憶區域。

剪貼簿一個時間只保存一個clip物件。
當一個app將clip物件放到剪貼簿上時,
前一個clip物件會消失。

如果你要允許使用者在你的app裡貼上資料的話,
你不需要去處理所有種類的資料。
你可以在你給使用者選項做貼上前,
檢驗剪貼簿上的資料。
除了有特定的資料格式以外,clip物件也包含metadata,
它會告訴你是什麼MIME種類,或者哪些種類可用。
這個metadata幫你決定你的app,
是否可以用剪貼簿資料來做有用的事情。
舉例來說,如果你有一個app主要是處理文字的話,
你也許想要忽略那種含有URI或Intent的clip物件。

你也許也想要讓使用者貼上文字,
且忽略掉原本剪貼版上的資料格式。
要這麼做的話,你可以強迫剪貼簿資料轉成文字表達,
然後再貼上文字。
這個部分在下面的Coercing the clipboard to text這個段落會說明。

Clipboard Classes:

這個段落描述了被剪貼簿的framework所使用的classes。

ClipboardManager
在Android系統裡,
系統剪貼簿是由global ClipboardManager class所表示的。
你不須直接實例化這個class;
取而代之的做法是,
藉由調用getSystemService(CLIPBOARD_SERVICE)來得到reference。

ClipData, ClipData.Item, and ClipDescription
要將資料加到剪貼簿,
製作一個含有資料描述和資料本身的ClipData物件。
剪貼簿一次只保留一個ClipData
一個ClipData包含一個ClipDescription物件,
以及一或多個ClipData.Item物件。

一個ClipDescription物件包含了clip的metadata。
尤其是,它含有一個給clip的資料可用的MIME類別的陣列。
當你將clip放到剪貼簿上時,這個陣列就可以做為貼到app上所用。
可以透過檢驗它來確認是否它們可以處理任何可用的MIME種類。

一個ClipData.Item物件含有text,URI,或者Intent data:
Text:
    一個CharSequence

URI:
    一個Uri。這通常含有一個content provider URI,
儘管任何URI都是被允許的。提供資料來放URI到剪貼簿上的app。
想要將資料貼上的apps從剪貼簿拿到URI,
並且使用它來存取content provider(或者其他的資料source),
並且取得資料。

Intent:
    一個Intent。這個資料型態允許你去複製一個app的捷徑到剪貼簿上。
使用者接著就可以貼上捷徑到他們的apps,作為稍後的用途。

你可以將超過一個的ClipData.Item物件到一個clip上。
這允許使用者來剪貼多重選擇區塊,以作為一個clip。
舉例來說,如果你有一個列表的widget允許使用者一次選取多個item,
你可以將所有items一次複製到剪貼簿上。
要這麼做的話,製作一個分開的ClipData.Item給每一項列表的item,
接著將ClipData.Item物件給ClipData物件。

ClipData convenience methods
ClipData class提供了方便的靜態methods,
給使用單一個ClipData.Item物件,
及一個簡單的ClipDescription物件,
以製作ClipData物件。

newPlainText(label, text)
    回傳一個ClipData物件,當中單一個物件包含了一個文字string。
ClipDescription物件的label是被設為label
ClipDescription裡單一的MIME種類是MIMETYPE_TEXT_PLAIN
使用newPlainText()來從文字string來製作clip。

newUri(resolver, label, URI)
回傳一個ClipData物件
如果URI是一個content URI的話,
(Uri.getScheme()回傳content:)
method使用在resolver裡提供的ContentResolver物件,
來從content provider取得可用的MIME種類,
並且將它們存到ClipDescription裡。
對於不是content: URI的URI,
這個method設定MIME種類到MIMETYPE_TEXT_URILIST

使用newUri()來從URI製作一個clip,尤其是content: URI。

newIntent(label, intent)
回傳一個ClipData物件,
其單一的ClipData.Item 物件包含一個Intent
ClipDescription物件的標籤被設為label
MIME種類被設為MIMETYPE_TEXT_INTENT

使用newIntent()來從一個Intent物件來製作一個clip。

Coercing the clipboard data to text
即便你的app只處理文字,
你可以透過使用ClipData.Item.coerceToText()的method,
轉換從剪貼簿所複製的非文字的資料。

這個method將ClipData.Item裡的資料轉換成文字,
並且回傳一個CharSequence
ClipData.Item.coerceToText()的回傳值是基於ClipData.Item的資料格式:

Text
    如果ClipData.Item是文字的話(getText()不為null),
coerceToText()回傳其文字。

URI:
    如果ClipData.Item是URI的話(getUri()不為null),
coerceToText()嘗試使用它來當作一個content URI:
    1. 如果URI是一個content URI,
且provider可以回傳一個文字串流(text stream),
coerceToText()回傳一個文字串流。
    2. 如果URI是一個content URI,
但provider並不提供文字串流,
coerceToText()回傳一個URI的表示式(representation)。
此表示式和由Uri.toString()所回傳的是相同的。
    3. 如果URI不是一個content URI,
coerceToText()回傳一個URI的表示式。
此表示式和由Uri.toString()所回傳的是相同的。

Intent:
    如果ClipData.Item是Intent的話(並非為null),
coerceToText()將它轉換成一個Intent URI並回傳之。
這個表示和被Intent.toUri(URI_INTENT_SCHEME)回傳的是一樣的。

剪貼簿的framework可以由下圖來做總結。
要複製資料的時候,
一個app將ClipData物件放上 ClipboardManager的全域剪貼簿。
ClipData包含了一或多個ClipData.Item物件,
以及一個ClipDescription
要貼上資料的時候,
一個app拿到ClipData
ClipDescription取得它的MIME種類,
並且從ClipData.Item
或者從ClipData.Item所指向的content provider取得資料。
A block diagram of the copy and paste framework

Copying to the Clipboard:

就如前面所描述的,要將資料複製到剪貼簿的話,
你要處理global的ClipboardManager物件,
做出一個ClipData物件,加上一個ClipDescription
以及一或多個ClipData.Item物件上去,
最後加上被完成的ClipData物件到ClipboardManager物件。
這在下面的程序中會詳盡的描述:

1. 如果你是用content URI來複製資料的話,設好一個content provider。
sample使用Note Pad sample app,
是一個對於使用content provider來複製貼上的範例。
NotePadProvider的class實作了content provider。
NotePad的class定義了provider與其他app之間的契約(contract),
包含了支援的MIME種類。

2. 拿到系統的剪貼簿:


...
// if the user selects copy
case R.id.menu_copy:
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager)
        getSystemService(Context.CLIPBOARD_SERVICE);


3. 將資料複製到一個新的ClipData物件:
對純文字而言:

// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText("simple text","Hello, World!");

對URI而言:
這個片段藉由對record ID做編碼放到content URI上,
建構了一個URI給provider。
這個技術在Encoding an identifier on the URI段落裡有更詳盡的敘述。

// Creates a Uri based on a base Uri and a record ID based on the contact's last name
// Declares the base URI string
private static final String CONTACTS = "content://com.example.contacts";
// Declares a path string for URIs that you use to copy data
private static final String COPY_PATH = "/copy";
// Declares the Uri to paste to the clipboard
Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName);
...
// Creates a new URI clip object. The system uses the anonymous getContentResolver() object to
// get MIME types from provider. The clip object's label is "URI", and its data is
// the Uri previously created.
ClipData clip = ClipData.newUri(getContentResolver(),"URI",copyUri);

對Intent而言:
這個片段建構一個Intent給app,
接著將其放進clip物件裡。

// Creates the Intent
Intent appIntent = new Intent(this, com.example.demo.myapplication.class);
...
// Creates a clip object with the Intent in it. Its label is "Intent" and its data is
// the Intent object created previously
ClipData clip = ClipData.newIntent("Intent",appIntent);


4. 將新的clip物件放上剪貼簿:

// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);


Pasting from the Clipboard:

如前面所描述的,我們藉由從global的剪貼簿物件,
從剪貼簿取得資料,拿到clip物件,看看它的資料,
並且可能的話將其從clip物件複製到自己的儲存空間。
這個段落詳盡敘述了怎麼樣針對三種剪貼簿資料的格式來處理。

Pasting plain text
想要貼上純文字,首先拿到global的剪貼簿,
接著拿到clip物件,
並且使用getText()將文字複製到自己的儲存空間,
如下面的程序所述:
1. 使用getSystemService(CLIPBOARD_SERVICE)
來取得global的ClipboardManager物件。
並且也聲明一個全域變數來包含這個被貼上的文字:

ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
String pasteData = "";

2. 接著決定是否要在現在的Activity裡打開或關掉"貼上"的選項。
你必須驗證剪貼簿有一個clip讓你來藉此處理資料類別。

// Gets the ID of the "paste" menu item
MenuItem mPasteItem = menu.findItem(R.id.menu_paste);
// If the clipboard doesn't contain data, disable the paste menu item.
// If it does contain data, decide if you can handle the data.
if (!(clipboard.hasPrimaryClip())) {

    mPasteItem.setEnabled(false);

    } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) {

        // This disables the paste menu item, since the clipboard has data but it is not plain text
        mPasteItem.setEnabled(false);
    } else {

        // This enables the paste menu item, since the clipboard contains plain text.
        mPasteItem.setEnabled(true);
    }
}

3. 從剪貼簿複製資料。這點只有在"貼上"的目錄選項有啟用時,
在程式裡才做得到,所以你可以預設剪貼簿含有純文字。
你不知道它是不是包含一個文字string,
或者一個指向純文字的URI。
下面的片段測試了這點,但它只展示了處理純文字的code。

// Responds to the user selecting "paste"
case R.id.menu_paste:
// Examines the item on the clipboard. If getText() does not return null, the clip item contains the
// text. Assumes that this application can only handle one item at a time.
 ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
// Gets the clipboard as text.
pasteData = item.getText();
// If the string contains data, then the paste operation is done
if (pasteData != null) {
    return;
// The clipboard does not contain text. If it contains a URI, attempts to get data from it
} else {
    Uri pasteUri = item.getUri();

    // If the URI contains something, try to get text from it
    if (pasteUri != null) {

        // calls a routine to resolve the URI and get data from it. This routine is not
        // presented here.
        pasteData = resolveUri(Uri);
        return;
    } else {

    // Something is wrong. The MIME type was plain text, but the clipboard does not contain either
    // text or a Uri. Report an error.
    Log.e("Clipboard contains an invalid data type");
    return;
    }
}

Pasting data from a content URI
如果ClipData.Item物件包含了一個content URI,
且你已經決定了你可以處理其中一個MIME的種類,
做一個ContentResolver然後呼叫適合的content provider程序來取得資料。
下面的程序描述了,
如何從基於剪貼簿上的content URI的content provider取得資料。
它檢查了app可以使用的MIME類型是否能由provider取得。
1. 聲明一個全域變數來包含MIME種類。

// Declares a MIME type constant to match against the MIME types offered by the provider
public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"

2. 取得全域的剪貼簿。並且也取得一個content provider,
藉此可以用來存取content provider。

// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
// Gets a content resolver instance
ContentResolver cr = getContentResolver();

3. 從剪貼簿取得主要的clip,並且將其中的內容抓成URI:

// Gets the clipboard data from the clipboard
ClipData clip = clipboard.getPrimaryClip();
if (clip != null) {

    // Gets the first item from the clipboard data
    ClipData.Item item = clip.getItemAt(0);

    // Tries to get the item's contents as a URI
    Uri pasteUri = item.getUri();

4. 透過getType(Uri)測試看看URI是否為content URI。
這個method回傳null,如果Uri並沒有指到一個合法的(valid)content provider:

    // If the clipboard contains a URI reference
    if (pasteUri != null) {

        // Is this a content URI?
        String uriMimeType = cr.getType(pasteUri);

5. 測試看看content provider是否支援目前的app了解的MIME類型。
如果支援,就呼叫ContentResolver.query()來取得資料。
回傳值是一個Cursor:

        // If the return value is not null, the Uri is a content Uri
        if (uriMimeType != null) {

            // Does the content provider offer a MIME type that the current application can use?
            if (uriMimeType.equals(MIME_TYPE_CONTACT)) {

                // Get the data from the content provider.
                Cursor pasteCursor = cr.query(uri, null, null, null, null);

                // If the Cursor contains data, move to the first record
                if (pasteCursor != null) {
                    if (pasteCursor.moveToFirst()) {

                    // get the data from the Cursor here. The code will vary according to the
                    // format of the data model.
                    }
                }

                // close the Cursor
                pasteCursor.close();
             }
         }
     }
}

Pasting an Intent
要貼上一個Intent,首先取得全域的剪貼簿。
檢查ClipData.Item物件來看看它是否包含一個Intent。
接著呼叫getIntent()來複製Intent到你的儲存空間。
下面的片段演示了作法:

// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
// Checks to see if the clip item contains an Intent, by testing to see if getIntent() returns null
Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent();
if (pasteIntent != null) {

    // handle the Intent
} else {

    // ignore the clipboard, or issue an error if your application was expecting an Intent to be
    // on the clipboard
}

Using Content Providers to Copy Complex Data:

content providers支援了複製複雜的資料,
像是資料庫紀錄或檔案系統。
要複製資料,你可以放個content URI到剪貼簿上。
貼到apps上然後從剪貼簿取得URI,
並且使用它來取得資料庫資料或者檔案串流descriptors。

因為貼上的app只有你的資料的content URI,
它需要知道要取得哪塊資料。
你可以藉由將identifier編碼給URI上的資料,來提供這個資訊。
或者,你可以提供一個特定的URI來回傳你想複製的資料。
使用哪種技巧,取決於你的資料的組織。

下面的區塊描述了如何設好URIs,
如何提供複雜的資料,以及如何提供檔案串流。
這些描述假設你對於大體的content provider設計的principles熟悉。

Encoding an identifier on the URI
用URI來複製資料到剪貼簿的一個好用的技巧,
是將資料的identifier紀錄到URI自己身上。
你的content provider接著可以從URI取得identifier,
並且使用它來取得資料。
貼上的app不會知道identifier存在;所有它所做的,
只是從剪貼簿拿到你的"參照"(URI及identifier),
給它你的content provider,並且取回資料。

你通常會將identifier透過串到URI的尾端,
來編碼到一個content URI上。
舉例來說,
假如你定義了你的provider URI如下面的字串:

"content://com.example.contacts"

如果你想要將一個名字編碼到這個URI的話,
使用下面的做法:

String uriString = "content://com.example.contacts" + "/" + "Smith"
// uriString now contains content://com.example.contacts/Smith.
// Generates a uri object from the string representation
Uri copyUri = Uri.parse(uriString);

如果你已經使用了一個content provider,
你可能想要加上一個新的URI path來指示作為複製的URI。
舉例來說,假設你已經有下面的URI paths:

"content://com.example.contacts"/people"content://com.example.contacts"/people/detail"content://com.example.contacts"/people/images

你可以加上另一個path作為特定用來複製URIs:

"content://com.example.contacts/copying"

接著你就能藉由pattern-matching偵測一個"複製"URI,
並且使用特定作為複製及貼上的code來處理了。

如果你已經在使用content provider、內部資料庫,
或者內部的table來管理你的資料,
你通常會用編碼技巧。
在這些狀況下,你有多塊資料要複製,
且想當然一個獨特的indentifier給每一塊。
要回應一個從貼上的app傳來的query,
你可以藉由資料的identifier來查找並回傳資料。

如果你沒有多塊的資料,那麼你大概不需要編碼一個identifier。
你可以簡單的使用一個對你的provider來說獨特的URI就行了。
要回應一個query的話,你的provider會回傳它現在所含的資料。

藉由ID取得單一一個record的方法,
Note Pad sample app裡被使用,
用來從notes list裡開啟一個note。
sample透過SQL資料庫,使用了_id的field。
但是你可以用任何你想要的數字或字母的identifier。

Copying data structures
我們將content provider設置好,做為將複製貼上複雜資料的準備,
寫成一個ContentProvider的subclass。
你也應該將你放上剪貼簿的URI做編碼,
如此一來,它會指向你所想提供的那個record。
除此以外,你必須要考慮目前存在的app狀態:
1. 如果已經有一個content provider,
你可以加到它的功能。你可能只需要去修改它的query() method,
來處理從apps而來要貼上資料的URIs。
你大概會要修改method來處理"複製"URI的樣式。
2. 如果你的app維護一個內部的資料庫,
你也許要將資料庫移進一個content provider來方便從它作複製。
3. 如果你現在並不是在使用資料庫,你可以實作一個content provider,
唯一用途就是作為提供資料給從剪貼簿來貼上東西的app。

在content provider裡,你會要複寫最少下面的methods:
query()
做貼上工作的app,
會假定他們可以使用你放上剪貼簿的URI,
來利用這個method取得你的資料。
要支援複製的話,你必須要有這個method,
來偵測含有特別的"複製"路徑的URIs。
你的app接著就可以做出一個"複製"URI來放上剪貼簿,
當中包含了複製的路徑以及一個指標,用以指向你所要複製的紀錄。

getType()
這個method應該要將一或多個你想要複製的資料的MIME類型做回傳。
method newUri()會呼叫getType()
為了要將MIME類型放進新的ClipData物件。

注意你不需要去有任何其他的content provider methods,
比如像是insert()update()
一個在做貼上的app,只需要去取得你支援的MIME類型,
並且從你的provider複製資料即可。
如果你已經有這些methods了,他們將不會干涉複製的操作。

下面的片段演示了如何將你的app設定來複製複雜的資料:

1. 在app全域變數的部分,聲明一個base URI字串,
以及一個路徑用以辨認你用來複製資料的URI字串。
同時也聲明一個MIME類型給被複製的資料:

// Declares the base URI string
private static final String CONTACTS = "content://com.example.contacts";
// Declares a path string for URIs that you use to copy data
private static final String COPY_PATH = "/copy";
// Declares a MIME type for the copied data
public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"

2. 在使用者複製資料的Activity哩,
設好code來複製資料到剪貼簿。
作為對複製請求的回應,將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的URI加上去:

public class MyCopyProvider extends ContentProvider {

    ...
// A Uri Match object that simplifies matching content URIs to patterns.
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// An integer to use in switching based on the incoming URI pattern
private static final int GET_SINGLE_CONTACT = 0;
...
// Adds a matcher for the content URI. It matches
// "content://com.example.contacts/copy/*"
sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);

4. 將query()的method設定好。這個method可以處理不同的URI樣式,
取決於你如何去寫code,但只有在剪貼簿上做複製的樣式會被顯示出來:

// Sets up your provider's query() method.
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    String sortOrder) {

    ...

    // Switch based on the incoming content URI
    switch (sUriMatcher.match(uri)) {

    case GET_SINGLE_CONTACT:

        // query and return the contact for the requested name. Here you would decode
        // the incoming URI, query the data model based on the last name, and return the result
        // as a Cursor.

    ...
}

5. 將getType() method設定好,
用以回傳對於複製的資料而言恰當的MIME種類:

// Sets up your provider's getType() method.
public String getType(Uri uri) {

    ...

    switch (sUriMatcher.match(uri)) {

    case GET_SINGLE_CONTACT:

            return (MIME_TYPE_CONTACT);

Pasting data from a content URI描述了如何從剪貼簿拿到一個content URI,
並且用其來取得和貼上資料。

Copying data streams
你可以複製貼上大量的文字和二進位資料作為串流。
資料可以有如下的格式:
1. 儲存在實際裝置的檔案。
2. 從sockets拿到的串流。
3. 大量的存在provider裡的相關的資料庫系統裡的資料。

一個給資料串流使用的content provider,
提供了使用file descriptor物件,
像是AssetFileDescriptor instead of 一個Cursor物件。
貼上的app透過資料串流讀取資料串流。

要設定好你的app來從provider進行資料複製,
遵循下面的步驟:
1. 設好content URI給你要放上剪貼簿的資料串流。
這麼做的幾個選項包括如下所述:
   a. 將identifier編碼給資料串流放上URI,
就如在段落Encoding an identifier on the URI所描述的,
接著維護你含有identifiers及相對應的串流名稱的provider裡的table。
   b. 將串流名稱直接在URI上編碼。
   c. 使用一個獨特的URI,此URI總是回傳現在provider的串流。
如果你使用了這個選項,你必須記得去更新你的provider,
以指向不同的串流。
(只要你是透過URI從剪貼簿上的串流來複製的時候)
2. 提供一個MIME種類,給任何你想提供的資料種類。
貼上的app需要這個資訊,來決定他們能不能在剪貼簿上貼東西。
3. 實作其中一個ContentProvider methods來回傳一個file descriptor給串流。
如果你將content URI上的identifiers編碼,使用這個method來決定要開哪個串流。
4. 要複製資料串流到剪貼簿的話,建構content URI,
並且將其放上剪貼簿。

要貼上資料串流,一個app從剪貼簿拿到clip,
接著取得URI,
並且將其在呼叫ContentResolver file descriptor method後使用。
ContentResolver method呼叫了對應ContentProvider的 method。
貼上的app接著就有責任去從串流讀出資料。

下面的列表,
秀出一個content provider最重要的file descriptor methods列表。
每一個都有其相對應的ContentResolver method,
且有著字串"Descriptor"加在method名字後面;
舉例來說,ContentResolveropenAssetFile()的analog,
openAssetFileDescriptor():

openTypedAssetFile()
這個method應該要回傳一個asset file descriptor,
但只有在提供的MIME種類有被provider支援的時候才會這麼做。
呼叫者(做貼上動作的app)提供了一個MIME種類的樣式。
content provider(有一個複製的URI到剪貼簿上的content provider)
會回傳一個AssetFileDescriptor檔案handle
(如果它可以提供那個MIME種類),或者拋出exception如果不行的話。

openAssetFile()
這個method是一個openTypedAssetFile()更普遍的的格式。
它並不會篩選允許的MIME種類,
但它可以讀檔案的subsections。


這是一個openTypedAssetFile()更普遍的格式。
它不能讀檔案的subsection。

你可以選擇性的使用openPipeHelper()這個method,
利用你的file descriptor method。這會允許貼上的app,
在背景的thread來讀串流資料,藉由使用pipw。
要使用這個method,
你需要實作ContentProvider.PipeDataWriter的介面。

一個做法的範例被放在Note Pad samploe app裡面,
openTypedAssetFile()NotePadProvider.java 的method。

Designing Effective Copy/Paste Functionality
要為你的app設計有效率的複製和貼上的功能,
記得以下幾點:
1. 在任何時候,在剪貼簿上只有一個clip。
系統內新的app複製操作都會把前面的clip複寫。
既然使用者可能會導航(navigate)到別的地方去,
你不能假定剪貼簿有你前面使用過複製所留的資料。
2. 一個clip有多個ClipData.Item物件的意圖,
是要支援多重選擇的複製及貼上,
這樣會比每次選一段來的好。
你通常想要所有的ClipData.Item物件在相同一個cilp裡,
擁有同樣的格式,也就是說,
他們應該全都是純文字,content URI,
或者Intent,但不是不同種類的組合。
3. 當你提供資料時,你可以提供不同的MIME表示法。
將你所支援的MIME種類加到ClipDescription
然後將MIME種類實作在你的content provider。
4. 當你從剪貼簿取得資料,你的app就該負責檢查可用的MIME種類,
並且決定使用哪一個(如果有的話)。
即便是有一個clip在剪貼簿上,且使用者請求貼上,
你的app並不是非要做貼上不可。
你應該只在MIME種類能相容時才做貼上。
你可以選擇去逼迫剪貼簿上的資料轉為純文字,
使用coerceToText()
如果你的app支援不只一個可用的MIME類型,
你可以允許使用者來選擇使用哪個。

沒有留言:

張貼留言