为保障不同应用间文件共享的安全性,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:由于个人组织能力太烂,这是本人首次写博客,今天先写到这里吧,请大家容我慢慢整理思路。。。