在应用间共享文件
对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。—— Google关于Android 7.0行为变更中的说明
关于Uri(Uniform Resource Indentifier)
结构:
[scheme:]scheme-specific-part[#fragment]
[scheme:][//authority][path][?query][#fragment]
[scheme:][//host:port][path][?query][#fragment]
例1:
http://192.168.6.80:8080/download/affix_read_blob.jsp?id=100&name=aaa.doc#fragid1
scheme:http
scheme-specific-part://192.168.6.80:8080/download/affix_read_blob.jsp?id=100&name=aaa.doc
authority:192.168.6.80:8080
host:192.168.6.80
port:8080
path:/download/affix_read_blob.jsp
query:id=100&name=aaa.doc
fragment:fragid01
例2:
content://com.tpp.testfileprovider/extfiles/abc.doc
scheme:content
scheme-specific-part://com.tpp.testfileprovider/extfiles/abc.doc
authority:com.tpp.testfileprovider
host:com.tpp.testfileprovider
port:-1
path:/extfiles/abc.doc
query:null
fragment:null
Uri得到各数据项的方法:
- getScheme();
- getSchemeSpecificPart();
- getFragment();
- getPath();
- getQuery();
- getAuthority();
- getHost();
- getPort();
在android中,除了scheme、authority是必须要有的,其它的几个path、query、fragment,它们每一个可以选择性的要或不要,但顺序不能变。
使用FileProvider
1,指定可共享的目录
创建可共享目录配置文件(假设叫filepaths.xml,创建在res/xml下):
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_download" path="download"/>
</paths>
说明:
external-path 代表的是:
Environment.getExternalStorageDirectory() 这个路径。
此时,path属性的值为:download,所以,可共享的目录为
Environment.getExternalStorageDirectory() + "/download";
注意,只有可共享的目录中的文件才被允许使用,否则程序会抛异常:failed to find configured root that contains 路径+文件;
此时,name属性为“my_download”,它是一个相对路径,假设上述路径中存在一个pdf文件:"abc.pdf",
则它的相对路径就是:/my_download/abc.pdf
当然对于<paths>元素,除了<external-path>,还可以包含以下可使用的子节点:
<file-path name="name" path="path"/>
路径:Context.getFilesDir() + /path
<cache-path name="name" path="path"/>
路径:Context.getCacheDir() + /path
<external-files-path name="name" path="path"/>
路径:Context.getExternalFilesDir(String) + /path
<external-cache-path name="name" path="path"/>
路径:Context.getExternalCacheDir() + /path
注意:external-cache-path直到support-v4:25.0.0才支持
2,在AndroidManifest.xml中定义FileProvider
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
...>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
...
</application>
</manifest>
其中,
<provider>元素的属性
name:android.support.v4.content.FileProvider,固定写法
authorities:指定了内容URI的提供者,一般是此应用的包名+“fileprovider”
<meta-data>元素的属性
name:android.support.FILE_PROVIDER_PATHS,固定写法
resource:就是第一步中定义的可共享目录xml文件的路径
3,代码实现(得到文件Uri并调起指定的Activity)
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/download/abc.pdf";
File file = new File(path);
String authority = "com.example.myapp.fileprovider"; //就是上述第二步中指定的authorities
Uri fileUri = FileProvider.getUriForFile(context, authority, file);
Intent intent = new Intent();
//此处使用的是显示调用,也可以是用隐式调用
intent.setComponent(new ComponentName(
"aaa.bbb.ccc", "aaa.bbb.ccc.dddActivity)); //根据自己的项目配置
if (fileUri != null)
{
//Intent的接受者将会临时被赋予读取Intent中URI数据的权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
String type = getContentResolver().getType(fileUri);
intent.setDataAndType(fileUri, type);
startActivity(intent);
}
4,被调用方需要配置和实现的地方:
4.1,AndroidManifest.xml中提供文件类型支持
<activity>
<intent-filter>
...
<data android:mimeType="application/pdf" />
</intent-filter>
</activity>
此处支持的是pdf格式类型
4.2,得到Uri提供的具体内容:
String data = intent.getDataString();
if (data != null && data.startsWith("content://"))
{
// 得到文件名
String decodeUri = Uri.decode(data);
int index = decodeUri.lastIndexOf("/");
String fileName = decodeUri.substring(index + 1);
// 得到Uri
Uri dataUri = Uri.parse(data);
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(dataUri, "r");
FileDescriptor fd = pfd.getFileDescriptor();
// 创建输入流,得到这个流之后,应用程序可以选择通过流来解析文件,或者把流存成本地临时文件再打开处理
InputStream is = new FileInputStream(fd);
//...
}
一句话概括FileProvider:我给你一个文件流,你不要管文件是哪里来的!
再提供一个Uri生成本地临时文件的方法:
private class GetProviderFile
{
private Uri mFileUri;
private String mTempFileDirectory;
private String mTempFileName;
public GetProviderFile(Uri providerFileUri, String tempFileDirectory, String tempFileName)
{
mFileUri = providerFileUri;
mTempFileDirectory = tempFileDirectory;
mTempFileName = tempFileName;
}
protected Boolean execute()
{
boolean bFinished = false;
try
{
// 判断SD卡是否存在,并且是否具有读写权限
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(mFileUri, "r");
FileDescriptor fd = pfd.getFileDescriptor();
// 创建输入流
InputStream is = new FileInputStream(fd);
// 创建临时文件
File file = new File(mTempFileDirectory);
// 判断文件目录是否存在
if (!file.exists())
{
file.mkdir();
}
File fileLocal = new File(mTempFileDirectory, mTempFileName);
FileOutputStream fos = new FileOutputStream(fileLocal);
// 缓存
byte buf[] = new byte[512];
// 写入到文件中
do
{
int num_read = is.read(buf);
if (num_read <= 0)
{
bFinished = true;
break;
}
// 写入文件
fos.write(buf, 0, num_read);
} while (true);
fos.close();
is.close();
}
}
catch (IOException e)
{
e.printStackTrace();
}
return bFinished;
}
}
说明:此方法是同步方法,但是按照AsyncTask模式来写的,很容易改成异步方式。
使用:
GetProviderFile uri2file = new GetProviderFile (uri, 临时文件目录,, 临时文件名);
if (uri2file.execute())
{
String filePath = 临时文件目录 + File.separator + 临时文件名;
//开始使用临时文件...
}