FileProvider的使用
一个应用经常需要向其他应用发送一个甚至多个文件。例如,一个图库应用可能需要向图片编辑器提供多个文件,或者一个文件管理器可能希望允许用户在外部存储的不同区域之间复制粘贴文件。一种让应用可以分享文件的方法,接收文件应用所发出的请求进行响应。
在所有情况下,唯一的一个将一个文件从你的应用发送至另一个应用的安全方法是向接收文件应用发送这个文件的URI,然后对这个URI授予临时的可访问权限。具有URI临时访问权限的URI是安全的,因为访问权限只授权于接收这个URI的应用,并且它们会自动过期。Android的FileProvider组件提供了getUriForFile())方法来创建一个文件的URI。
以上摘自androidtraining中文课程。这两天写了一个关于FileProvider的例子,基本是按照训练课程的思路做的。如果你有兴趣一览,不胜荣幸。
例子里写了两个应用,一个是服务器,一个是客户端。服务器应用给客户端应用提供分享的文件,客户端用来显示文件的内容。
服务器应用
1) 设置配置文件
修改AndroidManifest.xml文件,内容如下:
其中的android:authorities值定义在res/values/string.xml。显示如下:
服务器
Hello world!
Settings
创建内部文件
创建外部文件
com.example.serverapp
请选择文件
取消
完成
在/res/xml下定义了filepaths.xml文件。内容如下:
原training课程中只是使用了files-path,我另外加了一个external-path。files-path指明你要分享的文件的根级目录必须是getFilesDir()目录,path=”files/”,是这个根级目录下的一级目录。external-path指明的分享根级目录是Environment.getExternalStorageDirectory()。注意两个name值是不能相同的。
1) 创建分享文件
测试时,必须有文件分享才行,所以直接创建了一个activity用来生成分享文件。主界面如下:
点击“创建内部文件”按钮,会在应用的getFilesDir()/files/目录下创建10个txt文件,命名为“内部文件0.txt”,“内部文件1.txt”等,内容为“这是第0个内部文件”,“这是第1个内部文件”等。
点击“创建外部文件”按钮,会在Environment.getExternalStorageDirectory()/files/目录下创建10个txt文件,命名与内容与上面的内部文件类似,只不过用“外部”代替了“内部”字符串。
创建完后,你就可以按后退键退出应用了。
1) 建立响应分享请求的Activity
我们给这个activity命名为FileSelectActivity。还要声明相应的intent-filter才行。它的配置文件内容为:
/AndroidManifest.xml
我们设置的MIME类型为“text/plain”,表明只接收纯文本类型。当然了,如果你想让它响应别的类型的文件请求,请修改此值。
当有用户请求文件时,这个activity就会显示出来,界面如下:
我们使用了一个ExpandableListView来显示内部和外部文件列表,当用户点击的时候,就会把结果返回给请求者。
下面是设置结果的代码:
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
// TODO Auto-generated method stub
File file = (File)parent.getExpandableListAdapter().getChild(groupPosition, childPosition);
Uri fileUri;
try {
fileUri = FileProvider.getUriForFile(
FileSelectActivity.this,
getResources().getString(R.string.fileprovider_authority),
file);
Intent resultIntent = new Intent();
if (fileUri != null) {
resultIntent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION);
// Put the Uri and MIME type in the result Intent
resultIntent.setDataAndType(
fileUri,
getContentResolver().getType(fileUri));
// Set the result
setResult(Activity.RESULT_OK,
resultIntent);
} else {
resultIntent.setDataAndType(null, "");
setResult(RESULT_CANCELED,
resultIntent);
}
finish();
} catch (Exception e) {
e.printStackTrace();
}
。。。。。。
上面有一句resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION),如果没有这句话,原请求者去读文件的时候会发生安全异常错误的。
客户端应用
客户端应用就比较简单了,它只是去请求一个文件,然后从返回结果中读取文件内容就行了。它的主界面如下:
点击“请求文件”按钮,就会打开服务器应用,进入分享文件列表,选择后就会返回到本界面,然后显示文件名和文件内容。
“请求文件”按钮的响应事件代码如下:
private void requestFile() {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("text/plain");
startActivityForResult(intent, 0);
}
注意,首先应该先安装服务器应用,否则系统中可能不存在能响应此intent的组件,那么你的客户端应用可能崩溃。
当从客户端得到结果时,我们需要分别读取文件的名字和大小,还有文件的内容信息。
private void readFile(Uri returnUri) {
Context context = getActivity();
ParcelFileDescriptor inputPFD;
//获取文件句柄
try {
inputPFD = context.getContentResolver().openFileDescriptor(returnUri, "r");
} catch (FileNotFoundException e) {
e.printStackTrace();
tvFileContent.setText("获取文件句柄失败");
tvFileName.setText("获取文件句柄失败");
return;
}
//获取文件名字和大小
Cursor returnCursor =
context.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();
tvFileName.setText("文件名:"+returnCursor.getString(nameIndex)+", 大小:"+
Long.toString(returnCursor.getLong(sizeIndex))+" B");
returnCursor.close();
//读取文件内容
String content = "";
FileReader fr = null;
char[] buffer = new char[1024];
try {
StringBuilder strBuilder = new StringBuilder();
fr = new FileReader(inputPFD.getFileDescriptor());
while (fr.read(buffer) != -1) {
strBuilder.append(buffer);
}
fr.close();
content = strBuilder.toString();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (content.length() != 0) {
tvFileContent.setText(content);
} else {
tvFileContent.setText("<内容空>");
}
try {
inputPFD.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
如果上面的获取成功的话,显示效果将如下:
有兴趣的同学请参考源代码