http://iandroiddev.com/post/2012-06-06/40028637105
在開發一些系統應用的時候,我們會用到Android的剪貼板功能,比如將文本文件、或者其他格式的內容復制到剪貼板或者從剪貼板獲取數據等操作。Android平台中每個常規的應用運行在自己的進程空間中,相對於Win32而言Android上之間的進程間傳遞主要有IPC、剪切板。當然今天我們說下最簡單的ClipboardManager。使用剪切板可以直接實現數據的傳輸。整個實現比較簡單,注意剪切板中的類型判斷。
使用起來很簡單,系統給我們提供了很方便的接口,如下文本信息復制如下所示:
//獲取剪貼板管理服務 ClipboardManager cm =(ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); //將文本數據復制到剪貼板 cm.setText(message); //讀取剪貼板數據 cm.getText();
public void setClipboard(String text) {
ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
}
public String getClipboard() {
ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
return clipboard.getText().toString();
}
ClipData代表剪貼板中剪切數據。它有一個或多個Item實例,每個可容納一個或多個數據項。 ClipData包含ClipDescription,用來描述剪貼內容的重要元數據。尤其是getDescription().getMimeType(INT)必須返回正確的MIME類型。為了正確的設置剪貼內容的MIME類型,建議使用newPlainText(CharSequence,CharSequence的),newUri(ContentResolver,CharSequence中,URI),newIntent(CharSequence, Intent)構造ClipData。每個Item的實例可以是三大數據類型之一:text,intent,URI。詳情請參閱ClipData.Item
粘貼數據
為了獲取剪貼板中的數據,應用程序必須正確解析數據;如果CipData.Item包含的信息為文本或者Intent類型,有一點需要說明:文本只能解析為文本,intent通常用來當中快捷方式或者其他的動作類型;如果你只是想獲取文本內容,你可以通過Item.coerceToText()方法強制獲取,這樣就不需要考慮MIME類型,應為所有的item都會被強制轉換為文本。
復雜的數據類型通常用URL來完成粘貼。允許接受者以URI方式從ContentProvider的獲取數據。剪貼時需要填寫正確的MIME類型; 如:newUri(ContentResolver,CharSequence,URI)這樣才能被正確的處理。
下面是NotePad應用粘貼的例子。當從剪貼板中接受數據時,如果剪貼板中包含已有note的URI引用時,根據URI復制其結構到新的Note中,否則通過根據獲取的文本內容作為新的筆記內容:
/**
* A helper method that replaces the note's data with the contents of the clipboard.
*/
private final void performPaste() {
// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager)
getSystemService(Context.CLIPBOARD_SERVICE);
// Gets a content resolver instance
ContentResolver cr = getContentResolver();
// Gets the clipboard data from the clipboard
ClipData clip = clipboard.getPrimaryClip();
if (clip != null) {
String text=null;
String title=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 pointing to a note
Uri uri = item.getUri();
// Tests to see that the item actually is an URI, and that the URI
// is a content URI pointing to a provider whose MIME type is the same
// as the MIME type supported by the Note pad provider.
if (uri != null && NotePad.Notes.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) {
// The clipboard holds a reference to data with a note MIME type. This copies it.
Cursor orig = cr.query(
uri, // URI for the content provider
PROJECTION, // Get the columns referred to in the projection
null, // No selection variables
null, // No selection variables, so no criteria are needed
null // Use the default sort order
);
// If the Cursor is not null, and it contains at least one record
// (moveToFirst() returns true), then this gets the note data from it.
if (orig != null) {
if (orig.moveToFirst()) {
int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
int colTitleIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
text = orig.getString(colNoteIndex);
title = orig.getString(colTitleIndex);
}
// Closes the cursor.
orig.close();
}
}
// If the contents of the clipboard wasn't a reference to a note, then
// this converts whatever it is to text.
if (text == null) {
text = item.coerceToText(this).toString();
}
// Updates the current note with the retrieved title and text.
updateNote(text, title);
}
}
很多應用可以處理多種類型的數據,例如:E_mail應用希望用戶粘貼圖片或者其他二進制文件作為附件。這就需要通過ContentResolver的getStreamTypes(Uri, String)和openTypedAssetFileDescriptor(Uri,String,android.os.Bundle)方法處理。這需要客戶端檢測一個特定的內容URI以流的方式處理數據。
如下面是Item.coerceToText的實現:
public CharSequence coerceToText(Context context) {
// If this Item has an explicit textual value, simply return that.
if (mText != null) {
return mText;
}
// If this Item has a URI value, try using that.
if (mUri != null) {
// First see if the URI can be opened as a plain text stream
// (of any sub-type). If so, this is the best textual
// representation for it.
FileInputStream stream = null;
try {
// Ask for a stream of the desired type.
AssetFileDescriptor descr = context.getContentResolver()
.openTypedAssetFileDescriptor(mUri, "text/*", null);
stream = descr.createInputStream();
InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
// Got it... copy the stream into a local string and return it.
StringBuilder builder = new StringBuilder(128);
char[] buffer = new char[8192];
int len;
while ((len=reader.read(buffer)) > 0) {
builder.append(buffer, 0, len);
}
return builder.toString();
} catch (FileNotFoundException e) {
// Unable to open content URI as text... not really an
// error, just something to ignore.
} catch (IOException e) {
// Something bad has happened.
Log.w("ClippedData", "Failure loading text", e);
return e.toString();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
}
// If we couldn't open the URI as a stream, then the URI itself
// probably serves fairly well as a textual representation.
return mUri.toString();
}
// Finally, if all we have is an Intent, then we can just turn that
// into text. Not the most user-friendly thing, but it's something.
if (mIntent != null) {
return mIntent.toUri(Intent.URI_INTENT_SCHEME);
}
// Shouldn't get here, but just in case...
return "";
}
復制數據
做為復制的源數據,應用要構造容易被接受解析的剪貼數據。如果要復制包含文本,Intent,或者URI,簡單的方式是使用ClipData.Item包含相應的類型數據;
復雜的數據類型要求支持以ContentProvide方式描述和生成被接受的數據,常用的解決方案是以URI的方式復制數據,URI有復雜結構的數據組成,只有理解這種結果的應用才能接受處理這樣的數據;
對於不具有內在的數據結構知識的應用,可使用任意可接受的數據流類型。這是通過實現ContentProvider的getStreamTypes(URI,String)和openTypedAssetFile(URI字符串,android.os.Bundle)方法進行獲取。
回到記事本應用程序的例子,它是將要復制的內容以URI的傳遞的
/**
* This describes the MIME types that are supported for opening a note
* URI as a stream.
*/
static ClipDescription NOTE_STREAM_TYPES = new ClipDescription(null,
new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN });
/**
* Returns the types of available data streams. URIs to specific notes are supported.
* The application can convert such a note to a plain text stream.
*
* @param uri the URI to analyze
* @param mimeTypeFilter The MIME type to check for. This method only returns a data stream
* type for MIME types that match the filter. Currently, only text/plain MIME types match.
* @return a data stream MIME type. Currently, only text/plan is returned.
* @throws IllegalArgumentException if the URI pattern doesn't match any supported patterns.
*/
@Override
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
/**
* Chooses the data stream type based on the incoming URI pattern.
*/
switch (sUriMatcher.match(uri)) {
// If the pattern is for notes or live folders, return null. Data streams are not
// supported for this type of URI.
case NOTES:
case LIVE_FOLDER_NOTES:
return null;
// If the pattern is for note IDs and the MIME filter is text/plain, then return
// text/plain
case NOTE_ID:
return NOTE_STREAM_TYPES.filterMimeTypes(mimeTypeFilter);
// If the URI pattern doesn't match any permitted patterns, throws an exception.
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
/**
* Returns a stream of data for each supported stream type. This method does a query on the
* incoming URI, then uses
* {@link android.content.ContentProvider#openPipeHelper(Uri, String, Bundle, Object,
* PipeDataWriter)} to start another thread in which to convert the data into a stream.
*
* @param uri The URI pattern that points to the data stream
* @param mimeTypeFilter A String containing a MIME type. This method tries to get a stream of
* data with this MIME type.
* @param opts Additional options supplied by the caller. Can be interpreted as
* desired by the content provider.
* @return AssetFileDescriptor A handle to the file.
* @throws FileNotFoundException if there is no file associated with the incoming URI.
*/
@Override
public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
throws FileNotFoundException {
// Checks to see if the MIME type filter matches a supported MIME type.
String[] mimeTypes = getStreamTypes(uri, mimeTypeFilter);
// If the MIME type is supported
if (mimeTypes != null) {
// Retrieves the note for this URI. Uses the query method defined for this provider,
// rather than using the database query method.
Cursor c = query(
uri, // The URI of a note
READ_NOTE_PROJECTION, // Gets a projection containing the note's ID, title,
// and contents
null, // No WHERE clause, get all matching records
null, // Since there is no WHERE clause, no selection criteria
null // Use the default sort order (modification date,
// descending
);
// If the query fails or the cursor is empty, stop
if (c == null || !c.moveToFirst()) {
// If the cursor is empty, simply close the cursor and return
if (c != null) {
c.close();
}
// If the cursor is null, throw an exception
throw new FileNotFoundException("Unable to query " + uri);
}
// Start a new thread that pipes the stream data back to the caller.
return new AssetFileDescriptor(
openPipeHelper(uri, mimeTypes[0], opts, c, this), 0,
AssetFileDescriptor.UNKNOWN_LENGTH);
}
// If the MIME type is not supported, return a read-only handle to the file.
return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
}
/**
* Implementation of {@link android.content.ContentProvider.PipeDataWriter}
* to perform the actual work of converting the data in one of cursors to a
* stream of data for the client to read.
*/
@Override
public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
Bundle opts, Cursor c) {
// We currently only support conversion-to-text from a single note entry,
// so no need for cursor data type checking here.
FileOutputStream fout = new FileOutputStream(output.getFileDescriptor());
PrintWriter pw = null;
try {
pw = new PrintWriter(new OutputStreamWriter(fout, "UTF-8"));
pw.println(c.getString(READ_NOTE_TITLE_INDEX));
pw.println("");
pw.println(c.getString(READ_NOTE_NOTE_INDEX));
} catch (UnsupportedEncodingException e) {
Log.w(TAG, "Ooops", e);
} finally {
c.close();
if (pw != null) {
pw.flush();
}
try {
fout.close();
} catch (IOException e) {
}
}
}
not復制操作現在只是簡單的構造UPI:
case R.id.context_copy:
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager)
getSystemService(Context.CLIPBOARD_SERVICE);
// Copies the notes URI to the clipboard. In effect, this copies the note itself
clipboard.setPrimaryClip(ClipData.newUri( // new clipboard item holding a URI
getContentResolver(), // resolver to retrieve URI info
"Note", // label for the clip
noteUri) // the URI
);
// Returns to the caller and skips further processing.
return true;
注 如果粘貼操作需要文本(例如粘貼到編程器中)coerceToText(Context)方式會通知內容提供者將URI轉換為URL;
