由于Android的安全机制 ,一个进程默认不能影响另外一个进程的,如读取私有数据 。 那么对于进程间的文件的共享 ,出于安全考虑,用FileProvider。FileProvider会基于manifest中的定义定义的一个xml文件(xml目录 下),为所有定义的文件生成content URIs,这样外部的应用在没有权限的情况下,可以通过授予临时权限的content uri,读取相应的文件。

FileProvider是v4 support中的类 , 就继承ContentProvider。
Specify the FileProvider
既然FileProvider继承于ContentProvider,那么一些"增删查改"的方法就自动封装好了,我们用现成的即可。
在Manifest.xml文件中定义,并用xml文件定义哪些文件可以对外提供
<providerandroid:name="android.support.v4.content.FileProvider"android:authorities="com.ckt.fileproviderservice"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/filepath" /></provider>
android:name
---
定义名子
android:authorities --
定义authority
android:exported="false" 对其它应用不可用
android:grantUriPermissions="true" 既然对其它应用不可用,只能授予content uri临时权限
<meta-data>中的 android:name="android.support.FILE_PROVIDER_PATHS" (这个名子是固定的)和
android:resource 指向一个xml文件,这个xml文件定义了要共享文件的路径
xml/filepath.xml
<paths><files-path name="my_images" path="image/" /><external-path name="my_external" /><cache-path name="my_cache" /></paths>
<files-path> 指向的是内部存储要共享的目录
<external-path> 指向外部存储要共享的目录
<cache-path> 指向缓存要共享的目录
其中name为我们自己定义 的名子,path为具体的路径
如果请求一个default_image.jpg的 content uri,FileProvider返回的content URI是这样的
content://com.ckt.fileproviderservice/my_images/default_image.jpg
Send Binary Content
final Uri uri = FileProvider.getUriForFile(this, AUTHORITY, file);final Intent intent = new Intent(Intent.ACTION_SEND);intent.setType("image/png");intent.putExtra(Intent.EXTRA_STREAM, uri);startActivity(intent);
这是引用的supportv4Demos中的一例子,可以看到共享一个二进制数据需要三步:
1、用Intent.ACTION_SEND
2、设置MIME type
3、把指向数据的uri放到Intent.EXTRA_STREAM中
效果如下:
像这样的分享模式,最常见的就是ActionBar上的共享。
Sharing a File
上面说的是我主动share出去,如果我们在另外一个应用的主动请求这个文件呢,如何达到共享。 例如 ,QQ会进入图库来选择一张图片发送。那么如何做呢,首先需要一个QQ一样的应用,来请求数据 启动另外一个应用,被启动的应用会共享它能共享 的文件,然后我们选择一个文件,确定后返回到原始应用,这个就会得到一个文件。 这样说可能比较模糊,看看具体实现。
先看看效果
首先我们要有客户端的请求:
mGetFileBt.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent(Intent.ACTION_PICK);intent.setType("image/*");startActivityForResult(Intent.createChooser(intent, "Get File"), 666);}});
发送一个Intent,action用Intent.ACTION_PICK,然后设置MIME type
相应的服务端的Manifest文件中定义相应的Intent Filter
<activityandroid:name=".MainActivity"android:label="@string/app_name"android:theme="@style/AppTheme.NoActionBar"><intent-filter><action android:name="android.intent.action.MAIN" /><action android:name="android.intent.action.PICK" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.OPENABLE" /><category android:name="android.intent.category.LAUNCHER" /><data android:mimeType="image/*" /></intent-filter></activity>
我这里为了方便,直接用了入口MainActivity,在intent-filter中加入
<
action android
:
name
=
"android.intent.action.PICK"
/>
<
category android
:
name
=
"android.intent.category.DEFAULT"
/>
<
category android
:
name
=
"android.intent.category.OPENABLE"
/>
Used to indicate that an intent only wants URIs that can be opened with
openFileDescriptor(Uri, String). Openable URIs must support at least the columns defined in OpenableColumns when queried.
<
data android
:
mimeType
=
"image/*"
/>
MainActivity中,我们会先创建一个文件,然后用ListView展示出来
private void createInternalFiles() {mRootDir = getFilesDir();mImagesDir = new File(mRootDir, "Images");if (!mImagesDir.exists()) {mImagesDir.mkdirs();}File mPandaIcon = new File(mImagesDir, "panda.png");Bitmap mPandaBm = BitmapFactory.decodeResource(getResources(), R.mipmap.panda);saveFiles(mPandaBm, mPandaIcon);}private void saveFiles(Bitmap bitmap, File file) {FileOutputStream fos = null;if (bitmap != null) {try {fos = new FileOutputStream(file);bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);} catch (FileNotFoundException e) {e.printStackTrace();}}}
我们在内部存储中创建了一个文件panda.png,然后把一个图片pand写到这个panda.png文件中,这样内部存储现在就有一个文件了。
我们用一个数组存放内部文件的名子和bitmap
private void getBitmapsAndNames() {File[] images = mImagesDir.listFiles();for (int i = 0; i < images.length; i++) {File image = images[i];mFileNames.add(image.getName());try {FileInputStream fis = new FileInputStream(image);Bitmap bitmap = BitmapFactory.decodeStream(fis);mFileBitmaps.add(bitmap);} catch (FileNotFoundException e) {e.printStackTrace();}}}
现在所有的具备了, 我们可以用ListView显示出来了,当然我们还要自己写一个Adapter
public class FileProviderAdapter extends BaseAdapter {private List<String> mData;private Context mContext;private LayoutInflater mLayoutInflater;private List<Bitmap> mBitmaps;public FileProviderAdapter(Context context, List<String> data) {this.mContext = context;this.mData = data;mLayoutInflater = LayoutInflater.from(mContext);}public FileProviderAdapter(Context context, List<String> data, List<Bitmap> bitmaps) {this.mContext = context;this.mData = data;mLayoutInflater = LayoutInflater.from(mContext);this.mBitmaps = bitmaps;}@Overridepublic int getCount() {return mData.size();}@Overridepublic Object getItem(int position) {return mData.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder viewHolder;if(convertView == null){viewHolder = new ViewHolder();convertView = mLayoutInflater.inflate(R.layout.adapter_layout,null);viewHolder.img = (ImageView) convertView.findViewById(R.id.adapter_img);viewHolder.title = (TextView) convertView.findViewById(R.id.adapter_tv);convertView.setTag(viewHolder);}else{viewHolder = (ViewHolder) convertView.getTag();}if(mBitmaps != null){viewHolder.img.setImageBitmap(mBitmaps.get(position));}else{viewHolder.img.setBackgroundResource(R.mipmap.ic_launcher);}viewHolder.title.setText(mData.get(position));return convertView;}public final class ViewHolder{public ImageView img;public TextView title;}}
现在我们来展示 ,并定义item点击事件
mAdapter = new FileProviderAdapter(this, mFileNames, mFileBitmaps);mListView.setAdapter(mAdapter);mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {File file = new File(mImagesDir, mFileNames.get(position));Uri uri = FileProvider.getUriForFile(MainActivity.this, "com.ckt.fileprovidertest.imageprovider", file);Intent intent = new Intent();if (uri != null) {intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);intent.setDataAndType(uri, getContentResolver().getType(uri));setResult(888, intent);} else {intent.setDataAndType(null, "");setResult(RESULT_CANCELED, intent);}isOk = true;}});
setFlags是最好的临时授权方式 ,避免用Context.grantUriPermission(),因为一旦调用这个方法,我们必须用Context.revokeUriPermission来取消这个权限, 而这个setFlags,只要这个activity所在的任务栈没被finish掉,临时权限就一起存在 ,也就是说如果你点back button一起返回finish这个任务栈,或者重启,这个权限就自动消失了。
当我们点击了这个item,我们需要一个button来让我们finish掉这个界面 ,我们在actionbar上定义了一个DONE
@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.menu_main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {int id = item.getItemId();switch (id) {case R.id.action_done:if(isOk) {finish();isOk = false;}default:return super.onOptionsItemSelected(item);}}
现在我们已经完成了setResult工具,我们再回到我们请求的Activity中
@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if (requestCode == 666 && resultCode == 888) {ParcelFileDescriptor fileDescriptor = null;Log.d("david", "onActivityResult");Uri uri = data.getData();Cursor cursor = getContentResolver().query(uri, null, null, null, null);//move to fist cursor ,default is -1cursor.moveToNext();String name = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));String size = cursor.getString(cursor.getColumnIndex(OpenableColumns.SIZE));mNameTv.setText("Name : " + name + " Size : " + size);try {fileDescriptor = getContentResolver().openFileDescriptor(uri, "r");} catch (FileNotFoundException e) {e.printStackTrace();}FileDescriptor fd = fileDescriptor.getFileDescriptor();FileInputStream fis = new FileInputStream(fd);Bitmap bitmap = BitmapFactory.decodeStream(fis);mFileImg.setImageBitmap(bitmap);}}
获得临时授权的uri后,我们可以基本像访问content provider一样,访问这个uri中特定的内容。
ContentResolver.openFileDescriptor(uri,"r") 得到是一个ParcelFileDescriptor,通过这个
ParcelFileDescriptor.getFileDescriptor可以得到FileDescriptor,这个
FileDescriptor就可以用来读取文件了
当然我们可以用这个uri做其它的事情
例如 返回mime type
String mimeType = getContentResolver().getType(returnUri);
本文介绍了Android中利用FileProvider实现跨应用文件共享的方法。详细讲述了如何配置Manifest文件及xml路径文件来定义共享文件,同时提供了发送二进制内容及响应文件请求的具体实现步骤。
5万+

被折叠的 条评论
为什么被折叠?



