利用FileProvider建立文件分享
来源:互联网 发布:线性时间选择算法分析 编辑:程序博客网 时间:2024/06/05 15:30
app经常需要给其他的app传送文件,比如QQ里面我们可能需要将图库里面的图片返回,比如图片浏览器想把图片文件传到图片编辑器中,或者文件管理器想让用户在external storage中复制粘贴文件。
为了将文件安全地从我们的应用程序共享给其它应用程序,唯一一种安全的方法就是将文件的URI传输给目标应用并授予该URI临时权限. 因为这权限是对于接收URI的目标应用有效,并且是临时的,会自动失效,所以这种方式是安全的(Android可以用FileProvider中的getUriForFile()来获取文件的URI)
为了传输文件,我们需要建立两个应用,一个应用来请求数据 启动另外一个应用,被启动的应用会共享它能共享 的文件,然后我们选择一个文件,确定后返回到原始应用,这个就会得到一个文件。
实例
下面看一个具体的实现:
用来请求数据的应用我们称之为客户端(Client),另外一个用于分享数据的应用我们称之为服务端(Server)。
要实现的效果是:客户端请求数据,打开服务端,点击服务端中的图片返回到客户端。
设置文件分享
为了安全的提供一个文件让其他app访问,你需要将文件以URI的形式来提供出来.Android中有个类可以帮助我们,就是FileProvider,它能够基于xml中相应的配置生成相应的文件的URI。
step 1: 设置FileProvider
为了给应用程序定义一个FileProvider,需要在Manifest清单文件中定义一个entry,该entry指明了需要使用的创建Content URI的Authority。此外,还需要一个XML文件的文件名,该XML文件指定了我们的应用可以共享的目录路径。
下例展示了如何在清单文件中添加标签,来指定FileProvider类,Authority及XML文件名:
服务端(Service)
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.service"> <application ...> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.service.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> ... </application></manifest>
manifest中定义的时候有两点需要注意 :
android:authorities 字段指定了希望使用的Authority,该Authority针对于FileProvider所生成的content URI。本例中我的应用的包名为
com.example.service
对于自己的应用,要在我们的应用程序包名(android:package的值)之后继续追加“fileprovider”来指定Authority。<provider>
下的<meta-data>
指向了一个XML文件,该文件指定了我们希望共享的目录路径。“android:resource”属性字段是这个文件的路径和名字(无“.xml”后缀)
step 2: 设置合适的路径
一旦在Manifest清单文件中为自己的应用添加了FileProvider,就需要指定我们希望共享文件的目录路径。为指定该路径,首先要在“res/xml/”下创建文件“filepaths.xml”。在这个文件中,为每一个想要共享目录添加一个XML标签。下面的是一个“res/xml/filepaths.xml”的内容样例。
服务端(Service)
<?xml version="1.0" encoding= "utf-8"?><resources> <paths > <files-path path="images/" name="myimages" /> </paths ></resources>
不知道怎么创建xml文件的可以查看这里。
对于上面的代码有几点需要说明:
<files-path>
标签共享的是在我们应用的内部存储中“files/”目录下的目录(等同于用getFilesDir()返回的路径)。path属性的值表示的是前面的文件夹下的子文件,比如上例表示的是“files/images”。
name属性表示的是与路径对应的在URI中的值。
<path>
这个元素可以包含多个子标签,每一个都可以设置不同的分享路径,除了上例中使用的<files-path>
之外,还可以使用<external-path>
来分享external storage中的文件,还有<cache-path>
用来分享internal storage中的cache文件。
有一点需要注意的是:
通过xml文件的方式来分享路径是唯一的方式,不能通过代码添加路径.
现在我们有一个完整的FileProvider声明,它在应用程序的内部存储中“files/”目录或其子目录下创建文件的Content URI。当我们的应用为一个文件创建了Content URI,该Content URI将会包含下列信息:
<provider>
标签中指定的Authority(“com.example.myapp.fileprovider”);路径“myimages/”;
文件的名字
比如本例子定义了一个FileProvider,然后我们需要一个文件“panda.jpg”的Content URI,FileProvider会返回如下URI:
content://com.example.service.fileprovider/myimages/panda.jpg
分享文件
通过上述设置好之后,你的app就可以响应其他app的文件请求了.而对于如何响应,一种方法是服务器app(也就是你的app)提供一个文件选择接口,让请求的app来调用,然后它就能通过该接口获得选中文件的URI.
step 1 : 接收文件请求
为了接收文件请求和返回相应的URI,你的app需要提供一个文件选择的Activity,客服端app通过调用startActivityForResult()并携带一个action为ACTION_PICK的intent来启动你的Activity,然后你做相应的处理并返回结果。
step 2 : 创建一个选择文件的Activity
为建立一个选择文件的Activity,首先需要在Manifest清单文件中定义Activity,在其Intent过滤器中,匹配ACTION_PICKAction及CATEGORY_DEFAULT和CATEGORY_OPENABLE这两种Category。另外,还需要为应用程序设置MIME类型过滤器,来表明我们的应用程序可以向其他应用程序提供哪种类型的文件。
下面这段代码展示了如何在清单文件中定义新的Activity和Intent过滤器:
服务端(Service)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> ... <application> ... <activity android:name=".MainActivity" android:label="File Selector" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.PICK"/> <category android:name="android.intent.category.LAUNCHER" /> <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> <provider> ... </provider>
step 3 : 在代码中定义文件选择Activity
定义一个Activity子类,用于显示在内部存储的“files/images/”目录下可以获得的文件,然后允许用户选择期望的文件。下面代码展示了如何定义该Activity,并令其响应用户的选择:
服务端(Service)
MainActivity.java :
我们会先创建一个文件,然后用ListView展示出来
public class MainActivity extends Activity { private File mPrivateRootDir; private File mImagesDir; File[] mImageFiles; //存放图片 List<Bitmap> mBitmapList; //存放图片名称 List<String> mBitmaoNamesList; private ListView listView; FileProviderAdapter myAdapter; boolean isOK; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); createInternalFiles(); getBitmapsAndNames(); listView = (ListView) this.findViewById(R.id.listView); myAdapter = new FileProviderAdapter(this, mBitmaoNamesList, mBitmapList); listView.setAdapter(myAdapter); ... } //我们用一个数组存放内部文件的名子和bitmap private void getBitmapsAndNames() { mImageFiles = mImagesDir.listFiles(); mBitmapList = new ArrayList<Bitmap>(); mBitmaoNamesList = new ArrayList<String>(); for (int i = 0; i < mImageFiles.length; i++) { File image = mImageFiles[i]; mBitmaoNamesList.add(image.getName()); try { FileInputStream fis = new FileInputStream(image); Bitmap bitmap = BitmapFactory.decodeStream(fis); mBitmapList.add(bitmap); } catch (FileNotFoundException e) { e.printStackTrace(); } } } //内部存储中创建了一个文件panda.jpg,事先需要我们把panda.jpg文件放入mipmap中 private void createInternalFiles() { mPrivateRootDir = getFilesDir(); mImagesDir = new File(mPrivateRootDir, "images"); if (!mImagesDir.exists()) { mImagesDir.mkdirs(); } File mPandaIcon = new File(mImagesDir, "panda.jpg"); Bitmap pandaBp = BitmapFactory.decodeResource(getResources(), R.mipmap.panda); saveFiles(mPandaIcon, pandaBp); } //保存文件 private void saveFiles(File mPandaIcon, Bitmap pandaBp) { FileOutputStream fos = null; if (pandaBp != null) { try { fos = new FileOutputStream(mPandaIcon); pandaBp.compress(Bitmap.CompressFormat.JPEG, 100, fos); } catch (FileNotFoundException e) { e.printStackTrace(); } } }...}
创建一个ListView的子项布局文件:
list_item.xml :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/adpter_img" android:layout_width="50dp" android:layout_height="50dp" android:src="@mipmap/ic_launcher"/> <TextView android:id="@+id/adapter_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="15dp" android:textSize="18sp" android:textColor="@color/colorPrimaryDark" android:text="Hello"/></LinearLayout>
现在我们可以用ListView显示出来了,当然我们还要自己写一个Adapter**(FileProviderAdapte)**:
服务端(Service)
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> mBitmaoNamesList) { this.mContext = context; this.mData = mBitmaoNamesList; mLayoutInflater = LayoutInflater.from(mContext); } public FileProviderAdapter(Context context, List<String> mBitmaoNamesList, List<Bitmap> mBitmapList) { this.mContext = context; this.mData = mBitmaoNamesList; mLayoutInflater = LayoutInflater.from(mContext); this.mBitmaps = mBitmapList; } @Override public int getCount() { return mData.size(); } @Override public Object getItem(int position) { return mData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if(convertView == null){ viewHolder = new ViewHolder(); convertView = mLayoutInflater.inflate(R.layout.list_item,null); viewHolder.img = (ImageView) convertView.findViewById(R.id.adpter_img); viewHolder.title = (TextView) convertView.findViewById(R.id.adapter_txt); 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; }}
接下来我们为listView定义点击事件。
step 4 : 响应一个文件选择
一旦用户选择了一个文件,你的应用就要决定具体是哪个文件并产生相应的URI,比如上例,我们把文件列在ListView中,当用户点击了某个文件,你可以在ListView的onItemClick()f方法中拿到相关信息,就可以知道是哪个文件.然后将该文件之前在<provider>
中定义的FileProvider的authority以及Context这三个作为参数,调用getUriForFile()方法,就可以得到一个URI.如下示例:
protected void onCreate(Bundle savedInstanceState) { ... listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { File file = new File(mImagesDir, mBitmaoNamesList.get(position)); Uri uri = FileProvider.getUriForFile(MainActivity.this, "com.example.service.fileprovider", file); Intent intent = new Intent("com.example.myapp.ACTION_RETURN_FILE"); if (uri != null) { //给提供的分享文件授权 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(uri, getContentResolver().getType(uri)); MainActivity.this.setResult(Activity.RESULT_OK, intent); } else { intent.setDataAndType(null, ""); MainActivity.this.setResult(RESULT_CANCELED, intent); } isOK = true; } }); ... }
记住你要生成的URI的文件必须是在meta-data中定义的路径下的文件,否则会报错。
setFlags是最好的临时授权方式 ,避免用Context.grantUriPermission(),因为一旦调用这个方法,我们必须用Context.revokeUriPermission来取消这个权限, 而这个setFlags,只要这个activity所在的任务栈没被finish掉,临时权限就一起存在 ,也就是说如果你点back button一起返回finish这个任务栈,或者重启,这个权限就自动消失了。
当我们点击了这个item,我们需要一个button来让我们finish掉这个界面 ,我们在actionbar上定义了一个DONE。
在res/menu/中创建menu_main.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_done" android:title="DONE" app:showAsAction="ifRoom" /></menu>
服务端(Service)
MainActivity.java:
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public 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工具,(设置一个Intent将相应参数传入,然后传入setResult()中,当该Activity结束的时候,客户端app就会收到这个Intent对象),我们再回到我们请求的Activity中:
请求分享一个文件
Client(客户端)
MainActivity.java:
首先我们要有客户端的请求, 点击按钮,弹出服务端应用:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)this.findViewById(R.id.textView); imageView = (ImageView)this.findViewById(R.id.client_img); button = (Button)this.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mRequestFileIntent = new Intent(Intent.ACTION_PICK); mRequestFileIntent.setType("image/jpg"); startActivityForResult(Intent.createChooser(mRequestFileIntent, "Get File"), 666); } }); }
访问已请求的文件
客户端app在onActivityResult()方法中拿到服务器app返回的URI之后,就可以通过获取该文件的FileDescriptor访问该文件了.
客户端app唯一能够访问的文件就是拿到的URI的匹配文件,服务器app的其他文件它发现不了也打开不了,因为URI中不包含路径.
下面看具体处理示例:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode==600&&resultCode == RESULT_OK) { Log.d("david", "onActivityResult"); Uri uri = data.getData(); Cursor cursor = getContentResolver().query(uri, null, null, null, null); //move to fist cursor ,default is -1 cursor.moveToNext(); String name = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); String size = cursor.getString(cursor.getColumnIndex(OpenableColumns.SIZE)); textView.setText("Name : " + name + " Size : " + size); try { mInputPFD = getContentResolver().openFileDescriptor(uri, "r"); } catch (FileNotFoundException e) { e.printStackTrace(); Log.e("MainActivity", "File not found."); } FileDescriptor fd = mInputPFD.getFileDescriptor(); FileInputStream fis = new FileInputStream(fd); Bitmap bitmap = BitmapFactory.decodeStream(fis); imageView.setImageBitmap(bitmap); } }
获得临时授权的uri后,我们可以基本像访问content provider一样,访问这个uri中特定的内容。
ContentResolver.openFileDescriptor(uri,”r”) 得到是一个ParcelFileDescriptor,通过这个ParcelFileDescriptor.getFileDescriptor可以得到FileDescriptor,这个FileDescriptor就可以用来读取文件了
当然我们可以用这个uri做其它的事情
例如 返回mime type
String mimeType = getContentResolver().getType(returnUri);
效果展示:
放上Client应用MainActivity.java的完整代码:
public class MainActivity extends AppCompatActivity { private TextView textView; private ImageView imageView; private Button button; private Intent mRequestFileIntent; private ParcelFileDescriptor mInputPFD; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)this.findViewById(R.id.textView); imageView = (ImageView)this.findViewById(R.id.client_img); button = (Button)this.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mRequestFileIntent = new Intent(Intent.ACTION_PICK); mRequestFileIntent.setType("image/jpg"); startActivityForResult(Intent.createChooser(mRequestFileIntent, "Get File"), 666); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode==600&&resultCode == RESULT_OK) { Log.d("david", "onActivityResult"); Uri uri = data.getData(); Cursor cursor = getContentResolver().query(uri, null, null, null, null); //move to fist cursor ,default is -1 cursor.moveToNext(); String name = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); String size = cursor.getString(cursor.getColumnIndex(OpenableColumns.SIZE)); textView.setText("Name : " + name + " Size : " + size); try { mInputPFD = getContentResolver().openFileDescriptor(uri, "r"); } catch (FileNotFoundException e) { e.printStackTrace(); Log.e("MainActivity", "File not found."); } FileDescriptor fd = mInputPFD.getFileDescriptor(); FileInputStream fis = new FileInputStream(fd); Bitmap bitmap = BitmapFactory.decodeStream(fis); imageView.setImageBitmap(bitmap); } }}
放上Service应用MainActivity.java的完整代码:
public class MainActivity extends AppCompatActivity { 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; List<Bitmap> mBitmapList; List<String> mBitmaoNamesList; private ListView listView; FileProviderAdapter myAdapter; boolean isOK; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); createInternalFiles(); getBitmapsAndNames(); listView = (ListView) this.findViewById(R.id.listView); myAdapter = new FileProviderAdapter(this, mBitmaoNamesList, mBitmapList); listView.setAdapter(myAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { File file = new File(mImagesDir, mBitmaoNamesList.get(position)); Uri uri = FileProvider.getUriForFile(MainActivity.this, "com.example.service.fileprovider", file); Intent intent = new Intent("com.example.myapp.ACTION_RETURN_FILE"); if (uri != null) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(uri, getContentResolver().getType(uri)); MainActivity.this.setResult(Activity.RESULT_OK, intent); } else { intent.setDataAndType(null, ""); MainActivity.this.setResult(RESULT_CANCELED, intent); } isOK = true; } }); } private void getBitmapsAndNames() { mImageFiles = mImagesDir.listFiles(); mBitmapList = new ArrayList<Bitmap>(); mBitmaoNamesList = new ArrayList<String>(); for (int i = 0; i < mImageFiles.length; i++) { File image = mImageFiles[i]; mBitmaoNamesList.add(image.getName()); try { FileInputStream fis = new FileInputStream(image); Bitmap bitmap = BitmapFactory.decodeStream(fis); mBitmapList.add(bitmap); } catch (FileNotFoundException e) { e.printStackTrace(); } } } private void createInternalFiles() { mPrivateRootDir = getFilesDir(); mImagesDir = new File(mPrivateRootDir, "images"); if (!mImagesDir.exists()) { mImagesDir.mkdirs(); } File mPandaIcon = new File(mImagesDir, "panda.jpg"); Bitmap pandaBp = BitmapFactory.decodeResource(getResources(), R.mipmap.panda); saveFiles(mPandaIcon, pandaBp); } private void saveFiles(File mPandaIcon, Bitmap pandaBp) { FileOutputStream fos = null; if (pandaBp != null) { try { fos = new FileOutputStream(mPandaIcon); pandaBp.compress(Bitmap.CompressFormat.JPEG, 100, fos); } catch (FileNotFoundException e) { e.printStackTrace(); } } } public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public 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); } }}
- 利用FileProvider建立文件分享
- 利用FileProvider建立文件分享
- FileProvider文件分享
- 安卓下的文件分享——FileProvider
- android学习(十二) 分享文件 FileProvider
- android建立文件分享
- 使用FileProvider共享文件
- FileProvider文件共享
- 使用FileProvider共享文件
- FileProvider android 7 文件共享
- android文件FileProvider共享相关
- FileProvider
- FileProvider
- FileProvider
- Google Training 建立分享内容的APP ------ 分享文件
- 案例分享_利用CMS建立一个资讯网站
- Android7.0 应用间共享文件 FileProvider
- Android 应用间共享文件(FileProvider)
- win2003 DNS服务器配置方法[图文详解]
- python数据结构学习笔记-2016-10-05-02-抽象数据类型(二)
- effective stl 第40条:若一个类是函数子,则应使他可配接
- Linux Notes: alias and unalias
- 网狐 自绘 倒计时和准备按钮
- 利用FileProvider建立文件分享
- Prime Path (HDU1973/POJ3126)(B)
- MFC中CSkinImage显示PNG图片,包括大图中含有一串小图(网狐)
- js之bom_技术
- 网狐 "由于网络问题,您已经与服务器断开连接,请重新连接"
- 45. Jump Game II
- android studio for android learning (二十六 )自定义控件理解与浅析(1)
- dup and dup2的剖析
- 1060. Are They Equal (25)