【Android】Android之Copy and Paste
Android为复制粘贴提供了一个强大的基于剪切板的框架,它支持简单和复杂的数据类型,包括纯文本,复杂的数据结构,二进制流,甚至app资源文件。简单的文本数据直接存储在剪切板中,而复杂的数据则存储的是数据的引用,粘贴对象从content provider中获取数据。复制黏贴可以在应用内部和应用之间工作。
复制黏贴使用content providers,本文假设读者对content provider是熟悉的。
The Clipboard Framework
当你是用剪切板框架的时候,卡发着将数据放入一个剪切对象中,然后将剪切对象放置在系统的剪切板上,剪切对象有以下三种形式:
1)文本:一个文本字符串。开发者可以直接将字符串放进一个剪切对象中,然后放置到剪切板上去。要粘贴字符串,只需要从剪切板上拿到剪切对象,将字符串粘贴到应用的存储上就好;
2)任何形式的URI:这对于从content provider中复制复杂的数据来说非常重要,为了复制数据,你可以将一个Uri对象放入一个剪切对象。粘贴的时候可以解析这个Uri对象。如果是content provider的地址,则解析数据,进行复制;
3)Intent:支持复制应用的快捷方式,为了复制数据,开发者可以创建一个Intent放入剪切对象。
剪切板一次只能存储一个剪切对象。
如果你要允许用户粘贴数据到你的应用,你不必处理所有类型的数据,你在允许用户粘贴之前可以去检测剪切板上数据的类型,如果得不到确定的数据类型,剪切对象也有一个Metadata告诉你是什么类型的数据。Metadata可以帮助你决定你的应用是不是可以用剪切板数据做一些事情。
你或许也会允许用户不管数据格式直接粘贴数据,这种情况下你可以强制剪切板数据转化成文本表示,然后粘贴文本。
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对象:
// 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:
下面的代码片段式将记录ID编码到content URI中形成一个URI来构造的,详情见: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:
// 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)在剪切板上放置新的剪切对象:
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);
Pasting form the Clipboard
1)粘贴纯文本:
第一步:获取全局的ClipbaodrManager对象:
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
String pasteData = "";
2)决定是否需要在当前的Activity允许粘贴行为,你需要验证数据的格式:
// 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)粘贴数据,只有当可粘贴的时候该功能才会可用。这时候你不知道剪切板上存在的是纯文本还是指向纯文本的URI,下面的代码可以测试这个,但是它只显示了如何处理纯文本:
// 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
如果剪切板上是一个URI,你的应用也会处理它,那就创建一个ContentResolver,然后调用何时的content provider方法去获取数据。下面的流程描述了如何去做:
1)声明一个全局变量:
// 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)获取ClipboardManager和content resolver:
// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Gets a content resolver instance
ContentResolver cr = getContentResolver();
3)获取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,如果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类型,如果是,则查询数据:
// 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();
}
}
}
}
Psting an 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 Provider支持复杂的数据复制粘贴,比如数据库记录或者文件流。为了复制这样的数据,开发者可以将一个content URI放置在剪切板上,然后粘贴它的应用就可以使用Uri去检索数据。
由于粘贴的应用只有数据的Uri,它还需要知道去粘贴数据的哪一部分,你可以在URI本身中提供信息,或者你可以提供一个特殊的URI来指明指定的想要复制的数据,采用什么技术取决你的数据组织类型。
Encoding an identifier on the URI
在数据的URI本身中编码一个识别器(其实就是资源ID)是复制数据比较有用的一种技术。content provider就可以使用这个识别器去检索特定的数据,粘贴的应用不需要知道识别器的存在,它只需要获取这个uri,然后将它交给复制应用的content provider然后就可以获得数据。
通常我们将识别器放置在content URI的后面,假设你的provider URI如下:
"content://com.example.contacts"
如果你想在后面编码一个名字,你只需要这样:
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路径来指明这个URI是用来复制的,举个例子,加入你已经拥有下面这些URI path:
"content://com.example.contacts"/people
"content://com.example.contacts"/people/detail
"content://com.example.contacts"/people/images
你可以添加另外一个路径来指明这是专门用来复制的uris:
"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类型:
/ 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中,建立复制数据的代码,当需要复制数据的时候,将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:
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()方法。这个方法可以处理不同的URI patterns,决定于你怎么去编码,下面写明的是如何处理用于复制操作的URI:
// 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()方法,返回一个正确的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);
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类型,你可以允许用户选择一种。
【Android】Android之Copy and Paste的更多相关文章
- 将文件从已Root Android手机中copy出来的几个cmd窗口命令
将文件从已Root Android手机中copy出来的几个cmd窗口命令: 以shell身份登录adbadb shell进入adb后切换至root用户su更改文件的所属chown shell *更改文 ...
- [Android][Android Studio] Gradle项目中加入JNI生成文件(.so文件)
版权声明:本文作者:Qiujuer https://github.com/qiujuer; 转载请注明出处,盗版必究! ! ! https://blog.csdn.net/qiujuer/articl ...
- Stack Overflow 排错翻译 - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder
Stack Overflow 排错翻译 - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder 转自:ht ...
- [Android]Android端ORM框架——RapidORM(v2.1)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6020412.html [Android]Android端ORM ...
- [Android]Android端ORM框架——RapidORM(v2.0)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5626716.html [Android]Android端ORM ...
- Android android:gravity属性介绍及效果图
转自: http://blog.csdn.net/aminfo/article/details/7784229 Android:gravity的属性官方说明如下: public static fina ...
- [转][Android][Android Studio] *.jar 与 *.aar 的生成与*.aar导入项目方法
转自:http://blog.csdn.net/qiujuer/article/details/39754517?utm_source=tuicool [Android][Android Studi ...
- 图解Android - Android GUI 系统 (1) - 概论
Android的GUI系统是Android最重要也最复杂的系统之一.它包括以下部分: 窗口和图形系统 - Window and View Manager System. 显示合成系统 - Surfac ...
- 图解Android - Android GUI 系统 (2) - 窗口管理 (View, Canvas, Window Manager)
Android 的窗口管理系统 (View, Canvas, WindowManager) 在图解Android - Zygote 和 System Server 启动分析一 文里,我们已经知道And ...
- 图解Android - Android GUI 系统 (5) - Android的Event Input System
Android的用户输入处理 Android的用户输入系统获取用户按键(或模拟按键)输入,分发给特定的模块(Framework或应用程序)进行处理,它涉及到以下一些模块: Input Reader: ...
随机推荐
- 贵州省未来二十年的投资机会的探讨2>
房产投资 升值最快的 在教育资源丰富 生活方便的 地方 价格和地段取其中之一. 其次 车位 再其次墓地等 公寓住房. 还有商标 和网站注册 公司注册 除了以上的这些 还有茅台生效酒 收藏
- 手机站测试工具(node服务器)
最近在工作中遇到手机站测试的问题,于是就写了一个node服务外加一个第三方的转二维码功能,欢迎拍砖~ 项目地址:https://github.com/finderL/webserver
- TCP系列28—窗口管理&流控—2、延迟ACK(Delayed Acknowledgments)
一.简介 之前的内容中我们多次提到延迟ACK(Delayed Ack),延迟ACK是在RFC1122协议中定义的,协议指出,一个TCP实现应该实现延迟ACK,但是ACK不能被过度延迟,协议给出延迟AC ...
- TCP系列24—重传—14、F-RTO虚假重传探测
一.虚假重传 在一些情况下,TCP可能会在没有数据丢失的情况下初始化一个重传,这种重传就叫做虚假重传(Spurious retransmission).发生虚假重传的原因可能是包传输中重排序.传输中发 ...
- SVM之核函数
SVM之问题形式化 SVM之对偶问题 >>>SVM之核函数 SVM之解决线性不可分 写在SVM之前——凸优化与对偶问题 上一篇SVM之对偶问题中讨论到,SVM最终形式化为以下优化问题 ...
- 原生js移动端字体自适应方案
自从进入新公司之后,就一直采用800的方案,也就是判断屏幕尺寸,大于800px是一种html字体处理方案,另一种方案是小于800px的html字体处理方案, 代码如下: (function(doc, ...
- mysql通过binlog恢复数据
如果mysql不小心操作失误导致数据错误或者丢失这时候binlog起到了很大的作用 恢复有几种方式 1.按时间恢复--start-datetime 如果确定了时间点,那么按时间恢复是一个再好不过的 ...
- android异常Unable to instantiate activity ComponentInfo解决方法
我是下面提到的第四条: 在Order and Export 中 把新加的 android-support-v4.jar的前面的对号打上勾 保存:就可以了: 做android开发的可能都碰到" ...
- DBGrid相关技术整理
DBGrid相关技术整理: 注:对于DBGrid相关属性.方法的学习融入到技术整理过程中 一,多选 设置属性: Options->dgMultiSelect = True; ->dgRow ...
- BZOJ 1045 糖果传递(思维)
设第i个人给了第i+1个人mi个糖果(可以为负),因为最后每个人的糖果都会变成sum/n. 可以得到方程组 mi-mi+1=a[i+1]-sum/n.(1<=i<=n). 把方程组化为mn ...