cordova插件之下载文件并打开

kfq00 2019-06-28

补充更新

cordova-plugin-file-opener2插件在[email protected]上会报编译错误,换成cordova-plugin-vha-fileopener2即可

前言

近期混合app项目中有文件预览的需求,因文件较多并涉及office、视频等文件格式,采用第三方app打开方案。
实现过程中出现一些android7.0+/8.0+的兼容性问题,特此记录。

采用此预览方案文件会被先下载到本地,cordova-plugin-file-opener2插件其实可以直接打开网络地址来实现预览,采用此方式是基于以下考虑:

  1. 避免重复下载(因app中还有下载功能)
  2. 避免有文件格式解析错误的情况,用户可以到本地再次进行查看
  3. 下载目录可控

框架

项目采用cordova + VUE + MintUI

安装插件

cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-file-transfer
cordova plugin add cordova-plugin-file-opener2

eg: cordova-plugin-file-openercordova-plugin-file-opener2这两个插件都可以打开文件,但cordova-plugin-file-opener2支持cdvfile://协议,兼容android7.0+以上,不会出现文件权限的问题

一、确认API环境

采用Cordova开发的应用在运行的时候,Cordova提供的通过HTML5调用Native功能并不是立即就能使用的,Cordova框架在读入HTML5代码之后,要进行HTML5和Native建立桥接,在未能完成这个桥接的初始的情况下,是不能调用Native功能的。在Cordova框架中,当这个桥接的初始化完成后,会调用他自身特有的事件,即deviceready事件。

deviceready事件是在每回读入HTML的时候都会被调用,而不只是应用启动时调用。

document.addEventListener("deviceready", function () {
    // 现在可以安全的使用设备API
    console.log('Device is Ready!')
}, false);

二、创建有效的文件路径

关于路径的详细解释可以查看文章:
官网API
cordova-plugin-file 文件操作整理系列

使用cordova-plugin-file插件创建有效的文件路径

Device Path cordova.file.* AndroidExtraFileSystems r/w? persistent? OS clears private
file:///android_asset/ applicationDirectory assets r N/A N/A Yes
/data/data/<app-id>/ applicationStorageDirectory - r/w N/A N/A Yes
   cache cacheDirectory cache r/w Yes Yes* Yes
   files dataDirectory files r/w Yes No Yes
      Documents documents r/w Yes No Yes
<sdcard>/ externalRootDirectory sdcard r/w Yes No No
   Android/data/<app-id>/ externalApplicationStorageDirectory - r/w Yes No No
      cache externalCacheDirectory cache-external r/w Yes No** No
      files externalDataDirectory files-external r/w Yes No No
  1. 当目标的WebView的客户(而不是浏览器)或本地应用程序(Windows),你不需要在使用持久性存储使用requestquota。
  2. 在沙盒目录结构中使用window.requestFileSystem
  3. 获取或操作系统文件/目录,可以使用window.resolveLocalFileSystemURL

android7.0+遇到 android.os.FileUriExposedException: file:///storage/emulated.. exposed beyond app through Intent.getData()错误时,要使用window.resolveLocalFileSystemURLcordova.file.externalDataDirectory,不要使用沙盒目录结构

/**
 * desc: 创建文件方法
 */
window.resolveLocalFileSystemURL(
  cordova.file.externalDataDirectory,
  function(fs) {
    fs.getFile(
      _this.fileName, // 创建的文件名
      { create: true, exclusive: true },
      // create:创建新文件,exclusive:文件已存在时抛出异常
      function(fileEntry) {
        // 创建成功回调下载方法写入文件
        _this.downloadFile(fileEntry);
      },
      function(err) {
        // 失败回调
        // 重新读取文件并打开
        fs.getFile(
          _this.fileName,
          { create: false },
          function(fileEntry) {
            // 成功读取文件后调用cordova-plugin-file-opener2插件打开文件
            _this.preView(fileEntry);
          },
          function(err) {
            _this.toast('读取文件失败');
          }
        );
      }
    );
  },
  function(error) {
    _this.toast('进入文件系统失败!');
  }
);

三、下载文件

/**
 * desc: 文件下载方法
 */
function downloadFile(fileEntry) {
  // 初始化进度条并显示
  // 此处采用mint-ui的Progress组件
  _this.progress = 0;
  _this.showProgress = true;
  //实例化
  let fileTransfer = new FileTransfer();
  //监听下载进度
  fileTransfer.onprogress = function(e) {
    if (e.lengthComputable) {
      let progress = e.loaded / e.total;
      // 显示下载进度
      _this.progress = (progress * 100).toFixed(2);
    }
  };
  // 使用fileTransfer.download开始下载
  fileTransfer.download(
    encodeURI(_this.savePath), //uri网络下载路径
    fileEntry.toURL(), //文件本地存储路径
    function(entry) {
      // 下载完成执行本地预览
      if (_this.progress > 1 || _this.progress === 1) {
        _this.showProgress = false;
        entry.file(data => {
          _this.preView(fileEntry);
          // 此处data.type可以直接得到文件的MIME-TYPE类型
        });
      }
    },
    function(error) {
      _this.toast('下载失败!');
    }
  );
}

四、打开文件

/**
 * desc: 文件打开方法
 */
function preview(fileEntry){
    // 调用cordova-plugin-file-opener2插件实现用第三方app打开文件
    cordova.plugins.fileOpener2.showOpenWithDialog(
        // 此处必须填写cdvfile://地址,不然android7.0+会报文件权限错误
        fileEntry.toInternalURL(), //文件本地地址转cdvfile://地址
        fileTypeArr[_this.fileType], //文件类型,这里我是写了一个mime-Type类型合集fileTypeArr来调用 
        function onSuccess(data) {
            console.log('成功预览:' + fileURL);
        },
        function onError(error) {
            _this.toast(
                '出错!请在' + cordova.file.externalDataDirectory + '目录下查看'
            );
        }
    );
}

五、Android8.0+打开apk文件权限问题

Android8.0+上打开apk文件时会报错android.os.FileUriExposedException: file:///storage/emulated/0/test.apk exposed beyond app through Intent.getData()

根据cordova官网提示 在android 8.0+上您的应用程序必须具有ACTION_INSTALL_PACKAGE权限,需要在config.xml添加如下配置:

<platform name="android">
    <config-file parent="/manifest" target="AndroidManifest.xml" xmlns:android="http://schemas.android.com/apk/res/android">
        <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    </config-file>
</platform>

注意安装文件的路径:在Android 7之前,您只能从“外部”分区安装APK。例如,您可以从中安装cordova.file.externalDataDirectory,但不能从中安装cordova.file.dataDirectory。Android 7+没有这个限制。

并在AndroidManifest.xml文件中修改SDK版本

<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23" />

cordova/ionic默认配置16-26 经过多次试,只支持16-23的sdk 版本。版本再高就报以上错误。

结语

至此,Android5.0+已全部兼容。

Android的版本真的是一个大坑,第一次开发混合app被版本搞的焦头烂额,希望能给各位看官一点帮助~
如有疑问,欢迎沟通~

相关推荐