您的位置:首页 > 大数据 > 人工智能

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检索文件信息

下面开始代码分析。

如何设置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)));
...


好了,每次写完都晕头转向,等哪天用到了,再回来看你。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  谷歌 安全 uri