为保障不同应用间文件共享的安全性,Android平台提供了FileProvider组件来保障共享的安全。FileProvider组件根据程序的配置,为其他应用(我们简称为接受应用)提供一个对外的Content URI,同时为接受应用分配临时的访问权限,该权限在接受应用退出时自动过期。
FileProvider是Android支持库中提供的方法,在使用之前,需要得到支持库的支持。为了使用FileProvider,需要在工程的AndroidManifest.xml文件中声明provider,格式类似于:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.fileshare.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepath" />
</provider>
其中,android:authorities属性用生成URIs的authority,一般由工程的包名+fileprovider组成。<meta-data>节点声明应用要共享的文件目录,目录的配置文件位于res/xml文件夹下。共享目录的配置如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<paths>
<files-path path="imagedir/" name="image" />
</paths>
其中path是应用的files目录的子目录,也即应用要共享的文件的目录,name是为了告诉FileProvider,将其值添加到Content URIs中,具体的配置说明请参看这里:http://developer.android.com/training/secure-file-sharing/setup-sharing.html#DefineMetaData
假设在工程的files的imagedir目录下有个名为default_image.jpg的文件,则根据之前的配置,其URI为content:/com.example.fileshare.fileprovider/imagedir/default_image.jpg。
配置好了FileProvider后,还需要为接收应用提供一个选择界面,该界面主要是列出共享的文件供用户选择。界面根据用户的选择将对应的文件返回给接受应用。其实现如下:
public class FileSelector extends ListActivity {
private File privateRootDirs; //共享文件的父目录
private File imageDirs; //共享文件目录
private File[] imageFiles; //共享的文件
private String[] imageFileName; //共享的文件名
private ListView lvFileList; //显示共享文件的列表
private Uri fileUri; //FileProvider生成的Content URI
private Intent resultIntent; //将用户选择的结果返回给接受应用的Intent
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
privateRootDirs = getFilesDir();
imageDirs = new File(privateRootDirs, "imagedir");
imageFiles = imageDirs.listFiles();
imageFileName = new String[imageFiles.length];
for(int i=0; i<imageFiles.length; i++) {
imageFileName[i] = imageFiles[i].getName();
}
lvFileList = getListView();
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, imageFileName));
lvFileList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//根据官网提供的代码片段,这里在生成File实例时,需要传入“共享文件目录”参数
//否则为报“java.lang.IllegalArgumentException: Failed to find configured root that contains ...“异常
File requestFile = new File(imageDirs, imageFileName[position]);
fileUri = FileProvider.getUriForFile(FileSelector.this, "com.example.fileshare.fileprovider", requestFile);
if(fileUri != null) {
resultIntent = new Intent();
resultIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
resultIntent.setDataAndType(fileUri, getContentResolver().getType(fileUri));
FileSelector.this.setResult(Activity.RESULT_OK, resultIntent);
finish();
}
}
});
}
}
在声明给Activity时,需要添加如下的filter:
<activity android:name=".FileSelector" >
<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="image/*" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
为验证程序的正确性,需要重新创建一个新的工程,在工程代码中,调用FileSelector:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestFileIntent = new Intent();
requestFileIntent.setAction(Intent.ACTION_PICK);
requestFileIntent.setType("image/jpg");
startActivityForResult(requestFileIntent, 0);
}
为了显示用户在FileSelector选择的文件信息,复写如下方法:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
System.out.println("Stop here");
if(resultCode == Activity.RESULT_OK) {
Uri resultUri = data.getData();
try {
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(resultUri, "r");
FileDescriptor fd = pfd.getFileDescriptor();
String mimeType = getContentResolver().getType(resultUri);
Cursor returnCursor = getContentResolver().query(resultUri, null, null, null, null);
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
returnCursor.moveToFirst();
System.out.println("MIMEType: " + mimeType + " nameIndex: " + nameIndex + " sizeIndex: " + sizeIndex);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
参考代码可以从这里下载:http://pan.baidu.com/s/11RO0D
PS:由于个人组织能力太烂,这是本人首次写博客,今天先写到这里吧,请大家容我慢慢整理思路。。。