解决Android7共享文件编辑后无法保存的问题

java Android 18-07-05 02:13 1730  

### 一、问题来源 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)

18-07-07 09:44

评论测试