You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android使用PrintedPdfDocument和WebView生成PDF出现空白页问题

WebView生成PDF空白页的解决方法

问题根源

  • 直接创建的WebView未加入视图层级,无法完成完整渲染流程,onPageFinished触发时内容可能还没实际绘制出来。
  • 用webView.draw()直接绘制Canvas的方式不稳定,Android有专门的WebView打印API,应该优先用这个。
  • 手动指定的页面尺寸和PrintAttributes不匹配,导致内容无法正常显示。

修复后的代码实现

fun createPdfFromHtml(
    context: Context,
    htmlContent: String,
    onSuccess: (File) -> Unit,
    onFail: (Throwable) -> Unit
) {
    // 创建WebView并设置布局参数
    val webView = WebView(context).apply {
        settings.javaScriptEnabled = true
        layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
    }

    // 创建临时布局承载WebView,确保能正常渲染
    val tempLayout = FrameLayout(context).apply {
        addView(webView)
        // 根据上下文类型添加到视图层级
        if (context is Activity) {
            context.window.decorView.rootView.addView(this)
        } else {
            val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
            val params = WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 
                else 
                    WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                PixelFormat.TRANSLUCENT
            )
            windowManager.addView(this, params)
        }
    }

    webView.webViewClient = object : WebViewClient() {
        override fun onPageFinished(view: WebView?, url: String?) {
            super.onPageFinished(view, url)

            val printAttributes = PrintAttributes.Builder()
                .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
                .setResolution(PrintAttributes.Resolution("RES_A4", "A4 Resolution", 300, 300))
                .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
                .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
                .build()

            // 使用WebView官方打印适配器生成PDF
            val printAdapter = webView.createPrintDocumentAdapter("HTML_PDF")
            val pdfFile = File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
                "output.pdf"
            )

            printAdapter.onLayout(null, printAttributes, null, object : PrintDocumentAdapter.LayoutResultCallback() {
                override fun onLayoutFinished(info: PrintDocumentInfo?, changed: Boolean) {
                    printAdapter.onWrite(
                        arrayOf(PageRange.ALL_PAGES),
                        ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_WRITE_ONLY),
                        null,
                        object : PrintDocumentAdapter.WriteResultCallback() {
                            override fun onWriteFinished(pages: Array<out PageRange>?) {
                                super.onWriteFinished(pages)
                                // 清理临时视图
                                removeTempView(context, tempLayout)
                                onSuccess(pdfFile)
                            }

                            override fun onWriteFailed(error: CharSequence?) {
                                super.onWriteFailed(error)
                                removeTempView(context, tempLayout)
                                onFail(Exception(error?.toString() ?: "PDF生成失败"))
                            }
                        }
                    )
                }

                override fun onLayoutFailed(error: CharSequence?) {
                    super.onLayoutFailed(error)
                    removeTempView(context, tempLayout)
                    onFail(Exception(error?.toString() ?: "布局失败"))
                }
            }, null)
        }
    }

    webView.loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null)
}

// 提取清理临时视图的方法,简化代码
private fun removeTempView(context: Context, tempLayout: FrameLayout) {
    if (context is Activity) {
        context.window.decorView.rootView.removeView(tempLayout)
    } else {
        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        windowManager.removeView(tempLayout)
    }
}

关键修复点说明

  • 把WebView加入视图层级:WebView必须附着在Window上才能完成完整渲染,通过临时FrameLayout将WebView加入到Activity或全局WindowManager中,确保内容能正确绘制。
  • 用官方PrintDocumentAdapter:WebView自带的createPrintDocumentAdapter是专门用来打印和生成PDF的API,比直接画Canvas稳定得多,能正确处理分页、样式渲染等问题。
  • 及时清理临时视图:生成PDF完成后移除临时添加的视图,避免内存泄漏。
  • 权限提醒:Android 10及以下需要添加存储权限,Android 11及以上需要用MediaStore或MANAGE_EXTERNAL_STORAGE权限访问下载目录;非Activity上下文添加视图时,需要SYSTEM_ALERT_WINDOW权限。

内容的提问来源于stack exchange,提问作者nasibeyyubov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 04:13:09