天天看点

wex5 使用fileApid打开doc、pdf等文件时报错的问题解决(使用替代方法)

前言

最近接手一个跨平台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方法的具体实现:

wex5 使用fileApid打开doc、pdf等文件时报错的问题解决(使用替代方法)

这里其实调用了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的配置使用,有兴趣的朋友可以去看看。

wex5 使用fileApid打开doc、pdf等文件时报错的问题解决(使用替代方法)

照理说,我们打开的是网络文件,为什么会调用的打开本地文件的方法,这还要继续看代码,首先是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);
           

通过这种处理就可以再次实现预览,不过是通过第三方工具实现预览,算是一种折中的方案吧!

最后,希望这篇文章对你有所帮助,另外,本人经验有限,错误的地方希望各位指正!

继续阅读