解决Android7共享文件编辑后无法保存的问题
### 一、问题来源
1. 最近项目上有个需求,需要使用[Adobe Acrobat Reader](http://appstore.huawei.com/app/C37127)打开PDF文件。文件可以正常打开,但是如果对文件进行批注,则无法保存至原文件路径。文件被保存到Acrobat软件内部:
![file](https://i.loli.net/2019/02/12/5c628665c50b6.jpeg)
### 二、解决思路
1. 使用file://方式打开PDF文件,可以正常打开和保存,前提条件是Android编译版本要在Android N(24)以下。示例代码:
```
public void openPDF(){
File targetFile = new File(Environment.getExternalStorageDirectory().getPath()+"/ynApp/1.pdf" );
if(!targetFile.exists()){
Toast.makeText(MainActivity.this, "测试文件不存在", Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri data = Uri.fromFile(targetFile);
intent.setDataAndType(data, "application/pdf");
startActivity(intent);
}
```
2. 使用FileProvider方式共享文件,可以打开文件,但是修改文件后无法保存至原路径。示例代码:
```
public void openPDF(){
File targetFile = new File(Environment.getExternalStorageDirectory().getPath()+"/ynApp/1.pdf" );
if(!targetFile.exists()){
Toast.makeText(MainActivity.this, "测试文件不存在", Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri data = FileProvider.getUriForFile(MainActivity.this, getApplicationContext().getPackageName() +".provider", targetFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(data, "application/pdf");
startActivity(intent);
}
```
3. 经过观察测试,发现QQ下载的文件可以正常打开、修改、保存。于是乎观察Logcat,发现QQ使用的就是方式一。截图为证:
![file](https://i.loli.net/2019/02/12/5c62867f25bbe.png)
4. 经过分析QQ的targetSdkVersion为17,也就是QQ使用的是Android N(24)以下编译的。首先想到就是降低现在项目的compileSdkVersion,但是由于项目比较庞大,降级会带来一系列问题,降级方案pass。
![file](https://i.loli.net/2019/02/12/5c62869c32058.png)
5. 经过观察测试,发现华为自带的文件管理器也是可以正常打开、修改、保存PDF文件的,打开的Intent如下:
![file](https://i.loli.net/2019/02/12/5c6286b24a20d.png)
- 使用的是FileProvider的方式,既然自带管理器可以,那我们写的程序也是可以打开的。仿照着截图上的方式,我们自己构造一个Intent:
```
public void openPDF(){
File targetFile = new File(Environment.getExternalStorageDirectory().getPath()+"/ynApp/1.pdf" );
if(!targetFile.exists()){
Toast.makeText(MainActivity.this, "测试文件不存在", Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri data = FileProvider.getUriForFile(MainActivity.this, getApplicationContext().getPackageName() +".provider", targetFile);
// 构造一个0x13000003,可以写成|的形式
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
intent.setDataAndType(data, "application/pdf");
startActivity(intent);
}
```
- 运行上述代码,WTF!!还是不能保存!!仔细对照下Intent,原来是少了一个extra,但是extra具体是什么东西呢?下面就开启我们的找extra之路。
6. 寻找Extra
- 第一种方法,简单粗暴,直接下载一个[华为文件管理器](http://appstore.huawei.com/app/C10055832)反编译看源码,我想怕是没有比这种方式更直接的了。不过一点是现在找到的这个安装包版本有点旧了,代码结构都不一样。另外一点源码混淆过,看起来有点吃力。
- 第二种方法,让我的应用也出现在备选软件中,我直接来接收这个Intent,这样传来什么数据我都一目了然了。修改程序:
但是经过多次尝试,始终无法看到mParcelledData的值,如果有知道的麻烦告知一下。
![file](https://i.loli.net/2019/02/12/5c6286c8e0c20.png)
![file](https://i.loli.net/2019/02/12/5c6286d92b1f8.png)
7. 等等
我重新看了下自带浏览器发出的uri,格式怎么跟我的不一样啊!原来文件浏览器使用的是Android系统自带的ContentProvider。那我们再来改进下获取文件uri的方式:
```
public static Uri getImageContentUri(Context context, java.io.File imageFile) {
String filePath = imageFile.getAbsolutePath();
Uri baseUri = MediaStore.Files.getContentUri("external");
Cursor cursor = context.getContentResolver().query(baseUri,
new String[] { MediaStore.MediaColumns._ID }, MediaStore.MediaColumns.DATA + "=? ",
new String[] { filePath }, null);
if (cursor != null && cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
return Uri.withAppendedPath(baseUri, String.valueOf(id));
} else {
return null;
}
}
```
最新获取到的uri:content://media/external/file/67。其实也就是查询的/data/data/com.android.providers.media/databases/external.db
![file](https://i.loli.net/2019/02/12/5c6286eab7ca2.png)
搞定!!!
### 三、解决方法
使用Android自带的ContentProvider来获取文件的uri
### 四、参考链接
1. [Android 7.0 行为变更](https://developer.android.com/about/versions/nougat/android-7.0-changes)
2. [Android FileProvider的使用](https://blog.csdn.net/Next_Second/article/details/78585745)
3. [Android 文件绝对路径和Content开头的Uri互相转换](https://www.jianshu.com/p/02fa61d8dbf5)