1.给其他App发送简单的数据
当你构建一个intent,你必须指定这个intent需要触发的actions。Android定义了一些actions,包括ACTION_SEND,这个action表明着这个intent是用来从一个activity发送数据到另外一个activity的,甚至是跨进程之间的。
注意:为ActionBar添加分享功能的最好方法是使用ShareActionProvider,它能够在API level 14以上进行使用。
1.1.分享文本内容
ACTION_SEND的最直接与最常用的是从一个Activity发送文本内容到另外一个Activity。例如,Android内置的浏览器可以把当前显示页面的URL作为文本内容分享到其他程序。这是非常有用的,通过邮件或者社交网络来分享文章或者网址给好友。
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);
//startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to));
如果设备上有安装某个能够匹配ACTION_SEND与MIME类型为text/plain程序,那么Android系统会自动把他们都给筛选出来,并呈现Dialog给用户进行选择。如果你为intent调用了Intent.createChooser(),那么Android总是会显示可供选择。这样有一些好处:
1)即使用户之前为这个intent设置了默认的action,选择界面还是会被显示。
2)如果没有匹配的程序,Android会显示系统信息。
3)你可以指定选择界面的标题。
1.2.分享二进制内容
分享二进制的数据需要结合设置特定的MIME Type,需要在EXTRA_STREAM里面放置数据的URI。
Demo:
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)));
请注意下面的内容:
1>你可以使用 */*这样的方式来指定MIME类型,但是这仅仅会match到那些能够处理一般数据类型的Activity(即一般的Activity无法详尽所有的MIME类型)
2>接收的程序需要有访问URI资源的权限。下面有一些方法来处理这个问题:
1)把文件写到外部存储设备上,类似SDCard,这样所有的app都可以进行读取。使用Uri.fromFile()方法来创建可以用在分享时传递到intent里面的Uri.。然而,请记住,不是所有的程序都遵循file://这样格式的Uri。
2)在调用 getFileStreamPath()返回一个File之后,使用带有MODE_WORLD_READABLE 模式的openFileOutput() 方法把数据写入到你自己的程序目录下。像上面一样,使用Uri.fromFile()创建一个file://格式的Uri用来添加到intent里面进行分享。
3)媒体文件,例如图片,视频与音频,可以使用scanFile()方法进行扫描并存储到MediaStore里面。onScanCompletted()回调函数会返回一个content://格式的Uri.,这样便于你进行分享的时候把这个uri放到intent里面。
4)图片可以使用 insertImage() 方法直接插入到MediaStore 系统里面。那个方法会返回一个content://格式的Uri.。
5)存储数据到你自己的ContentProvider里面,确保其他app可以有访问你的provider的权限。(或者使用 per-URI permissions)
1.3.发送多块内容
为了同时分享多种不同类型的内容,需要使用ACTION_SEND_MULTIPLE与指定到那些数据的URIs列表。MIME类型会根据你分享的混合内容而不同。例如,如果你分享3张JPEG的图片,那么MIME类型仍然是image/jpeg。如果是不同图片格式的话,应该是用image/*来匹配那些可以接收任何图片类型的activity。如果你需要分享多种不同类型的数据,可以使用*/*来表示MIME。像前面描述的那样,这取决于那些接收的程序解析并处理你的数据。
ArrayList<Uri> imageUris = new ArrayList<Uri>(); imageUris.add(imageUri1); // Add your image URIs here imageUris.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.."));
1.4.更新AndroidManifest文件
Intent filters通知了Android系统说,一个程序会接受哪些数据。像上一课一样,你可以创建intent filters来表明程序能够接收哪些action。
<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>
1.5.处理接收到的数据
为了处理从Intent带过来的数据,可以通过调用getIntent()方法来获取到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 } ... }
1.6.在ActionBar上添加分享功能(ShareActionProvider)
更新菜单声明
使用ShareActionProvider的第一步,在你的menu resources对应item中定义android:actionProviderClass属性。
表明了这个item的appearance与function需要与ShareActionProvider匹配。然而,你还是需要告诉provider你想分享的内容。
<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>
设置分享的intent
为了能够实现ShareActionProvider的功能,你必须提供给它一个intent。这个share intent应该带有ACTION_SEND和附加数据(例如EXTRA_TEXT与 EXTRA_STREAM)的。
private ShareActionProvider mShareActionProvider; ... @Override public 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 intent private void setShareIntent(Intent shareIntent) { if (mShareActionProvider != null) { mShareActionProvider.setShareIntent(shareIntent); } }
2.分享文件
在所有情况下,唯一的一个将文件从你的应用发送至另一个应用的安全方法是向接收文件的应用发送这个文件的URI,然后对这个URI授予临时的可访问权限。具有URI临时访问权限的URI是安全的,因为访问权限只授权于接收这个URI的应用,并且它们会自动过期。Android的FileProvider组件提供了getUriForFile())方法来创建一个文件的URI。
2.1.建立文件分享
为了从你的应用安全地将一个文件发送给另一个应用,你需要配置你的应用来提供安全的文件句柄(URI的形式),Android的FileProvider组件会基于你在XML文件中的具体配置,为文件创建URI。
指定FileProvider
为你的应用定义一个FileProvider,需要在你的清单文件中定义一个字段,这个字段指明了需要使用创建URI的权限。除此之外,还需要一个XML文件,它指定了你的应用可以共享的目录路径。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example"> <application ...> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.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的authority。在这个例子中,这个authority是“com.example.fileprovider”。对于你自己的应用,定义authority时,是在你的应用包名(android:package的值)之后追加“fileprovider”。
<provider>下的子标签<meta-data>指定了一个XML文件,它指定了你希望共享的目录路径。“android:resource”属性字段是这个文件的路径和名字(无“.xml”后缀)。
指定可共享目录路径
一旦你在你的清单文件中为你的应用添加了FileProvider,你需要指定你希望共享文件的目录路径。为了指定这个路径,我们首先在“res/xml/”下创建文件“filepaths.xml”。在这个文件中,为每一个目录添加一个XML标签。
<paths> <files-path path="images/" name="myimages" /> </paths>
<files-path>标签共享的是在你的应用的内部存储中“files/”目录下的目录。“path”属性字段指出了该子目录为“files/”目录下的子目录“images/”。“name”属性字段告知FileProvider向在“files/images/”子目录中的文件URI添加一个路径分段(path segment)标记:“myimages”。
<paths>标签可以有多个子标签,每一个子标签都指定一个不同的要共享的目录。除了<files-path>标签,你可以使用<external-path>来分享位于外部存储的文件,而<cache-path>标签用来共享在你的内部缓存目录下的目录。
例如:你需要一个放置在images目录下文件为"default_image.jpg"的URI,FileProvider会返回如下URI:
content://com.example.fileprovider/myimages/default_image.jpg
2.2.分享文件
一旦你配置了你的应用来使用URI共享文件,你可以响应其他应用关于这些文件的请求。一种响应的方法是在服务端应用端提供一个文件选择接口,它可以由其他应用激活。这种方法可以允许客户端应用端让用户从服务端应用端选择一个文件,然后接收这个文件的URI。
接受文件请求
为了从客户端应用端接收一个文件索取请求,然后以URI形式进行响应,你的应用应该提供一个选择文件的Activity。客户端应用端通过调用startActivityForResult()来启动这个Activity。该方法包含了一个Intent,它具有ACTION_PICK的Action。当客户端应用端调用了startActivityForResult(),你的应用可以向客户端应用端返回一个结果,该结果即用户所选文件对应的URI。
创建一个文件选择Activity
为了配置文件选择Activity,我们从在清单文件定义你的Activity开始,在其intent过滤器中,匹配ACTION_PICK的Action,以及CATEGORY_DEFAULT和CATEGORY_OPENABLE的Category。另外,还需要为你的应用设置MIME类型过滤器,来表明你的应用可以向其他应用提供哪种类型的文件。
<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>……
在代码中定义文件选择Activity
用来显示在你内部存储的“files/images/”目录下可以获得的文件,然后允许用户选择期望的文件。
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 */ ... } ... }
响应一个文件选择
一旦一个用户选择了一个共享的文件,你的应用必须明确哪个文件被选择了,然后为这个文件生成一个对应的URI。若Activity在ListView中显示了可获得文件的清单,当用户点击了一个文件名时,系统调用了方法onItemClick()),在该方法中你可以获取被选择的文件。
在onItemClick())中,为选择的文件文件名获取一个File对象,然后将它作为参数传递给getUriForFile()),另外还需传入的参数是你为FileProvider所指定的<provider>标签值。这个结果URI包含了相应的被访问权限,一个对应于文件目录的路径标记(如在XML meta-date中定义的),以及包含扩展名的文件名。有关FileProvider如何匹配基于XML meta-data的目录路径的信息,可以阅读:指定可共享目录路径。
protected void onCreate(Bundle savedInstanceState) { ... mFileListView.setOnItemClickListener( new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { File requestFile = new File(mImageFilename[position]); // 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所对应的文件,是那些在meta-data文件中包含标签的(即你定义的)目录内的文件,如果你为一个在你没有指定的目录内的文件调用了getUriForFile())方法,你会收到一个IllegalArgumentException。
为文件授权
if (fileUri != null) { // Grant temporary read permission to the content URI mResultIntent.addFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION); }
注意:调用setFlags()是唯一安全的方法,为你的文件授予临时的被访问权限。避免对文件URI调用Context.grantUriPermission(),因为通过该方法授予的权限,你只能通过调用Context.revokeUriPermission())来撤销。
与请求应用共享文件
为与请求文件的应用共享其需要的文件,将包含了URI和响应权限的Intent传递给setResult()。当你定义的Activity被结束后,系统会把这个包含了URI的Intent传递给客户端应用。
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:onClick属性字段为它关联一个方法。在该方法中,调用finish()。
2.3 请求分享一个文件
当一个应用希望访问由其它应用所共享的文件时,请求应用(即客户端)经常会向其它应用(服务端)发送一个文件请求。在大多数情况下,这个请求会在服务端应用启动一个Activity,来显示可以共享的文件。当服务端应用向客户端应用返回了URI后,用户即选择了文件。
发送一个文件请求
为了向服务端应用发送文件请求,在客户端应用,需要调用startActivityForResult(),同时传递给这个方法一个Intent,它包含了客户端应用能处理的某个Action,比如ACTION_PICK;以及一个MIME类型。
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); ... } ... }
访问请求的文件
当服务端应用向客户端应用发回包含URI的Intent时,这个Intent会传递给客户端应用中覆写的onActivityResult()方法当中。一旦客户端应用有了文件的URI,它就可以通过获取其FileDescriptor来访问文件。
文件的安全问题在这一过程中不用过多担心,因为这个客户端应用所收到的所有数据只有URI而已。由于URI不包含目录路径,客户端应用无法查询出或者打开任何服务端应用的其他文件。客户端应用仅仅获取了这个文件的访问渠道和访问的权限。同时访问权限是临时的,一旦这个客户端应用的任务栈被完成了,这个文件将只能被服务端应用访问。
public void onActivityResult(int requestCode, int resultCode, Intent returnIntent) { if (resultCode != RESULT_OK) { return; } else { // Get the file's content URI from the incoming Intent Uri returnUri = returnIntent.getData(); 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对象,然后用户就可以利用这个对象读取这个文件。
2.4.获取文件信息
当一个客户端应用尝试对一个有URI的文件进行操作时,应用可以向服务端应用索取关于文件的信息,包括文件的数据类型和文件大小。数据类型可以帮助客户端应用确定该文件自己能否处理,文件大小能帮助客户端应用为文件设置合理的缓冲区。
获取文件的MIME类型
一个文件的数据类型能够告知客户端应用应该如何处理这个文件的内容。为了得到URI所对应文件的数据类型,客户端应用调用ContentResolver.getType()。这个方法返回了文件的MIME类型。默认的,一个FileProvider通过文件的后缀名来确定其MIME类型。
Uri returnUri = returnIntent.getData(); String mimeType = getContentResolver().getType(returnUri);
获取文件名和文件大小
FileProvider类有一个默认的query())方法的实现,它返回一个Cursor,它包含了URI所关联的文件的名字和尺寸。默认的实现返回两列:
DISPLAY_NAME:是文件的文件名,一个String。这个值和File.getName())所返回的值是一样的。
SIZE:文件的大小,以字节为单位,一个“long”型。这个值和File.length())所返回的值是一样的。
客户端应用可以通过将query())的参数都设置为“null”,只保留URI这一参数,来同时获取文件的名字和大小。例如,下面的代码获取一个文件的名字和大小,然后在两个TextView中进行显示:
Uri returnUri = returnIntent.getData(); Cursor returnCursor = getContentResolver().query(returnUri, null, null, null, null); 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)));