前言
我前面写过一篇文章:系统下载管理器下载pdf。下载完弹出选择打开方式弹框。
但是我需要的是在应用内预览,所以这种方式pass。
思考
IOS的同事使用WebView直接加载PDF的Url就实现了这个功能。因为IOS的WebView自带浏览器组件,算是个移动版的Safari。
Android的WebView和IOS的实现不一样,不能直接通过远程文件Url实现预览。但是可以另辟蹊径:使用如谷歌文档服务或者mozilla的文档服务(操作Url,拼接字符串)
直接使用一些第三方的自定义控件,实现方式基本是将pdf下载到本地,解析之后显示出来。这里列举几个:AndroidPdfViewer、PdfViewPager。可能出现的问题也挺多的,而且会大大增加apk的体积。
个人极力推荐使用pdf.js,可以直接拉到本地,放到asset里面,然后使用WebView加载。但是会造成apk体积增大5M,跟其他第三方控件相比,已经很小了;还可以让前端的同事把库部署到服务器或者cdn,这样的话本地就只需要写一个h5和js文件,指向cdn地址,最后通过WebView加载。
1. Google PDF Viewer
讲实话,谷歌的文档服务我认为是几种实现方式中最优雅的。不过,需要科学上网(翻墙),最终放弃了这种方式。
重点是这个要拼接的url:https://drive.google.com/viewerng/viewer?embedded=true&url=
WebView webview = (WebView) findViewById(R.id.webview);
webview.getSettings().setJavaScriptEnabled(true);
String pdf = "http://www.adobe.com/devnet/acrobat/pdfs/pdf_open_parameters.pdf";
webview.loadUrl("https://drive.google.com/viewerng/viewer?embedded=true&url=" + pdf);
2.mozilla PDF Viewer
挺好的,不需要翻墙。但是存在跨域问题。(由于浏览器的同源策略,即属于不同域的页面之间不能相互访问各自的页面内容)
重点是这个要拼接的url:http://mozilla.github.io/pdf.js/web/viewer.html?file=
还有一个: http://mozilla.github.io/pdf.js/es5/web/viewer.html?file=
//先设置WebView
webview.getSettings().setJavaScriptEnabled(true);
webview .getSettings().setAllowFileAccess(true);
webview .getSettings().setAllowFileAccessFromFileURLs(true);
//能否访问来自于任何源的文件标识的URL
webview .getSettings().setAllowUniversalAccessFromFileURLs(true);
webview .loadUrl("http://mozilla.github.io/pdf.js/web/viewer.html?file=" + pdfUrl);
可能出现问题,原因多指向库里面的js文件。
No ‘Access-Control-Allow-Origin’ header is present on the requested resource.(所请求的资源上没有“ Access-Control-Allow-Origin”标头。)
chromium: [INFO:CONSOLE(0)] “Uncaught (in promise) UnknownErrorException: The browser/environment lacks native support for critical functionality used by the PDF.js library.please use an ES5-compatible build instead.”(浏览器/环境缺少对PDF.js库使用的关键功能的本机支持。请改用与ES5兼容的版本。)
mozilla PDF Viewer整到项目里
pdfjs下载地址,我选择的ES5版本的稳定版。当然你也可以去pdf.js直接拉master分支或者选择某个TAG版本。不过要注意,下载下的资源build文件夹里的东西可不能少。如果没有请选择其他版本。
下载之后放到asset文件下
//先设置WebView
webview.getSettings().setJavaScriptEnabled(true);
webview .getSettings().setAllowFileAccess(true);
webview .getSettings().setAllowFileAccessFromFileURLs(true);
//能否访问来自于任何源的文件标识的URL
webview .getSettings().setAllowUniversalAccessFromFileURLs(true);
webview .loadUrl("file:///android_asset/pdfjs/web/viewer.html?file="+pdfUrl);
小插曲:关于跨域检查
查看viewer.js发现有一个validateFileURL变量。
var validateFileURL;
{
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 URL(window.location.href).origin || 'null';
if (HOSTED_VIEWER_ORIGINS.includes(viewerOrigin)) {
return;
}
var _ref8 = new URL(file, window.location.href),
origin = _ref8.origin,
protocol = _ref8.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这个数组,如果数组里有viewerOrigin,直接返回,意味着这次校验通过。所以我们可以把我们的地址加到这个数组里,就不会执行后面的检查了。
往下看,当前源与目标源不一样同时没有使用伪协议blob的时候会抛出一个错误:
if (origin !== viewerOrigin && protocol !== 'blob:') {
throw new Error('file origin does not match viewer\'s');
}
- 干脆不让他抛出错误得了,注释掉这行throw的代码。
- 或者直接不调用validateFileURL方法。(我就是这么干的)
简单写了个Demo
地址:PDFPreViewDemo
如果想修改上图的样式,具体就是viewer.html这个文件。我为了隐藏掉顶上的toobar,用chrome浏览器打开viewer.html,摁【F12】后找到了toolbar的div,设置了属性:style=“display: none;”
实现手势放大缩小
直接修改html文件的属性,查看viewer.html文件中有一行:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
把最大缩放规模改成3.0,并添加属性user-scalable=yes:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=3.0,user-scalable=yes">
最后确保webview里有这3句设置:
// 可以缩放
webview.getSettings().setSupportZoom(true);
// 显示放大缩小
webview.getSettings().setBuiltInZoomControls(true);
//设置此属性,可任意比例缩放
webview.getSettings().setUseWideViewPort(true);
mozilla PDF Viewer部署到服务器或者cdn
本来引入到项目里只会造成apk大小变大5M,如果把库放到服务器的话,可能这5M都省了。只需要写2个文件,然后里面的变量指向服务器的资源就行了。
- 首先写一个预览的index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
<title>Document</title>
<style type="text/css">
canvas {
width: 100%;
height: 100%;
border: 1px solid black;
}
</style>
<script src="此处为配置到服务器的地址/build/pdf.min.js"></script>
<script type="text/javascript" src="index.js"></script>
</head>
<body>
</body>
</html>
- 再写一个index.js
var url = location.search.substring(1);
PDFJS.cMapUrl = '此处为服务器地址/cmaps/';
PDFJS.cMapPacked = true;
var pdfDoc = null;
function createPage() {
var div = document.createElement("canvas");
document.body.appendChild(div);
return div;
}
function renderPage(num) {
pdfDoc.getPage(num).then(function (page) {
var viewport = page.getViewport(2.0);
var canvas = createPage();
var ctx = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
page.render({
canvasContext: ctx,
viewport: viewport
});
});
}
PDFJS.getDocument(url).then(function (pdf) {
pdfDoc = pdf;
for (var i = 1; i <= pdfDoc.numPages; i++) {
renderPage(i)
}
});