2、安卓编辑器中建立 Layout ,添加 WebView :
3、extends Activity 将上面的 WebView 赋值给变量(实例化?),并设置其大堆属性,以扩展其功能:
6、重写 WebView 的下载监听器 DownloadListener
参考资料:
1.Android WebView支持下载blob协议文件_Misdirection_XG的博客-CSDN博客_android blob
2. Android Webview实现文件下载功能 – huidaoli – 博客园
3.base64和Blob互相转换_weixin_30776863的博客-CSDN博客
4.Android:你要的WebView与JS交互方式都在这里了 – 百度文库
应用于:html转pdf文件下载之最合理的方法支持中文_jessezappy的博客-CSDN博客
2.手机打开以上网页下载 pdf 文件,并用外部程序打开 pdf 文件。
之前的文章:html转pdf文件下载之最合理的方法支持中文_jessezappy的博客-CSDN博客 ,中已经实现了网页动态生成 pdf 文件,通过好多种方式都可在 PC 端实现自动下载动态生成的 pdf 文件,如:
但我项目原计划是用手机打开这个网页下载 pdf 的,那些方法到了手机端,用 WebView 打开网页后,均无法下载由网页 js 生成的 pdf 文档。
深度研究 jspdf.umd.js 后,发现,其输出 pdf 数据有好多种参数:
/**
* Generates the PDF document.
*
* If `type` argument is undefined, output is raw body of resulting PDF returned as a string.
*
* @param {string} type A string identifying one of the possible output types.<br/>
* Possible values are: <br/>
* 'arraybuffer' -> (ArrayBuffer)<br/>
* 'blob' -> (Blob)<br/>
* 'bloburi'/'bloburl' -> (string)<br/>
* 'datauristring'/'dataurlstring' -> (string)<br/>
* 'datauri'/'dataurl' -> (undefined) -> change location to generated datauristring/dataurlstring<br/>
* 'dataurlnewwindow' -> (window | null | undefined) throws error if global isn't a window object(node)<br/>
* 'pdfobjectnewwindow' -> (window | null) throws error if global isn't a window object(node)<br/>
* 'pdfjsnewwindow' -> (wind | null)
* @param {Object|string} options An object providing some additional signalling to PDF generator.<br/>
* Possible options are 'filename'.<br/>
* A string can be passed instead of {filename:string} and defaults to 'generated.pdf'
* @function
* @instance
* @returns {string|window|ArrayBuffer|Blob|jsPDF|null|undefined}
* @memberof jsPDF#
* @name output
*/
var output = API.output = API.__private__.output = SAFE(function output(type, options) {
options = options || {};
if (typeof options === "string") {
options = {
filename: options
};
} else {
options.filename = options.filename || "generated.pdf";
}
switch (type) {
case undefined:
return buildDocument();
case "save":
API.save(options.filename);
break;
case "arraybuffer":
return getArrayBuffer(buildDocument());
case "blob":
return getBlob(buildDocument());
case "bloburi":
case "bloburl":
// Developer is responsible of calling revokeObjectURL
if (typeof globalObject.URL !== "undefined" && typeof globalObject.URL.createObjectURL === "function") {
return globalObject.URL && globalObject.URL.createObjectURL(getBlob(buildDocument())) || void 0;
} else {
console.warn("bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser.");
}
break;
case "datauristring":
case "dataurlstring":
var dataURI = "";
var pdfDocument = buildDocument();
try {
dataURI = btoa(pdfDocument);
} catch (e) {
dataURI = btoa(unescape(encodeURIComponent(pdfDocument)));
}
return "data:application/pdf;filename=" + options.filename + ";base64," + dataURI;
case "pdfobjectnewwindow":
if (Object.prototype.toString.call(globalObject) === "[object Window]") {
var pdfObjectUrl = "https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.1.1/pdfobject.min.js";
var integrity = ' integrity="sha512-4ze/a9/4jqu+tX9dfOqJYSvyYd5M6qum/3HpCLr+/Jqf0whc37VUbkpNGHR7/8pSnCFw47T1fmIpwBV7UySh3g==" crossorigin="anonymous"';
if (options.pdfObjectUrl) {
pdfObjectUrl = options.pdfObjectUrl;
integrity = "";
}
var htmlForNewWindow = "<html>" + '<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style><body><script src="' + pdfObjectUrl + '"' + integrity + '></script><script >PDFObject.embed("' + this.output("dataurlstring") + '", ' + JSON.stringify(options) + ");</script></body></html>";
var nW = globalObject.open();
if (nW !== null) {
nW.document.write(htmlForNewWindow);
}
return nW;
} else {
throw new Error("The option pdfobjectnewwindow just works in a browser-environment.");
}
case "pdfjsnewwindow":
if (Object.prototype.toString.call(globalObject) === "[object Window]") {
var pdfJsUrl = options.pdfJsUrl || "./examples/PDF.js/web/viewer.html";
var htmlForPDFjsNewWindow = "<html>" + "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style>" + '<body><iframe id="pdfViewer" src="' + pdfJsUrl + "?file=&downloadName=" + options.filename + '" width="500px" height="400px" />' + "</body></html>";
var PDFjsNewWindow = globalObject.open();
console.log(htmlForPDFjsNewWindow);
if (PDFjsNewWindow !== null) {
PDFjsNewWindow.document.write(htmlForPDFjsNewWindow);
var scope = this;
PDFjsNewWindow.document.documentElement.querySelector("#pdfViewer").onload = function () {
PDFjsNewWindow.document.title = options.filename;
PDFjsNewWindow.document.documentElement.querySelector("#pdfViewer").contentWindow.PDFViewerApplication.open(scope.output("bloburl"));
};
}
return PDFjsNewWindow;
} else {
throw new Error("The option pdfjsnewwindow just works in a browser-environment.");
}
case "dataurlnewwindow":
if (Object.prototype.toString.call(globalObject) === "[object Window]") {
var htmlForDataURLNewWindow = "<html>" + "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style>" + "<body>" + '<iframe src="' + this.output("datauristring", options) + '"></iframe>' + "</body></html>";
var dataURLNewWindow = globalObject.open();
if (dataURLNewWindow !== null) {
dataURLNewWindow.document.write(htmlForDataURLNewWindow);
dataURLNewWindow.document.title = options.filename;
}
if (dataURLNewWindow || typeof safari === "undefined") return dataURLNewWindow;
} else {
throw new Error("The option dataurlnewwindow just works in a browser-environment.");
}
break;
case "datauri":
case "dataurl":
return globalObject.document.location.href = this.output("datauristring", options);
default:
return null;
}
});
以上这些参数,在 CallBack 中调用 pdf.Output 时使用:
var link = document.getElementById('linklink');
link.target = '_blank';
//link.href = window.URL.createObjectURL(convertBase64UrlToBlob(pdf.output('datauristring',{filename: 'A4.pdf'})));//140ms Base64 数据转 Blob
//link.href = window.URL.createObjectURL(pdf.output('blob',{filename: 'A4.pdf'}));//77ms Base64 数据
link.href = pdf.output('bloburi');//77ms 直接输出Blob 链接
//link.href =
//pdf.output('pdfobjectnewwindow',{filename: 'A41.pdf'});//弹出对象窗口,无用
//link.href = pdf.output('dataurl',{filename: 'A4.pdf'});//数据链接无用
//pdf.output('pdfjsnewwindow',{filename: 'A42.pdf'});//弹出窗口
//pdf.output('dataurlnewwindow',{filename: 'A43.pdf'});//弹出窗口,无用
link.download ="A41.pdf";
link.text='点击这里下载';
经过对比,发现在不考虑后台生成 pdf 文件的情况下,只有生成 Blob 链接适合用于手机前台下载,但只有华为自带浏览器支持下载 blob 链接而且还必须用华为浏览器打开那个网页才行,QQ、百度等均不支持下载 blob 链接。
jspdf 中原例子里面使用的是 iframe.src = pdf.output(‘datauristring’); 方式,返回的是 Base64 编码的 pdf 数据,可以使用参考资料 3 中的 convertBase64UrlToBlob 转换为 Blob 链接,分析 jspdf 的 Out 方法后发现其可直接输出 Blob 链接,那么就暂时用不到参考资料 3 中的方法了。
最终决定,由网页 JS 生成 Blob 链接给 A 标签,在手机端点击这个 A 标签下载为 pdf 文档,并打开。
(刚刚写了一大段,按了下 Ctrl+z 就全没了,草稿也没了,是要我重新梳理下吗???)
那么,就重新梳理,整理一下解决方法好了,零碎的分析就不写了。
1、已有可实用的动态 pdf 生成方案,详见:
html转pdf文件下载之最合理的方法支持中文_jessezappy的博客-CSDN博客
2、安卓编辑器中建立 Layout ,添加 WebView :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/webfrm"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<WebView
android:id="@+id/webshow"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true" />
</RelativeLayout>
3、extends Activity 将上面的 WebView 赋值给变量(实例化?),并设置其大堆属性,以扩展其功能:
WebView mWebView;
onCreate 中赋值 mWebView:
mWebView=(WebView)findViewById(R.id.webshow);
setWebStyle();
@SuppressLint("SetJavaScriptEnabled")
private void setWebStyle() {
WebSettings webseting = mWebView.getSettings();
webseting.setAppCachePath(getApplicationContext().getCacheDir().getAbsolutePath());
webseting.setUseWideViewPort(true);
webseting.setLoadWithOverviewMode(true);
//webseting.setPluginState(WebSettings.PluginState.ON);
webseting.setDomStorageEnabled(true);//最重要的方法,一定要设置,这就是出不来的主要原因 //webseting.setDomStorageEnabled(true);
webseting.setSupportZoom(true);
webseting.setDefaultTextEncodingName("utf-8");
/* 下载blob准备 */
webseting.setJavaScriptEnabled(true);
webseting.setJavaScriptCanOpenWindowsAutomatically(true);
/***********************/
mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);// 去掉底部和右边的滚动条
mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); // 去掉底部和右边的滚动条
mWebView.requestFocus();
webseting.setCacheMode(WebSettings.LOAD_DEFAULT); // 默认使用缓存
// webseting.setCacheMode(WebSettings.LOAD_NO_CACHE); //默认不使用缓存!
webseting.setAppCacheMaxSize(1024*1024*20);//设置缓冲大小,便于第一次缓存字体,否则每次下载字体需时太长。
String appCacheDir=this.getApplicationContext().getDir("cache",Context.MODE_PRIVATE).getPath();
webseting.setAppCachePath(appCacheDir);
webseting.setAllowFileAccess(true);
webseting.setAppCacheEnabled(true);
webseting.setCacheMode(WebSettings.LOAD_DEFAULT|WebSettings.LOAD_CACHE_ELSE_NETWORK);
//webview 动作重写
mWebView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return false;// false 显示frameset, true 不显示Frameset ,内嵌页面
//return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
//有页面跳转时被回调
//dialog = ProgressDialog.show(webxj.this,null,"数据加载中,请稍侯...");
super.onPageStarted(view, url,favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
//页面跳转结束后被回调
//dialog.dismiss();
super.onPageFinished(view, url);
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);//错误处理+ description
Toast.makeText(webxj.this, "错误:请检查网络链接! " , Toast.LENGTH_SHORT).show();
dialog.dismiss();
new Handler().postDelayed(new Runnable() {
public void run() {
mWebView.loadUrl(urlw); //显示等待画面
}
}, 500);
}
});
//禁用webview右键功能:长按
mWebView.setOnLongClickListener(new OnLongClickListener(){
public boolean onLongClick(View v) {
// TODO Auto-generated method stub
return true;
}
});
//blob
//下载支持,A 标签内需 download
mWebView.setDownloadListener(new MyWebViewDownLoadListener()); // 设置 WebView 下载监听器
mWebView.addJavascriptInterface(mDownloadBlobFileJSInterface, "Android"); // Blob 下载 js 定义
mDownloadBlobFileJSInterface.setDownloadGifSuccessListener(new openDownloadfile()); // Blob 下载完成后监听器
}
4、引入参考资料 1 的方法,定义下载完成监听器
mDownloadBlobFileJSInterface.setDownloadGifSuccessListener(new openDownloadfile()); //下载完成后监听器
参考资料 1 中的:
mDownloadBlobFileJSInterface.setDownloadGifSuccessListener(absolutePath -> Toast.makeText(MainActivity.this,"下载成功,在Download目录下",Toast.LENGTH_SHORT).show());
这句有问题,absolutePath 未定义,怀疑其是抄来的代码,没抄全,分析发现其为定义下载完成监听器,那么,就按照重写 DownloadListener 的定义,自己定义一个监听器,用于下载完成后的处理,如调用外部程序打开:
/* 定义下载完成事件监听器 */
private class openDownloadfile implements DownloadGifSuccessListener {
public void ondownloadGifSuccess(String gifFile){
String fileName=gifFile.substring(gifFile.lastIndexOf("/")+1);
String directory=gifFile.substring(0,gifFile.lastIndexOf("/")+1);
// System.out.println("11.下载成功,fileName:"+fileName);
// System.out.println("12.下载成功,directory:"+directory);
File File = new File(directory,fileName);
Intent intent = getFileIntent(File);
startActivity(intent); //调用万部程序打开
// System.out.println("13.下载成功,在Download目录下:"+gifFile);
}
}
public Intent getFileIntent(File file){
// Uri uri = Uri.parse("http://m.ql18.com.cn/hpf10/1.pdf");
Uri uri = Uri.fromFile(file);
String type = getMIMEType(file);
// Log.e("tag", "type="+type);
Intent intent = new Intent("android.intent.action.VIEW");
intent.addCategory("android.intent.category.DEFAULT");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri, type);
return intent;
}
private String getMIMEType(File f){
String type="";
String fName=f.getName();
// * 取得扩展名 * /
String end=fName.substring(fName.lastIndexOf(".")+1,fName.length());//.toLowerCase();
Locale loc = Locale.getDefault();//可以去掉这步
end=end.toLowerCase(loc);
// * 依扩展名的类型决定MimeType * /
if(end.equals("pdf")){
type = "application/pdf";//
}
else if(end.equals("m4a")||end.equals("mp3")||end.equals("mid")||
end.equals("xmf")||end.equals("ogg")||end.equals("wav")){
type = "audio/*";
}
else if(end.equals("3gp")||end.equals("mp4")){
type = "video/*";
}
else if(end.equals("jpg")||end.equals("gif")||end.equals("png")||
end.equals("jpeg")||end.equals("bmp")){
type = "image/*";
}
else if(end.equals("apk")){
// * android.permission.INSTALL_PACKAGES * /
type = "application/vnd.android.package-archive";
}
// else if(end.equals("pptx")||end.equals("ppt")){
// type = "application/vnd.ms-powerpoint";
// }else if(end.equals("docx")||end.equals("doc")){
// type = "application/vnd.ms-word";
// }else if(end.equals("xlsx")||end.equals("xls")){
// type = "application/vnd.ms-excel";
// }
else{
// // * 如果无法直接打开,就跳出软件列表给用户选择 * /
type="*/*"; //因注释多加了一个空格
}
return type;
}
其中 getFileIntent 和 getMIMEType 来自参考资料 2 。
参考资料 1 中的 DownloadBlobFileJSInterface 可直接使用,等下 获取 Blob 文件名时才需修改其转换 Base64 及保存过程。
5、获取 Blob 链接文件名及类型
参考资料 1 中,是无法取得 Blob 文件名及类型的,那么就要用个变通的方法:调用网页预设的 JS 中的 getDname 函数,获取文件名,文件名已事先由 .html 方法的回调函数中预设。
<script>
// 网页中已由 link.download ="A41.pdf"; 设置文件名
pdf.html(document.getElementById('pdfx'), { // 只有 addFileToVFS 方法添加的字体才能用于 .html 方法
callback: function (pdf) {
d = new Date();timex=d.getTime();
var link = document.getElementById('linklink');
link.target = '_blank';
link.href = pdf.output('bloburi');//77ms
link.download ="A41.pdf";
link.text='点击这里下载';
d = new Date();
timex=d.getTime()-timex;
console.log('总用时'+timex + "ms");
//link.click();
//pdf.save("A4.pdf");//自动下载
//getDname(link.href);
}
});
// 用于 安卓 WebView 调用获取 download 属性的文件名
function getDname(s){
var a=document.getElementsByTagName("a");
var h="";
for(let i=0;i<a.length;i++){
console.log(a[i].href);
if(a[i].href===s){
h=a[i].download;
i=a.length;
}
}
console.log(h);
return h;
}
</script>
6、重写 WebView 的下载监听器 DownloadListener
注:下载监听器已在 setWebStyle(); 中设置:
mWebView.setDownloadListener(new MyWebViewDownLoadListener());
在下载监听器中判断是否为 Blob 链接,不是则启用参考资料 2 中的直接下载进程。
是的话,则启用参考资料 1 中的 Blob 下载进程,并在下载前调用网页中的 getDname js 函数获取预设的 Blob 文件名:
// 改装blob下载
private class MyWebViewDownLoadListener implements DownloadListener {
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype,long contentLength){
if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
Toast.makeText(webxj.this, "需要SD卡。", Toast.LENGTH_SHORT).show();
return;
}
//Toast.makeText(webxj.this,"Url:"+url.lastIndexOf("data:app"), Toast.LENGTH_SHORT).show();
// System.out.println("Url:"+url);
// Log.e("tag", "Url="+url);
if(url.indexOf("blob:http")==0){
urlP=url;
// System.out.println("打开:"+urlP);
//mWebView.loadUrl("javascript:calljs();");
mWebView.evaluateJavascript("javascript:getDname('"+urlP+"')", new ValueCallback<String>(){
public void onReceiveValue(String value){
// System.out.println("JS返回::"+value);
pdffn=value.replace(""","");
// System.out.println("pdffn:"+pdffn);
File directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File file=new File(directory,pdffn);
if(file.exists()){
// Log.e("tag", "The file has already exists."); //文件已存在
System.out.println("文件已存在:"+directory+pdffn);
Toast.makeText(webxj.this, "文件已保存:"+directory+"/"+pdffn,Toast.LENGTH_LONG).show();
File File = new File(directory,pdffn);
Intent intent = getFileIntent(File);
startActivity(intent);
}else{
mWebView.loadUrl(DownloadBlobFileJSInterface.getBase64StringFromBlobUrl(urlP));
}
}
});
//mWebView.loadUrl(DownloadBlobFileJSInterface.getBase64StringFromBlobUrl(url));
}else{
DownloaderTask task=new DownloaderTask();
task.execute(url);
}
}
}
注意:网页 JS 返回的字符串前后有双引号 ” 包裹,需要将其去除。
其中, DownloaderTask 为参考资料 2 的直接下载方法,注意,A 标签中必须加上 download ,否则报错:
<a href="./upload/dll.rar" download>dll.rar</a>
然后,在 onCreate 打开动态生成 pdf 文件的页面即可:
double rndX=Math.random();
urlx="?v="+String.valueOf(rndX);
urlm=getString(R.string.mainUrl);//
mWebView=(WebView)findViewById(R.id.webshow);
setWebStyle();
mWebView.loadUrl(urlm + urlx);
7、其中的几个变量及其他说明
// 定义于 extends Activity 与 onCreate 之间
WebView mWebView; // WebView 实例变量
private String urlx=new String(""); //存放调用首页的参数等,如 double rndX=Math.random(); urlx="?v="+String.valueOf(rndX);
private String urlw=new String("file:///android_asset/index.html"); //存放等待页面url
private String urlm;//存放首页菜单的页面
DownloadBlobFileJSInterface mDownloadBlobFileJSInterface = new DownloadBlobFileJSInterface(this); // Blob 下载 js 接口定义
private String urlP=new String(""); // 下载链接全局变量
private static String pdffn=new String(""); // Blob 文件名全局变量
其中 pdffn 用于存放 Blob 链接预设文件名,因为我懒得其动 DownloadBlobFileJSInterface 的输入变量,就用这个传递给其中的 convertToGifAndProcess 方法,用做保存文件名:
/**
* 转换成file
* @param base64
*/
private void convertToGifAndProcess(String base64) {
String fileName =pdffn;// UUID.randomUUID().toString() + ".pdf";
// System.out.println("3.convertToGifAndProcess:fileName:" + fileName);
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File gifFile = new File(directory, fileName);
// System.out.println("4.convertToGifAndProcess:gifFile:" + gifFile);
// Log.e("tag", "type=convertToGifAndProcess" + gifFile);
saveFileToPath(base64, gifFile);
// System.out.println("7.convertToGifAndProcess:mDownloadGifSuccessListener:" + mDownloadGifSuccessListener);
// System.out.println("8.convertToGifAndProcess gifFile:" + gifFile);
if (mDownloadGifSuccessListener != null) {
// System.out.println("10.mDownloadGifSuccessListener 不为空到这里:" + mDownloadGifSuccessListener);
mDownloadGifSuccessListener.ondownloadGifSuccess(gifFile.getAbsolutePath());
}
}
/**
* 保存文件
* @param base64
* @param gifFilePath
*/
private void saveFileToPath(String base64, File gifFilePath) {
// System.out.println("5.saveFileToPath gifFilePath.getAbsolutePath():" + gifFilePath.getAbsolutePath());
try {
byte[] fileBytes = Base64.decode(base64.replaceFirst(
"data:application/pdf;base64,", ""), 0);
FileOutputStream os = new FileOutputStream(gifFilePath, false);
os.write(fileBytes);
os.flush();
os.close();
Toast.makeText(mContext, "文件已保存:"+gifFilePath,Toast.LENGTH_LONG).show();
// Log.e("tag", "type=saveFileToPath" + gifFilePath);
// System.out.println("6.saveFileToPath FileOutputStream gifFilePath:" + gifFilePath);
} catch (Exception e) {
e.printStackTrace();
}
}
’——————————-
此记!
原文地址:https://blog.csdn.net/jessezappy/article/details/126264165
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_40008.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!