之前碰到一个需求,需要在手机 APP 中显示 pdf 文件。经过调研发现,在电脑上的浏览器如 chrome、safari等,可以直接显示 pdf 文件。由此联想到,在 APP 中能否通过浏览器来加载显示 pdf 文件呢?最后经过测试发现:
- iOS 的 WebView 可以直接加载 pdf 文件并显示出来;
- Android 的 WebView 不支持;
- 在 iOS 中有电子签名的 pdf 文件,关于有电子签名的地方无法显示;
我们需要显示的 pdf 文件基本上都是电子合同相关的 pdf 文件,也就是说基本上都包含了电子签名,典型的如电子发票上的公章。那么怎么实现该功能呢?首先我们排除掉采用原生解析 pdf 文件的方式,网上找了下解析 pdf 文件的第三方开源库,这些库都很大,动则 20M 以上,并且稳定性也不保障,显然对 APP 来说是不划算的。
最后,我们采用了 pdf.js 开源库,通过 WebView 的方式来显示 pdf 文件。
1. 自己构建 pdf.js
1.下载源码到本地:
git clone https://github.com/mozilla/pdf.js.git
cd pdf.js
2.安装 gulp 工具
npm install -g gulp-cli
3.运行本地 demo
npm install
gulp server
构建成功后,浏览器打开地址:http://localhost:8888/web/viewer.html,可以看到里面提供的 demo。
4.构建 pdf.js 文件
//通用构建
gulp generic
//混淆压缩
gulp minified
我这里采用的是 gulp minified
的方式来构建,构建成功后,在 build/minified/
目录下会输出结果,如下所示:
图中红线标注的 viewer.html 就是最终我们用来加载显示 pdf 文件的入口 html 文件。
2. 如何在 WebView 中加载 pdf 文件
将前面构建好的 pdf.js 相关文件拷贝到应用程序里,例如 Android 则拷贝到 assets 目录中,如下图所示:
采用 WebView 来加载本地网页:
// pdf 文件的 url 地址,可以是本地文件,也可以是网络文件
String pdfUrl = "......";
//很重要,允许 js 执行
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
settings.setAllowFileAccess(true);
//很重要,设置允许跨域访问
settings.setAllowFileAccessFromFileURLs(true);
settings.setAllowUniversalAccessFromFileURLs(true);
webView.loadUrl("file:///android_asset/pdfjs/web/viewer.html?file=" + Uri.encode(pdfUrl));
以上是 Android 中的例子,有几点很重要,必须要配置好:
- 必须允许 js 能够执行;
- WebView 必须设置成能够跨域访问;
- pdf 文件地址可以是以 file:// 开头的本地文件,也可以是以 http:// 开头的网络文件;
- file 参数值必须 uri encode;
iOS 的配置与此相似,这里不赘述。但是运行后发现,网页会报错,无法显示 pdf 文件,这时候我们需要修改源码,这是因为源码里限制了 pdf 文件地址必须是同源的。
打开源码目录下面的 web/app.js 文件,找到如下代码,将之注释掉:
if (origin !== viewerOrigin && protocol !== 'blob:') {
throw new Error('file origin does not match viewer\'s');
}
这句代码对 pdf 文件地址来源做了限制,如果涉及到跨域访问,就会直接抛出异常。代码注释掉之后,重新 build 后再运行,应该就可以正常加载显示 pdf 文件了。
3. 如何显示电子签章
这样 build 出的 pdf.js ,是无法显示电子签章的,要显示电子签章,还需要对源码做出修改,找到源码目录下的 src/core/annotation.js 文件,找到如下代码:
// Hide signatures because we cannot validate them, and unset the fieldValue
// since it's (most likely) a `Dict` which is non-serializable and will thus
// cause errors when sending annotations to the main-thread (issue 10347).
if (data.fieldType === 'Sig') {
data.fieldValue = null;
this.setFlags(AnnotationFlag.HIDDEN);
}
同样将这段代码给注释掉,看源码可以知道这里是隐藏掉电子签名了。最后再重新构建之后,就可以通过 WebView 正常加载显示 pdf 文件了。
这种方式对 iOS、Android 都是适用的,并且能够显示 pdf 文件中的电子签名。需要注意的是,最终你需要把构建文件中的一些不必要的文件给删除掉,例如 .map 文件、.pdf 文件,这些都是多余的,最终总的文件大小约 4M 多的样子。