Android实现附件预览

本文介绍了一种解决Android 7.0及以上版本中文件预览问题的方法,特别是针对使用FileProvider和Content URI的问题进行了详细说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近公司提了个需求,实现手机上点击附件名称可以直接打开预览的功能。可以提供几个思路:

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())。可见两个路径不一致,但是文件照样能打开。我也是懵逼了,不知道是啥原因,如有大神知道还望指点一二。。


Demo下载。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值