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

Posted 我叫白小飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了wex5 使用fileApid打开docpdf等文件时报错的问题解决(使用替代方法)相关的知识,希望对你有一定的参考价值。

前言

最近接手一个跨平台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);

通过这种处理就可以再次实现预览,不过是通过第三方工具实现预览,算是一种折中的方案吧!
最后,希望这篇文章对你有所帮助,另外,本人经验有限,错误的地方希望各位指正!

以上是关于wex5 使用fileApid打开docpdf等文件时报错的问题解决(使用替代方法)的主要内容,如果未能解决你的问题,请参考以下文章

分享WeX5的正确打开方式——绑定机制

分享WeX5的正确打开方式

分享WeX5的正确打开方式

分享WeX5的正确打开方式——绑定机制

分享WeX5的正确打开方式——数据组件初探

分享WeX5的正确打开方式——绑定机制