最近公司提了个需求,实现手机上点击附件名称可以直接打开预览的功能。可以提供几个思路:
1.利用poi组件把附件转换成html然后再打开,实现比较麻烦。(没有实现,因为服务器端不是我们写的,我们是调用别人的接口)
2.谷歌提供了一个url接口,可以直接实现附件预览,方法“https://docs.google.com/viewer?url=文件具体的路径”。可惜此方法行不通,中国人都明白。
3.其实国内也提供了类似谷歌的预览方式,我找到的有 web office 365。可是这个免费服务内容比较少,要实现客户的需求就咬花钱啦!所以,排除了。
4.先下载,再用第三方软件打开(这里我用的是wps)。
此项目中我用的方法就是第4种,经济实惠又不烦琐。好了闲话少说直接进入主题。
首先下载文件 :
/**
* 打开附件
* @param view
*/
public void openFile(View view) {
NetworkHelper.doGet(
"http://112.27.197.248:9909/ctop/Documents/1500511784978.doc",
null,
new HttpCallbackListener() {
@Override
public void onSuccess(byte[] response) {
Log.e(TAG, response + "");
write(response);
}
@Override
public void onError(Exception e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
}
}
);
}
这里需要往手机中写文件,所以用到了android的存储权限,在android6.0之后需要用到动态申请权限,代码如下:
/**
* 写文件的权限申请
* @param content
*/
private void write(byte[] content) {
this.content = content;
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
}else{
write2SD(content);
}
}
/**
* 权限申请
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE)
{
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
write2SD(content);
} else
{
// Permission Denied
Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
/**
* 写文件
* @param content
*/
private void write2SD(byte[] content) {
FileUtil fileUtil = new FileUtil(this);
File file = fileUtil.write2SDFromByte("doc/", "1500511784978.doc", content);
open(file.getAbsolutePath());
Log.i(TAG, "文件保存到:" + file.getAbsolutePath());
}
附件打开:
/**
* 用第三方软件打开附件
* @param path
*/
private void open(String path) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_DEFAULT);
File file = new File(path);
Uri uri = Uri.fromFile(file);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri, "application/msword");
startActivity(intent);
}
本人的测试手机是小米2s,android5.0(哎,穷屌丝一枚),测试没有问题,然后再拿同事手机(android6.0)测试也可以。
打包提交。本来以为功能完成了,可是现实很残酷,第二天用户反馈,华为荣耀、小米6点击闪退,什么鬼,闪退?跟客户一聊解
情况原来出问题的都是android7.0手机。通过一番搜索发现android7.0不允许以"file:///"(Uri.fromFile(file);)的形式共享文件,要使用
content://URI的格式,并授予URI临时访问权限才行。android也提供了解决方案:FileProvider。
FileProvider的使用:
首先要在清单文件(manifest)中添加
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" />
</provider>
...
</application>
</manifest>
//exported:要求必须为false,为true则会报安全异常。
//grantUriPermissions:true,表示授予 URI 临时访问权限。
然后,再在res/xml下添加provider_paths
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
<external-files-path name="external_files" path="." />
</paths>
其中
<external-path /> 代表目录:Environment.getExternalStorageDirectory()
<external-files-path /> 代表目录:ContextCompat.getExternalFilesDirs(context, null)[0](ContextCompat.getExternalFilesDirs(context, null)返回一个File[])
<cache-path/>代表的根目录: getCacheDir()
<files-path />代表的根目录:context.getFilesDir()
<external-cache-path />代表的根目录:ContextCompat.getExternalCacheDirs(context)[0](ContextCompat.getExternalCacheDirs(context)返回一个File[])
<root-path />代表的目录: "/",根目录
最后,分享文件可修改为:
/**
* 用第三方软件打开附件
* @param path
*/
private void open(String path) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_DEFAULT);
setFlags(intent);
Uri uri = getUri(path);
intent.setDataAndType(uri, "application/msword");
startActivity(intent);
}
/**
* 适配android7.0的文件打开
* @param intent
*/
private void setFlags(Intent intent) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
}
public Uri getUri(String UrlStr) {
File file = new File(UrlStr);
Uri uri;
//判读版本是否在7.0以上
if (Build.VERSION.SDK_INT >= 24) {
//provider authorities
uri = FileProvider.getUriForFile(this, getPackageName()+".provider", file);
} else {
uri = Uri.fromFile(file);
}
return uri;
}
。。。。。。。。
你以为这样就完了?大错特错,这种方案在没有sd卡的机型上完全没有问题,但是要是带有sd卡的机型那就蛋疼了(要把存储设置成sd卡存储才能测试出问题)。首先他会给你一个异常 "Failed to find configured root that contains " + filePath,怎么样找不着北了吧。赶紧找资料发现原来有个标签<root-path />(还纳闷这个标签用来干嘛的),除了这个其他的标签全部不能适用于sd卡上,坑爹啊!然后赶紧在provider_paths.xml中加上<root-path name="linyeting" path="." />测试可以了。注意文件下载保存的路径一定要跟标签设置的保持一致,不然会出现只打开wps而没有内容的情况。出现这种情况的原因应该是标签转换的content://URI路径和文件保存的路径不一样,导致找不到文件(这个坑我花了三天的时间才发现抓狂)。但是我在没有把sd卡设置成默认存储的时候,文件保存路径是:Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),provider_paths.xml中配置的是<external-path name="external_files" path="."/>(这个对应路径是Environment.getExternalStorageDirectory())。可见两个路径不一致,但是文件照样能打开。我也是懵逼了,不知道是啥原因,如有大神知道还望指点一二。。