一、问题来源
- 最近项目上有个需求,需要使用Adobe Acrobat Reader打开PDF文件。文件可以正常打开,但是如果对文件进行批注,则无法保存至原文件路径。文件被保存到Acrobat软件内部:
二、解决思路
- 使用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);
}
- 使用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);
}
- 经过观察测试,发现QQ下载的文件可以正常打开、修改、保存。于是乎观察Logcat,发现QQ使用的就是方式一。截图为证:
- 经过分析QQ的targetSdkVersion为17,也就是QQ使用的是Android N(24)以下编译的。首先想到就是降低现在项目的compileSdkVersion,但是由于项目比较庞大,降级会带来一系列问题,降级方案pass。
- 经过观察测试,发现华为自带的文件管理器也是可以正常打开、修改、保存PDF文件的,打开的Intent如下:
- 使用的是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之路。
- 寻找Extra
- 第一种方法,简单粗暴,直接下载一个华为文件管理器反编译看源码,我想怕是没有比这种方式更直接的了。不过一点是现在找到的这个安装包版本有点旧了,代码结构都不一样。另外一点源码混淆过,看起来有点吃力。
- 第二种方法,让我的应用也出现在备选软件中,我直接来接收这个Intent,这样传来什么数据我都一目了然了。修改程序: 但是经过多次尝试,始终无法看到mParcelledData的值,如果有知道的麻烦告知一下。
- 等等
我重新看了下自带浏览器发出的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 搞定!!!
三、解决方法
使用Android自带的ContentProvider来获取文件的uri