前言
最近接手一个跨平台app的开发,项目中使用了cordova!ch.ti8m.documenthandler这个插件,这个插件中有个功能是在线预览,但是在使用过程中发现,android系统版本在7.0以上的手机,在打开网络上的文档时(doc、pdf等),程序会奔溃退出,后来发现是因为系统高的版本,对一些特殊文件夹做了保护,访问方式发生了变化,现已解决,以此记录一下,分享给又需要的朋友。
正题
我们先来看看之前的在线预览的写法,和具体实现:
具体代码如下:
require("$UI/system/lib/cordova/cordova");
require("cordova!cordova-open");
require("cordova!cordova-plugin-file");
require("cordova!cordova-plugin-file-transfer");
require("cordova!cordova-plugin-x-toast");
require("cordova!at.modalog.cordova.plugin.cache");
var fileApi = require("$UI/system/components/justep/docCommon/fileApi");
fileApi.browse(url, name).done(function(){
//alert("成功打开");
}).fail(function(){
//alert("打开出错");
});
fileapi.js里边具体代码如下:
/**
* fileEntry
* fullPath: "/test"
* name: "test"
* toURL():"filesystem:http://192.168.1.49:8080/temporary/test"
*
* 浏览本地文件需要传url为 toURL之后的
*/
browse:function(url,fileName, option){
var dtd = $.Deferred();
var self = this;
if(Browser.isX5App){
if(this._isLocalFile(url)){
if(Browser.isAndroid){
window.open(url,"_system");
dtd.resolve(url);
}else if(Browser.isIOS){
window.open(url, '_blank', 'toolbarposition=top,location=no,enableViewportScale=yes');
dtd.resolve(url);
}
}else{
url = this._toFullUrl(url);
/*this.download(url, fileName,option).done(function(nativeUrl){
self.browse(nativeUrl).done(function(url){
dtd.resolve(url);
}).fail(function(err){
dtd.reject(err);
});
}).fail(function(err){
dtd.reject(err);
});*/
plugins.toast.showShortBottom("正在载入文件,请稍候。。。。");
handleDocumentWithURL(function(){
dtd.resolve(url);
},function(err){
dtd.reject(err);
},url);
}
}else{
//TODO:支持浏览器中filesystem的文件浏览
if(justep.Browser.isWeChat){
var fileBrowseNode = $('<div style="z-index:9999;position:absolute;top:0;bottom:0;left:0;right:0;background-color:white;"></div>');
var closeBtn = $('<i class="icon-chevron-left" style="position:absolute;z-index:3;left:25px;width:50px;top:10px;height:36px;"/>').on('click',function(){
fileBrowseNode.remove();
}).appendTo(fileBrowseNode);
var fileFrame = $('<iframe src="'+url+'" style="width:100%;height:100%;padding-top:46px;position:absolute;background-color:white;border:none;"></iframe>').appendTo(fileBrowseNode);
fileBrowseNode.appendTo('body');
dtd.resolve(url);
}else{
window.open(url, '_blank');
dtd.resolve(url);
}
}
return dtd.promise();
},
我们来看看handleDocumentWithURL方法的具体实现:
这里其实调用了java代码,我只截取部分:
private class FileDownloaderAsyncTask extends AsyncTask<Void, Void, File> {
private final CallbackContext callbackContext;
private final String url;
public FileDownloaderAsyncTask(CallbackContext callbackContext,
String url) {
super();
this.callbackContext = callbackContext;
this.url = url;
}
@Override
protected File doInBackground(Void... arg0) {
return downloadFile(url, callbackContext);
}
@Override
protected void onPostExecute(File result) {
if (result == null) {
// case has already been handled
return;
}
Context context = cordova.getActivity().getApplicationContext();
// get mime type of file data
String mimeType = getMimeType(result.getAbsolutePath());
if (mimeType == null) {
callbackContext.error(ERROR_UNKNOWN_ERROR);
return;
}
// start an intent with the file
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(result), mimeType);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
callbackContext.success(); // Thread-safe.
} catch (ActivityNotFoundException e) {
// happens when we start intent without something that can
// handle it
e.printStackTrace();
callbackContext.error(ERROR_NO_HANDLER_FOR_DATA_TYPE);
}
}
}
出问题的代码在哪里呢?就是下边这段代码,其实代码本身没有什么问题,主要问题还是从android N (24)开始,打开一些特殊位置的文件是需要权限的,具体的就是FileProvider的配置使用,有兴趣的朋友可以去看看。
照理说,我们打开的是网络文件,为什么会调用的打开本地文件的方法,这还要继续看代码,首先是downloadfile,其实这里边使用了AsyncTask去异步下载了文件,具体如下:
public FileDownloaderAsyncTask(CallbackContext callbackContext, String url) {
super();
this.callbackContext = callbackContext;
this.url = url;
}
//下载文件
@Override
protected File doInBackground(Void... arg0) {
return downloadFile(url, callbackContext);
}
......
}
具体下载代码如下:
/**
* downloads a file from the given url to external storage.
*
* @param url
* @return
*/
private File downloadFile(String url, CallbackContext callbackContext) {
try {
// get an instance of a cookie manager since it has access to our
// auth cookie
CookieManager cookieManager = CookieManager.getInstance();
// get the cookie string for the site.
String auth = null;
if (cookieManager.getCookie(url) != null) {
auth = cookieManager.getCookie(url).toString();
}
URL url2 = new URL(url);
HttpURLConnection conn = (HttpURLConnection) url2.openConnection();
if (auth != null) {
conn.setRequestProperty("Cookie", auth);
}
InputStream reader = conn.getInputStream();
String extension = this.getExtension(url,conn);
//File f = File.createTempFile(FILE_PREFIX, "." + extension,null);//原代码
//change by pk先获取外部存储缓存路径,如果不存在,则使用内部存储缓存路径
File targetFile = null;
Context context = cordova.getActivity().getApplicationContext();
File targetPath = new File(context.getExternalCacheDir().getAbsolutePath());
if(judeDirExists(targetPath)){
targetFile = new File(context.getExternalCacheDir().getAbsolutePath() + "/temp/");
}else{
targetFile = new File(context.getCacheDir().getAbsolutePath() + "/temp/");
}
if (!targetFile.exists()) {
targetFile.mkdir();
}
File f = File.createTempFile(FILE_PREFIX, "." + extension,targetFile);
// make sure the receiving app can read this file
f.setReadable(true, false);
FileOutputStream outStream = new FileOutputStream(f);
byte[] buffer = new byte[1024];
int readBytes = reader.read(buffer);
while (readBytes > 0) {
outStream.write(buffer, 0, readBytes);
readBytes = reader.read(buffer);
}
reader.close();
outStream.close();
return f;
} catch (FileNotFoundException e) {
e.printStackTrace();
callbackContext.error(ERROR_FILE_NOT_FOUND);
return null;
} catch (IOException e) {
e.printStackTrace();
callbackContext.error(ERROR_UNKNOWN_ERROR);
return null;
}
}
这里使用的targetFile的路径其实就是我们真正打开的路径,只是这里我们已经不能直接访问了,这里访问会报一个异常:
android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/XXX/cache/temp/DH_5675987974107900719.pdf exposed beyond app through Intent.getData()
这个异常其实就是因为android系统问题导致的,具体怎么处理呢?我们继续看,其实处理起来无非两种方法:
- 升级cordova插件,使其具备访问特殊文件夹的权限,但是这种方法很麻烦,因为官方未给出升级的方法,这里不推荐这种方法。
- 还有一种方法就是不用这个工具打开,那我用哪个呢?用cordova.plugins.disusered.open()这个方法,具体操作如下:
var fileTransfer = new FileTransfer();
//这个路径是当前程序的缓存路径,会被不定期清理
var fileURL = cordova.file.externalCacheDirectory;
//url 是网络上文件的地址
var uri = encodeURI(url);
// 本地保存路径
var filePath = fileURL + "/"+fileName;
var fileUrl=encodeURI(filePath);
//首先下载到本地
fileTransfer.download(uri,fileUrl,
function(entry) {
//然后用插件打开
cordova.plugins.disusered.open(filePath, function(suc){
plugins.toast.showShortBottom("正在打开文件,请稍候。。。。");
}, function(err){
plugins.toast.showShortBottom("打开文件出错");
});},
function(entry) {plugins.toast.showShortBottom("下载失败");},false);
通过这种处理就可以再次实现预览,不过是通过第三方工具实现预览,算是一种折中的方案吧!
最后,希望这篇文章对你有所帮助,另外,本人经验有限,错误的地方希望各位指正!