创建可以分享内容的应用
来源:互联网 发布:淘宝联盟无法登陆 编辑:程序博客网 时间:2024/05/17 07:44
原文链接:http://android.xsoftlab.net/training/building-content-sharing.html
第一篇文章,虽然是翻译,也会学习到一些内容的,以后会不定时更新的
这篇文章主要讲如何构建在不同应用和设备间共享数据的应用。该部分总共包含三部分,分别是:
- 共享简单数据
- 共享文件
- 通过NFC共享文件
共享简单数据
1. 发送简单数据到其他应用
1.1 发送文本内容
在应用间使用Intent发送、接收数据是最简单的分享内容的做法。Intent可以让用户使用他们喜欢的应用快速、简单的分享内容。
注意:在ActionBar上添加分享动作的最好做法就是使用ShareActionProvider,它从API14开始可用。
ACTION_SEND
action最直接、常见的用法是在activity间发送文本内容。例如内置的浏览器应用可以把当前显示页面的URL作为文本内容分享给任何应用。这对于朋友间通过email或社交网络分享文章或网址来说很重要。下面是一个例子:
Intent sendIntent = new Intent();sendIntent.setAction(Intent.ACTION_SEND);sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");sendIntent.setType("text/plain");startActivity(sendIntent);如果安装的应用有匹配的,就运行。如果匹配的应用超过一个,就显示一个应用选择列表,来让用户选择要启动的应用。然而,如果调用
Intent.createChooser()
,传入构建好的Intent
对象,将返回一个永远展示选择器的Intent。 以下是改动后的代码:Intent sendIntent = new Intent();sendIntent.setAction(Intent.ACTION_SEND);sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");sendIntent.setType("text/plain");startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));效果图如下:
1.2 发送二进制内容
二进制数据通过ACTION_SEND action并设置合适的MIME类型,使用EXTRA_STREAM来标示附加 的URI数据。这经常用来分享图片,也可以分享任何类型的二进制数据。
Intent shareIntent = new Intent();shareIntent.setAction(Intent.ACTION_SEND);shareIntent.putExtra(Intent.EXTRA_STREAM, uriToImage);shareIntent.setType("image/jpeg");startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));注意以下几点:
- MIME类型也可以使用“*/*”,但是这样只有处理一般数据流的activity才能匹配
- 目标应用需要有相应的权限才能访问uri指向的数据。推荐的做法是:(1)在自己的ContentProvider中保存数据,确保其他应用有访问的权限。更好的提供访问权限的机制是使用per-URI权限,这是临时的并且只赋给目标应用。一个创建这样的ContentProvider的简单做法是使用FileProvider帮助类。(2)使用系统的MediaStore。MediaStore只有匹配video,audio和image的MIME类型,然而从Android 3.0 (APIlevel 11)开始,它也可以存储非media类型。content://类型的Uri传递给onScanCompleted()回调方法后,使用scanFile()就可以把文件插入MediaStore。要注意的是一旦添加到系统的MediaStore,这些内容就可以被设备的其他应用访问。
1.3 发送多条内容
为了发送多条内容,使用ACTION_SEND_MULTIPLE action,以及指向了目标内容的URI集合。MIME类型由要分享的内容而定。比如,要分享3张JPEG格式的图片,MIME类型还是“image/jpeg”。如果图片的格式不一致,MIME类型就是“image/*”,以此来匹配可以处理任何类型图片的Activity。如果分享的内容的类型有很多种,MIME类型是“*/*”。如前所述,由接收应用来解析、处理数据。示例如下:
ArrayList<Uri> imageUris = new ArrayList<Uri>();imageUris.add(imageUri1); // Add your image URIs hereimageUris.add(imageUri2);Intent shareIntent = new Intent();shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);shareIntent.setType("image/*");startActivity(Intent.createChooser(shareIntent, "Share images to.."));和以前一样,要确保接收应用可以访问URI列表指向的数据。
2. 接收其他应用发送的简单数据
2.1 更新manifest文件
Intent 过滤器告诉系统一个应用的组件可以接收什么intent。和上一节创建带有ACTION_SEND动作的Intent相似,创建Intent过滤器是为了可以接收带有这种动作的Intent。在manifest文件中使用<intent-filter>节点添加intent过滤器。例如,如果你的应用可以接收文本内容,任何类型的一张图片,或任何类型的多张图片,manifest文件是这样:
<activity android:name=".ui.MyActivity" > <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="image/*" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.SEND_MULTIPLE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="image/*" /> </intent-filter></activity>当另一个应用想要分享这些东西时,你的应用就会作为带选项显示在列表里。如果用户选择了你的应用,相应的activity(
.ui.MyActivity
)就会启动。然后就可以自行处理这些内容。2.2 处理收到的内容
为了处理Intent传递的内容,调用getIntent()得到Intent对象,然后就可以决定下一步要做什么。始终要记住的一点是如果这个Activity可以被系统的其他部分启动,如Launcher,那么当你检查intent时要考虑到这一点。
void onCreate (Bundle savedInstanceState) { ... // Get intent, action and MIME type Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); if (Intent.ACTION_SEND.equals(action) && type != null) { if ("text/plain".equals(type)) { handleSendText(intent); // Handle text being sent } else if (type.startsWith("image/")) { handleSendImage(intent); // Handle single image being sent } } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) { if (type.startsWith("image/")) { handleSendMultipleImages(intent); // Handle multiple images being sent } } else { // Handle other intents, such as being started from the home screen } ...}void handleSendText(Intent intent) { String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); if (sharedText != null) { // Update UI to reflect text being shared }}void handleSendImage(Intent intent) { Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); if (imageUri != null) { // Update UI to reflect image being shared }}void handleSendMultipleImages(Intent intent) { ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); if (imageUris != null) { // Update UI to reflect multiple images being shared }}警告:要仔细检查收到的数据, 你不知道是否有其他的应用发送过数据。例如,发送的数据的MIME类型不对,或者发送的图片太大。而且,记得在另一个线程中处理二进制数据而不是在主线程("UI"线程)。UI的更新可以和定位一个EditText一样简单,也可以和在图片上使用一个图片过滤器一样复杂。这都取决于你的应用接下来会做什么。
3. 添加简单的分享动作
Android4.0(API14)引进的ActionProvider让我们在ActionBar上添加高效、易用的分享动作变得非常简单,一旦在ActionBar上添加一个菜单项,它就会处理菜单的外观和动作。使用ShareActionProvider时,我们提供一个Intent,然后Intent处理其他的部分。
注意:ShareActionProvider也是从API14开始引进的。
3.1 更新菜单说明
为了使用ShareActionProvider,在menu.xml文件的对应项定义android:actionProviderClass
属性:
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_item_share" android:showAsAction="ifRoom" android:title="Share" android:actionProviderClass= "android.widget.ShareActionProvider" /> ...</menu>这表明了item的外观和ShareActionProvider的功能。然而你应该告诉它你想要分享什么。.
3.2 设置分享intent
为了让ShareActionProvider起作用,你必须提供给它一个分享Intent。这个分享Intent应该和在“发送简单数据到其他应用”一节中描述的一样,有ACTION_SEND动作和通过特定标签,如EXTRA_TEXT和EXTRA_STREAM,附加的数据。为了分配一个分享Intent,首先在Activity或Fragment中找到对应的菜单项,然后调用MenuItem.getActionProvider()得到ShareActionProvider的一个实例对象。使用setShareIntent()设置Intent。下面是一个例子:
private ShareActionProvider mShareActionProvider;...@Overridepublic boolean onCreateOptionsMenu(Menu menu) { // Inflate menu resource file. getMenuInflater().inflate(R.menu.share_menu, menu); // Locate MenuItem with ShareActionProvider MenuItem item = menu.findItem(R.id.menu_item_share); // Fetch and store ShareActionProvider mShareActionProvider = (ShareActionProvider) item.getActionProvider(); // Return true to display menu return true;}// Call to update the share intentprivate void setShareIntent(Intent shareIntent) { if (mShareActionProvider != null) { mShareActionProvider.setShareIntent(shareIntent); }}你也许只需要在创建菜单栏的设置分享Intent,或者在UI变化时更新。例如,当使用图库应用全屏查看图片时,随着图片的滑动,分享Intent也会变。
共享文件
1. 设置文件分享
1.1 指定FileProvider
为应用定义FileProvider需要在manifest.xml文件中添加入口。该入口明确了生成URI时使用的authority,以及一个指明应用可以分享的目录的XML文件。下面是示例:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <application ...> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.myapp.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> ... </application></manifest>
例子中的 android:authorities
明确了使用FileProvider生成的URI的URI authority。例如"com.example.myapp.fileprovider
"。对于自己的应用定义authority时可以使用包名+fileprovider。<meta-data>子节点指向了定义应用可以分享的目录的XML文件。
android:resource
定义了文件的路径和名字。
在manifest.xml文件添加FileProvider后,在/res/xml/目录下定义
filepaths.xml
文件指明应用可以分享的目录和名字:<paths> <files-path path="images/" name="myimages" /></paths>
<files-path>
标签分享应用内存的files/目录的子目录。
images/
目录实际代表的是files/images/
子目录。
<paths>
元素可以包含许多子元素,每一个都指明了要分享的不同目录。如使用<external-path>
分享内存中的目录;使用<cache-path>分享内存缓存目录。
当这一切都完成后,当你的应用为一个文件产生URI时,它会包含在manifest.xml文件中定义的authority,路径和文件名。如下所示:
content://com.example.myapp.fileprovider/myimages/default_image.jpg
2. 分享一个文件
2.1 接收文件请求
应用应该包含一个文件选择Activity,其他应用使用带有ACTION_PICK的intent,并调用startActivityForResult()。在manifest.xml文件中注册Activity时,应该包含以下内容:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> ... <application> ... <activity android:name=".FileSelectActivity" android:label="@"File Selector" > <intent-filter> <action android:name="android.intent.action.PICK"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.OPENABLE"/> <data android:mimeType="text/plain"/> <data android:mimeType="image/*"/> </intent-filter> </activity>action:PICK;category:DEFAULT和OPENABLE。
2.2 定义文件选择Activity
public class MainActivity extends Activity { // The path to the root of this app's internal storage private File mPrivateRootDir; // The path to the "images" subdirectory private File mImagesDir; // Array of files in the images subdirectory File[] mImageFiles; // Array of filenames corresponding to mImageFiles String[] mImageFilenames; // Initialize the Activity @Override protected void onCreate(Bundle savedInstanceState) { ... // Set up an Intent to send back to apps that request a file mResultIntent = new Intent("com.example.myapp.ACTION_RETURN_FILE"); // Get the files/ subdirectory of internal storage mPrivateRootDir = getFilesDir(); // Get the files/images subdirectory; mImagesDir = new File(mPrivateRootDir, "images"); // Get the files in the images subdirectory mImageFiles = mImagesDir.listFiles(); // Set the Activity's result to null to begin with setResult(Activity.RESULT_CANCELED, null); /* * Display the file names in the ListView mFileListView. * Back the ListView with the array mImageFilenames, which * you can create by iterating through mImageFiles and * calling File.getAbsolutePath() for each File */ ... } ...}
2.3 响应文件选择
应用以ListView的形式列出所有的文件,当回调onItemClick()时,就代表选择了要分享的文件。根据得到的文件名字,生成一个File对象,并传递给getUriForFile(),生成一个URI。
protected void onCreate(Bundle savedInstanceState) { ... // Define a listener that responds to clicks on a file in the ListView mFileListView.setOnItemClickListener( new AdapterView.OnItemClickListener() { @Override /* * When a filename in the ListView is clicked, get its * content URI and send it to the requesting app */ public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { /* * Get a File for the selected file name. * Assume that the file names are in the * mImageFilename array. */ File requestFile = new File(mImageFilename[position]); /* * Most file-related method calls need to be in * try-catch blocks. */ // Use the FileProvider to get a content URI try { fileUri = FileProvider.getUriForFile( MainActivity.this, "com.example.myapp.fileprovider", requestFile); } catch (IllegalArgumentException e) { Log.e("File Selector", "The selected file can't be shared: " + clickedFilename); } ... } }); ... }记住,只可以为在文件中指明了的目录下的文件生成URI,否则会抛出IllegalArgumentException。
2.4 为文件授权
为指定文件生成URI后,还需要为其指定读的权限。此时可以在Intent中添加权限标志。此时授予的是临时权限,应用的回退栈结束后,权限就无效了:
protected void onCreate(Bundle savedInstanceState) { ... // Define a listener that responds to clicks in the ListView mFileListView.setOnItemClickListener( new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { ... if (fileUri != null) { // Grant temporary read permission to the content URI mResultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } ... } ... }); ... }警告:使用setFlags()是唯一安全的授予临时权限访问文件的方式。避免使用Context.grantUriPermission(),因为这样授予的权限只能使用Context.removeUriPermission()移除。
2.5 在应用间分享文件
为了和请求应用分享文件,使用setResult()把包含URI值得Intent传递进去。系统会把Intent发送给请求应用:
protected void onCreate(Bundle savedInstanceState) { ... // Define a listener that responds to clicks on a file in the ListView mFileListView.setOnItemClickListener( new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { ... if (fileUri != null) { ... // Put the Uri and MIME type in the result Intent mResultIntent.setDataAndType( fileUri, getContentResolver().getType(fileUri)); // Set the result MainActivity.this.setResult(Activity.RESULT_OK, mResultIntent); } else { mResultIntent.setDataAndType(null, ""); MainActivity.this.setResult(RESULT_CANCELED, mResultIntent); } } });给用户提供一种方式:当他们选择文件后,立即返回。此时可以在完成按钮上使用android:onCkick属性绑定一个监听来立即结束Activity:
public void onDoneClick(View v) { // Associate a method with the Done button finish();}
3. 请求分享文件
3.1 发送文件请求
定义好Intent后,即设置了ACTION_PICK动作和数据的MIME类型,调用startActivityForResult()来启动目标Activity:
public class MainActivity extends Activity { private Intent mRequestFileIntent; private ParcelFileDescriptor mInputPFD; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRequestFileIntent = new Intent(Intent.ACTION_PICK); mRequestFileIntent.setType("image/jpg"); ... } ... protected void requestFile() { /** * When the user requests a file, send an Intent to the * server app. * files. */ startActivityForResult(mRequestFileIntent, 0); ... } ...}
3.2 访问请求到的文件
客户端在onActivityResult()中处理服务器端发送过来的Intent数据。得到文件的URI后,就可以使用FileDescriptor来访问文件。
在此过程中,文件安全是受保护的,因为URI是客户端收到的唯一数据。由于URI不包含目录路径,客户端就不能访问服务器端的任何文件。只有客户端可以使用服务器端授予的权限访问文件。由于权限是临时的,一旦客户端结束,在服务器外,文件就不可访问:
public void onActivityResult(int requestCode, int resultCode, Intent returnIntent) { // If the selection didn't work if (resultCode != RESULT_OK) { // Exit without doing anything else return; } else { // Get the file's content URI from the incoming Intent Uri returnUri = returnIntent.getData(); /* * Try to open the file for "read" access using the * returned URI. If the file isn't found, write to the * error log and return. */ try { /* * Get the content resolver instance for this context, and use it * to get a ParcelFileDescriptor for the file. */ mInputPFD = getContentResolver().openFileDescriptor(returnUri, "r"); } catch (FileNotFoundException e) { e.printStackTrace(); Log.e("MainActivity", "File not found."); return; } // Get a regular file descriptor for the file FileDescriptor fd = mInputPFD.getFileDescriptor(); ... } }openFileDescriptor()得到文件的ParcelFileDescriptor对象,使用该对象可以得到用于访问文件的FileDescriptor对象。
4. 检索文件信息
4.1 检索文件的MIME类型
客户端根据文件的数据类型决定如何处理。调用ContentResolver.getType()得到文件的类型,返回文件的MIME类型。默认FileProvider根据文件扩展名决定文件的MIME类型:
... /* * Get the file's content URI from the incoming Intent, then * get the file's MIME type */ Uri returnUri = returnIntent.getData(); String mimeType = getContentResolver().getType(returnUri); ...
4.2 检索文件名和大小
FileProvider默认实现的query()方法可以根据文件URI得到的Cursor对象返回文件的名字和大小。默认返回两列:
- DISPLAY_NAME:文件名,String类型。和通过File.getName()得到的一样。
- SIZE:文件大小,long类型。和通过File.lengh()得到的一样。
... /* * Get the file's content URI from the incoming Intent, * then query the server app to get the file's display name * and size. */ Uri returnUri = returnIntent.getData(); Cursor returnCursor = getContentResolver().query(returnUri, null, null, null, null); /* * Get the column indexes of the data in the Cursor, * move to the first row in the Cursor, get the data, * and display it. */ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); returnCursor.moveToFirst(); TextView nameView = (TextView) findViewById(R.id.filename_text); TextView sizeView = (TextView) findViewById(R.id.filesize_text); nameView.setText(returnCursor.getString(nameIndex)); sizeView.setText(Long.toString(returnCursor.getLong(sizeIndex))); ...
通过NFC共享文件
1. 发送文件到其他设备
2. 从其他设备接收文件
最后的NFC还没有学习过,所以暂时没有翻译,等以后学习会回来更新的。
- 创建可以分享内容的应用
- Google Android开发者文档系列-创建有内容分享特性的应用之设置共享文件
- Google Android开发者文档系列-创建有内容分享特性的应用之共享文件
- Google Android开发者文档系列-创建有内容分享特性的应用之请求共享文件
- Google Android开发者文档系列-创建有内容分享特性的应用之获取文件信息
- Google Android开发者文档系列-创建有内容分享特性的应用之接收其它应用程序发送的简单数据
- Google Android开发者文档系列-创建有内容分享特性的应用之添加一个简单的共享action
- Google Android开发者文档系列-创建有内容分享特性的应用之发送简单数据到其它应用程序
- Google Android开发者文档系列-创建有内容分享特性的应用之文件共享(序言)
- DataSet 的内容可以从 XML 流或文档创建
- Mi2上装的应用们,大家也可以分享下好的应用
- Android分享中,如何过滤指定的应用,并且对不同的分享方式发送不同的内容?
- Android分享中,如何过滤指定的应用,并且对不同的分享方式发送不同的内容?
- Android分享中,如何过滤指定的应用,并且对不同的分享方式发送不同的内容?
- Android分享中,如何过滤指定的应用,并且对不同的分享方式发送不同的内容?
- Android分享中,如何过滤指定的应用,并且对不同的分享方式发送不同的内容?
- 结合网上内容,分享一个,java用for循环创建i个对象,的方法
- 腾讯优测优分享 | 让你头疼的手机应用自动遍历可以这样实现
- 最简洁条件判断写法
- k-近邻算法(kNN)
- 安装IG 常见Cydia错误提示(红字/黄字)和解决办法
- POJ 1324 Holedox Moving 状态压缩+A* -
- 用Android Studio创建一个Android Project
- 创建可以分享内容的应用
- eclipse+cdt 配置mysql(附测试代码)
- POJ 3281 Dining 最大流
- 今日头条2017年实习生在线笔试题1
- VS2015配置EGE图形库
- 编译时注解
- Qt tips 如何给qlabel添加边框
- this的小练习
- 欢迎使用CSDN-markdown编辑器