You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android 19:无需系统打印UI将WebView转矢量PDF的技术问询

嘿,我来帮你梳理这几个关于WebView直接生成矢量PDF的问题——我之前在做类似需求的时候也踩过这些坑,下面给你逐个解答:

1. 是否真的可以实现无需系统打印UI,直接将WebView内容转为PDF?

完全可以!这是完全可行的原生方案,核心就是利用WebView自带的PrintDocumentAdapter生成矢量PDF,不需要依赖系统打印UI。你之前的思路是对的,只是因为没有正确处理回调导致空指针异常,补全回调逻辑就能实现。

2. 为何WebView.draw(PdfDocument.Page.getCanvas())会生成文本不可选中的栅格PDF?

原因很简单:WebView.draw()capturePicture()都是栅格化渲染——它们会把WebView当前显示的内容转换成一张位图(Bitmap),然后把这张位图绘制到PDF的Canvas上。本质上你生成的PDF只是包裹了一张图片,所以文本无法选中,缩放后会出现模糊、锯齿等问题。

而系统PrintManager生成的矢量PDF,是调用WebView的打印适配层直接将网页的DOM结构、CSS样式转换成PDF的矢量元素(比如文本路径、矢量图形),所以能保留文本的可选中性和缩放清晰度。

3. 是否可以像第二个示例那样,直接使用WebView创建的PrintDocumentAdapter?

当然可以!这其实是生成矢量PDF的正确且推荐的方式,你之前的代码问题在于传入了null的回调对象,导致空指针异常。针对Android 19(KitKat)的限制(LayoutResultCallbackWriteResultCallback是包级保护类),可以通过以下方式解决:

解决步骤(针对API 19)

因为API 19中这两个回调类是android.print包的包级成员,我们可以在该包下创建自定义的回调实现类:

1. 创建布局回调类(放在android.print包下)

package android.print;

public class CustomLayoutCallback extends PrintDocumentAdapter.LayoutResultCallback {
    private final OnLayoutFinishedListener listener;

    public interface OnLayoutFinishedListener {
        void onLayoutDone();
    }

    public CustomLayoutCallback(OnLayoutFinishedListener listener) {
        this.listener = listener;
    }

    @Override
    public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
        super.onLayoutFinished(info, changed);
        if (listener != null) {
            listener.onLayoutDone();
        }
    }

    @Override
    public void onLayoutFailed(CharSequence error) {
        // 处理布局失败逻辑,比如打印错误日志
    }
}

2. 创建写入回调类(放在android.print包下)

package android.print;

import android.os.ParcelFileDescriptor;
import android.print.PrintDocumentAdapter;

public class CustomWriteCallback extends PrintDocumentAdapter.WriteResultCallback {
    private final OnWriteFinishedListener listener;
    private final ParcelFileDescriptor fileDescriptor;
    private final PrintDocumentAdapter adapter;

    public interface OnWriteFinishedListener {
        void onWriteDone();
    }

    public CustomWriteCallback(OnWriteFinishedListener listener, ParcelFileDescriptor fd, PrintDocumentAdapter adapter) {
        this.listener = listener;
        this.fileDescriptor = fd;
        this.adapter = adapter;
    }

    @Override
    public void onWriteFinished(PageRange[] pages) {
        super.onWriteFinished(pages);
        try {
            // 关闭文件描述符
            if (fileDescriptor != null) {
                fileDescriptor.close();
            }
            // 结束打印适配流程
            adapter.onFinish();
            if (listener != null) {
                listener.onWriteDone();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onWriteFailed(CharSequence error) {
        // 处理写入失败逻辑
        try {
            if (fileDescriptor != null) {
                fileDescriptor.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. 在你的Activity中正确调用PrintDocumentAdapter

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);

        // 配置打印属性
        final PrintAttributes attrs = new PrintAttributes.Builder()
                .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
                .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
                .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
                .setResolution(new PrintAttributes.Resolution("custom", "300dpi", 300, 300))
                .build();

        // 创建WebView的打印适配器
        final PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter();
        // 定义输出文件
        final File outputFile = new File(Environment.getExternalStorageDirectory(), "vector_webview.pdf");

        try {
            // 打开文件描述符
            final ParcelFileDescriptor fd = ParcelFileDescriptor.open(
                    outputFile,
                    ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_WRITE
            );

            // 初始化布局回调,布局完成后触发写入
            CustomLayoutCallback layoutCallback = new CustomLayoutCallback(() -> {
                // 布局完成,开始写入PDF
                printAdapter.onWrite(
                        new PageRange[]{PageRange.ALL_PAGES},
                        fd,
                        new CancellationSignal(),
                        new CustomWriteCallback(() -> {
                            // PDF生成完成,这里可以添加通知或后续逻辑
                            Log.d("PDF_GENERATOR", "矢量PDF已成功保存到:" + outputFile.getAbsolutePath());
                        }, fd, printAdapter)
                );
            });

            // 启动打印适配流程
            printAdapter.onStart();
            printAdapter.onLayout(attrs, attrs, new CancellationSignal(), layoutCallback, new Bundle());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});

针对API 21+的简化方案

如果你的应用可以兼容到API 21及以上,就不用把回调类放在android.print包下了——因为这两个回调类在API 21已经改为public访问权限,你可以直接在自己的包中实现PrintDocumentAdapter.LayoutResultCallbackPrintDocumentAdapter.WriteResultCallback,代码会更简洁。


内容的提问来源于stack exchange,提问作者lorenzo-s

火山引擎 最新活动