Google Training 建立分享内容的APP ------ 分享文件
2016-09-04 10:53
435 查看
今天看了Google上如何分享文件的教程,内容主要涉及了客户APP向另一服务APP请求文件,服务APP如何处理封装Uri,然后客户APP如何处理返回资源。大概是这样一个流程。
一般来说,一个应用向另一个应用提供文件的唯一的安全的方式,就是向客户APP提供文件内容的URI和暂时的访问权限。这个方式很安全,因为服务APP只会给客户APP提供文件的URI,并且当客户APP获取结果后授权会自动失效。
如果你只想在APP之间分享一些小型的数据,你只需要把数据放进Intent,并对Intent进行合理的设置即可。内容见上一篇。
这次内容分四部分:
如何设置APP去分享文件
服务APP分享文件
客户APP请求文件
客户APP检索文件信息
下面开始代码分析。
官方给出如下示例:
android:authorities指定了你想要分享的content URI的URI 权限,在这里是android:authorities=”com.example.myapp.fileprovider”,对于你的APP来说,权限的值由APP完整的包名,以及”fileprovider” 组成。
< meta-data >指向一个XML文件,在这个XML文件里定义了你想要分享的文件的具体路径。android:resource里就是XML文件的路径和文件名,没有.xml后缀。
在这个例子里,< files-path >标签表明你想要分享APP的内部存储里的files文件夹,path属性表明你想要分享的是files文件夹下的images文件夹,name属性告诉FileProvider去添加 myimages路径片段到files/images content URI里。
< paths >元素里你能够添加很多子元素,每一个子元素都对应了一个你想要分享的具体的文件,除了使用 < files-path >标签,你还可以使用< external-path >去分享在外部存储中的文件,还可以用< cache-path >去分享在内部缓存目录下的文件。
注意:XML文件是指定要分享的文件夹的唯一方式,你不能用编码的方式添加。
这时,你就完成了分享文件的预先设置,如果现在你分享一个文件,那么它对应的content URI应该包含 authority ,文件路径和文件名。比如说你分享default_image.jpg这个文件,它对应的content URI如下所示:
首先我们要获取选中的文件,然后把这个问价作为参数传递给getUriForFile()方法,这个方法需要三个参数,上下文,< provider >里的授权,选中的文件。
下面是官方示例:
注意了,你只能分享在meta-data里path元素下的那些文件,如果在getUriForFile()方法里传递一个路径没有明确的文件,你将会收到 IllegalArgumentException 异常。
当用户完成选择之后,我们应该提供给用户返回客户APP的途径。通常的做法是添加一个checkmark或者是一个完成按钮,然后调用finish()方法。
服务APP的文件的安全性是可以保证的,因为content URI是返回给客户APP的唯一的数据。URI不包含文件的目录,所以客户APP不能访问服务APP其他的文件,只能通过暂时性的权限,去访问这个url对应的文件。
下面的代码演示了客户APP如何处理接收到的Intent,并通过content URIdedao FileDescriptor:
openFileDiscriptor()方法会返回一个对应文件的ParcelFileDescriptor,通过它我们调用getFileDescriptor()得到FileDescriptor,然后我们就能任意读取文件了。
DISPLAY_NAME
文件的名字,String类型,我们也可以通过File.getName()方法得到。
SIZE
文件的大小,以byte为单位,我们可以通过File.length()得到相同的值。
我们通过把query()方法的参数设置为null,除了contentURI,我们就能直接获得这两个值。下面是官方代码示例,他还把它们都设置在了TextView里:
好了,每次写完都晕头转向,等哪天用到了,再回来看你。
一般来说,一个应用向另一个应用提供文件的唯一的安全的方式,就是向客户APP提供文件内容的URI和暂时的访问权限。这个方式很安全,因为服务APP只会给客户APP提供文件的URI,并且当客户APP获取结果后授权会自动失效。
如果你只想在APP之间分享一些小型的数据,你只需要把数据放进Intent,并对Intent进行合理的设置即可。内容见上一篇。
这次内容分四部分:
如何设置APP去分享文件
服务APP分享文件
客户APP请求文件
客户APP检索文件信息
下面开始代码分析。
如何设置APP分享文件
为了安全的分享文件,你应该对分享的文件进行安全的处理,通过content URI的方式。根据你在XML资源文件里明确的路径,Android组件-FileProvider就能够产生文件content URI。1.明确FileProvider
为了给APP定义FileProvider你需要去修改APP的Manifest文件,添加< provider >元素,在这个元素里,应该包含产生content URI的授权,以及定义你想分享文件路径的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指定了你想要分享的content URI的URI 权限,在这里是android:authorities=”com.example.myapp.fileprovider”,对于你的APP来说,权限的值由APP完整的包名,以及”fileprovider” 组成。
< meta-data >指向一个XML文件,在这个XML文件里定义了你想要分享的文件的具体路径。android:resource里就是XML文件的路径和文件名,没有.xml后缀。
明确想要分享的文件
一旦你完成了FileProvider的manifest文件修改,你的下一步就是要明确你想要分享那些文件。首先我们要在res/xml的子目录下新建filepaths.xml文件,然后为每一个我们想要分享的文件添加< xml >标签。下面这个代码片段展示了如何去分享APP内部存储的files/目录下的子文件:<paths> <files-path path="images/" name="myimages" /> </paths>
在这个例子里,< files-path >标签表明你想要分享APP的内部存储里的files文件夹,path属性表明你想要分享的是files文件夹下的images文件夹,name属性告诉FileProvider去添加 myimages路径片段到files/images content URI里。
< paths >元素里你能够添加很多子元素,每一个子元素都对应了一个你想要分享的具体的文件,除了使用 < files-path >标签,你还可以使用< external-path >去分享在外部存储中的文件,还可以用< cache-path >去分享在内部缓存目录下的文件。
注意:XML文件是指定要分享的文件夹的唯一方式,你不能用编码的方式添加。
这时,你就完成了分享文件的预先设置,如果现在你分享一个文件,那么它对应的content URI应该包含 authority ,文件路径和文件名。比如说你分享default_image.jpg这个文件,它对应的content URI如下所示:
content://com.example.myapp.fileprovider/myimages/default_image.jpg
分享文件
完成分享文件的设置后,我们就能响应其他APP的文件分享请求。一种响应的方式是在服务APP上给用户提供文件选择界面,这个界面能被其他APP触发,然后用户完成选择后,客户APP就收到了选中文件的content URI。接受文件分享请求
为了接受请求并响应文件的content URI,你应该先定义一个文件选择Activity。客户APP先封装好Action为ACTION_PICK的Intent,然后调用startActivityForResult()方法,然后你的APP接受处理好,然后再把数据通过Intent返回给客户APP。创建一个文件选择Activity
在创建前,我们应该先明确这个Acitivity的Manifest文件该怎么写。在intent filter里面设置action 为 ACTION_PICK,设置category为 CATEGORY_DEFAULT 和 CATEGORY_OPENABLE,还有你向客户APP提供的数据的MIME类型。官方给出了如下示例:<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>
在代码里设置文件选择Activity
这个Acitivity应该能展示files/images/里的文件资源,并且用户可以通过它挑选自己想要的文件,下面的代码展示了如何定义Acticity并响应用户的挑选: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 res e9cd ult 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 */ ... } ... }
响应用户的选择
一旦用户完成的文件的选择,我们就要确定用户选中了哪个文件,并产生选中文件的content URI,通常如果用listview来展示文件资源,我们可以在onItemClick()里处理这个事情。首先我们要获取选中的文件,然后把这个问价作为参数传递给getUriForFile()方法,这个方法需要三个参数,上下文,< provider >里的授权,选中的文件。
下面是官方示例:
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); } ... } }); ... }
注意了,你只能分享在meta-data里path元素下的那些文件,如果在getUriForFile()方法里传递一个路径没有明确的文件,你将会收到 IllegalArgumentException 异常。
准许访问文件的权限
获取到选中文件的content URI之后,我们还要允许客户APP有权限访问这个文件。通常的做法是,现在返回数据的Intent里面加入URI,然后再加入允许权限的permission flags,这个访问权限是暂时的,当客户APP完成任务之后资格权限就自动失效了。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); } ... } ... }); ... }
和客户APP分享文件
最后一步就是把包含了content URI和权限的Intent传递给setIntent()函数,当你的Activity完成使命结束之后,系统就会把Intent返回给客户APP。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); } } });
当用户完成选择之后,我们应该提供给用户返回客户APP的途径。通常的做法是添加一个checkmark或者是一个完成按钮,然后调用finish()方法。
public void onDoneClick(View v) { // Associate a method with the Done button finish(); }
客户APP请求文件
发送请求
为了获取请求,首先设置好请求的Intent,设置action为ACTION_PICK,不要忘记设置请求的数据MIME类型,然后吊桶startActivityForResult()方法,下面是官方示例: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); ... } ... }
访问分享到的文件
文件通过Intent的形式返回,所以我们应该在客户APP上覆写onActivityResult()方法。一旦我们取得了文件的content URL,我们技能通过FileDescriptor来访问文件。服务APP的文件的安全性是可以保证的,因为content URI是返回给客户APP的唯一的数据。URI不包含文件的目录,所以客户APP不能访问服务APP其他的文件,只能通过暂时性的权限,去访问这个url对应的文件。
下面的代码演示了客户APP如何处理接收到的Intent,并通过content URIdedao FileDescriptor:
/* * When the Activity of the app that hosts files sets a result and calls * finish(), this method is invoked. The returned Intent contains the * content URI of a selected file. The result code indicates if the * selection worked or not. */ @Override 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(); ... } }
openFileDiscriptor()方法会返回一个对应文件的ParcelFileDescriptor,通过它我们调用getFileDescriptor()得到FileDescriptor,然后我们就能任意读取文件了。
检索文件信息
在客户APP拿到文件后,最后要先确定一个文件的大小和数据类型等信息在开始工作。文件的数据类型能让客户APP知道自己是否可以处理它,而文件的大小能帮助客户APP建立对于这个文件的缓冲区。获取文件的MIME类型
文件的数据类型决定了APP是否能处理它。为了确定文件类型,我们可以掉ContentResolver.getType(),这方法取药传递文件的content uri,返回文件的MIME类型。下面的代码展示了客户APP如何获取文件类型:... /* * 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); ...
获取文件的名称和大小
FileProvider类提供了一个默认的实现方法-query(),这个方法能返回文件的名称大小,需要提供文件的content URI,结果以Cursor的形式返回。这个默认的实现返回两列:DISPLAY_NAME
文件的名字,String类型,我们也可以通过File.getName()方法得到。
SIZE
文件的大小,以byte为单位,我们可以通过File.length()得到相同的值。
我们通过把query()方法的参数设置为null,除了contentURI,我们就能直接获得这两个值。下面是官方代码示例,他还把它们都设置在了TextView里:
... /* * 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))); ...
好了,每次写完都晕头转向,等哪天用到了,再回来看你。
相关文章推荐
- Google Training 建立分享内容的APP ------ 通过NFC分享文件
- Google Training 建立分享内容的APP ------ 分享简单的数据
- 更新App.config文件内容
- 用代码修改配置文件(app.config)的内容
- IOS App开启iTunes文件共享 去documen的内容
- shell对比文件内容脚本分享
- java读取文件内容的三种方法代码片断分享(java文件操作)
- [原创]读写app.config以及web.config文件中的appSettings节次内容
- 从键盘输入若干个字符,逐个存到磁盘文件中,直到输入‘\n’为止,并将建立的文本文件的内容在存入文件的同时,在屏幕上显示出来。。
- 如何为iOS app添加AirDrop文件分享功能
- iOS版Google+更新SDK:支持第三方app内容一键分享至Google+ 支持ID token
- Android 建立文件夹、生成文件并写入文本文件内容
- 一步一步学Remoting之一:从简单开始建立配置文件:app.config
- MSSQL分享:sp_writeall 将文本文件内容全部写入某一文件
- 【Android Training - 06】分享数据内容 [Lesson 2 - 从其它app接收分享的内容]
- 如何为iOS app添加AirDrop文件分享功能
- 在app_code 建立的.cs文件下alert弹出窗口
- 【Android Training - 06】分享数据内容 [Lesson 1 - 发送分享的数据到其他App]
- Android - 分享内容 - 接收其他APP的内容
- 【Android Training - 06】分享数据内容 [Lesson 2 - 从其它app接收分享的内容]