在之前的Android端预览PDF方案一文中,曾经提到了使用Mozilla的PDF.js来加载PDF的办法,今天就详细的说一下具体的实现。
- 简介
PDF.js是一个使用HTML5构建的PDF查看器。由社区驱动并得到Mozilla Labs的支持。该项目的目标是创建一个通用的、基于Web标准的平台来实现PDF文件的解析与展示。
提到Mozilla基金会,大家最熟悉的可能就是Firefox了,是的,它是一个非常有名的开源组织,其作品火狐浏览器深受全球大量用户的喜爱(虽然近期的版本不是那么给力)。而今天的主角PDF.js也是该组织的作品,Github上高达25K的stars充分说明该项目所受的关注度和该组织的号召能力。
- 集成
首先明确说明一点,我们可以让后端服务器集成PDF.js,然后进行一下跳转,这样App端直接通过WebView进行访问就能预览PDF了,但显然这跟今天的主题关系不大,就不细说了(实际上也没啥要细说的,都是后端的活儿),只说Android端的使用。
首先,下载PDF.js的全部源码,包括一些图片资源、css文件等。可以下载最新的release包,也可以直接下载master分支,反正基本上都没有严重的问题,求稳就选择release版本吧。然后解压到一个目录中并给一个合适的命名,这里我把这个目录命名为pdf_js。然后把这个目录及所有的文件都复制到App的assets目录下。OK,集成工作就算是完成了,下面就是具体的使用了。
- 使用
分为预览本地PDF和网络PDF两种,原理是一致的,就是PDF.js加载PDF文件后再用WebView进行展示,跟网站端跳转的唯一区别就在于这里是把PDF.js放在了本地。
然后我们需要初始化一个WebView,然后给WebView进行一点点设置:首先,开启JavaScript支持是必须的(我想这是一句废话,因为本文标题里就指明了PDF.js)
webView.getSettings().setJavaScriptEnabled(true);
然后还需要开启allowUniversalAccessFromFileURLs:
webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
这项设置代表什么意思呢?我们来看看官方文档:
Sets whether JavaScript running in the context of a file scheme URL should be allowed to access content from any origin. This includes access to content from other file scheme URLs. SeesetAllowFileAccessFromFileURLs(boolean). To enable the most restrictive, and therefore secure policy, this setting should be disabled. Note that this setting affects only JavaScript access to file scheme resources. Other access to such resources, for example, from image HTML elements, is unaffected. To prevent possible violation of same domain policy when targeting Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 and earlier, you should explicitly set this value to false.
The default value is true for apps targeting Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 and below, and falsewhen targeting Build.VERSION_CODES.JELLY_BEAN and above.
简单来说,该项设置决定了JavaScript能否访问来自于任何源的文件标识的URL。比如我们把PDF.js放在本地assets里,然后通过一个URL跳转访问来自于任何URL的PDF,所以这里我们需要将其设置为true。而一般情况下,为了安全起见,是需要将其设置为false的。
再然后呢,就是简单的WebView.loadUrl来加载了,当然,会因为PDF文件来源不同而稍有不同。
加载本地PDF文件:
webView.loadUrl(“file:///android_asset/pdf_js/web/viewer.html?file=file://” + path);
加载在线PDF文件:
webView.loadUrl(“file:///android_asset/pdf_js/web/viewer.html?file=” + url);
这里我在网上找了一个在线PDF文件的地址,然后兴奋的加载一下,搓搓手等待着成功加载,结果却会发现这样的错:
Message: file origin does not match viewer’s
看起来意思好像是说,文件源与viewer的源不符合,这是啥情况?来查看一下viewer.js的代码,找到报错的地方,是在一个名为validateFileURL的函数里。
var validateFileURL = void 0; { var HOSTED_VIEWER_ORIGINS = ['null', 'http://mozilla.github.io', 'https://mozilla.github.io']; validateFileURL = function validateFileURL(file) { if (file === undefined) { return; } try { var viewerOrigin = new _pdfjsLib.URL(window.location.href).origin || 'null'; if (HOSTED_VIEWER_ORIGINS.includes(viewerOrigin)) { return; } var _ref14 = new _pdfjsLib.URL(file, window.location.href), origin = _ref14.origin, protocol = _ref14.protocol; if (origin !== viewerOrigin && protocol !== 'blob:') { throw new Error('file origin does not match viewer\'s'); } } catch (ex) { var message = ex && ex.message; PDFViewerApplication.l10n.get('loading_error', null, 'An error occurred while loading the PDF.').then(function (loadingErrorMessage) { PDFViewerApplication.error(loadingErrorMessage, { message: message }); }); throw ex; } }; }
定义了一个HOSTED_VIEWER_ORIGINS数组,里面预置了一些主机源。如果viewer所在主机在这个合法数组里,则不进行后续检查,否则进行同源检查,一旦发现是跨域,就会抛出file origin does not match viewer的错误了。显然这么做是为了安全考虑,毕竟有大量的网站提供的在线预览PDF功能就是依赖的PDF.js。不过对于我们Android来说,是部署在设备端而不是服务端,所以简单的把这个函数的调用注释掉,或者在开头添加一个允许跨域的开关
var allowCrossDomain = true;
然后在调用validateFileURL的地方添加一个判断:
if (!allowCrossDomain) { validateFileURL(file); }
正常显示了!
顶栏还有PDF.js特有的一些功能按钮,如果你不需要,就需要自己去更改js和css了,需要有一定的HTML5基础。
我简单的写了一个WebView,支持加载阅读assets目录下的、手机本地存储的和网络上的PDF文件,具体代码请查看:PDFJSAndroid
2019年6月26日 上午10:52 1F
下载代码不好使呀,提示assets下没有PDF.JS文件,找了一个,发现没有报错,也没有正常显示,是PDF.JS版本不对?
2019年10月17日 上午10:35 B1
@ 深思 哎,悲剧啊。根据你的提示,我去github查看了一下,确实少文件了。因为.gitignore文件里默认把build文件夹给忽略了,但是PDF.js的几个关键js文件都在一个叫build的目录里,所以就出错了。我重新上传了,你可以重新下载一下代码了。