Jetpack Compose-使用WebView加载展示PDF

前言

Android Jetpack Compose没有相关解析PDF的库,所以我们使用WebView进行加载。

使用pdf.js来解析PDF。

访问跨域

pdf.js 加载 PDF的时候就算,PDF的地址是允许跨域的,但是只要域不一样,依旧会报错,直接在viewer.js,找到下面的代码,把抛出异常的地方注释掉就行。

1
2
3
if (fileOrigin !== viewerOrigin) {
// throw new Error('file origin does not match viewer\'s');
}

添加依赖

这个主要用于使用模板生成HTML

1
2
3
dependencies {
implementation 'com.x5dev:chunk-templates:3.6.2'
}

添加模板

PDF解析库

assets 中添加相关库文件

image-20250911162520720

模板

模板文件要放在src/main/assets/themes文件夹下

注意文件必须以.chtml作为后缀

assets/themes 中添加 zpdf.chtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF预览</title>

<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
}

#pdfIframe {
width: 100%;
height: 100vh;
}

#hideDiv{
display: none;
}
</style>
</head>
<body>
<div id="hideDiv">{$pdf_url}</div>
<iframe frameborder="0" id="pdfIframe"></iframe>

<script>
let hideDiv = document.getElementById("hideDiv");
let pdfIframe = document.getElementById("pdfIframe");

let pdfUrl = hideDiv.innerHTML;
pdfIframe.src = "file:///android_asset/pdfjs/viewer.html?file=" + encodeURIComponent(pdfUrl);
</script>
</body>
</html>

自定义组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.webkit.WebSettings
import android.webkit.WebView
import com.x5.template.Chunk
import com.x5.template.Theme
import com.x5.template.providers.AndroidTemplates

@SuppressLint("SetJavaScriptEnabled")
public class ZPdfView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
WebView(context, attrs, defStyleAttr) {
val TAG = "ZPdfView"
private val TAG_PDF_URL: String = "pdf_url"
private val TAG_TP_NAME: String = "zpdf"

private var mPdfUrl: String = ""

init {
settings.cacheMode = WebSettings.LOAD_NO_CACHE
settings.displayZoomControls = false
settings.builtInZoomControls = false
settings.setSupportZoom(false)
settings.useWideViewPort = true // 启用宽视口支持
settings.loadWithOverviewMode = true // 配合使用,让网页自适应屏幕

settings.javaScriptEnabled = true // 如果图片加载依赖JS
settings.allowFileAccess = true
settings.domStorageEnabled = true
settings.loadsImagesAutomatically = true // 确保自动加载图片
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW // 如果有混合内容

setBackgroundColor(Color.TRANSPARENT)
}

fun loadPdf(url: String) {
mPdfUrl = url
loadHTML()
}


private fun getChunk(): Chunk {
return Theme(AndroidTemplates(context)).makeChunk(TAG_TP_NAME)
}

/**
* 加载页面
*/
private fun loadHTML() {
val mChunk: Chunk = getChunk()
mChunk.set(TAG_PDF_URL, mPdfUrl)
this.loadDataWithBaseURL(null, mChunk.toString(), "text/html", "utf-8", "about:blank")
}
}

注意

下面的配置确保图片的正常加载

1
2
3
4
5
settings.javaScriptEnabled = true // 如果图片加载依赖JS
settings.allowFileAccess = true
settings.domStorageEnabled = true
settings.loadsImagesAutomatically = true // 确保自动加载图片
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW // 如果有混合内容

Compose封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import android.content.Context
import android.view.ViewGroup
import android.webkit.WebViewClient
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import cn.psvmc.zpdfview.ZPdfView

private fun getPdfView(context: Context): ZPdfView {
val pdfView = ZPdfView(context).apply {
webViewClient = WebViewClient()
}
pdfView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
return pdfView
}

@Composable
fun ZPdfViewComp(
pdfUrl: String,
modifier: Modifier = Modifier
) {
var pdfView: ZPdfView? = null
AndroidView(
modifier = modifier,
factory = { context ->
pdfView = getPdfView(context)
pdfView
},
update = { texView ->
texView.loadPdf(pdfUrl)
}
)

DisposableEffect(Unit) {
// 组件销毁时释放WebView资源
onDispose {
pdfView?.stopLoading()
// 清除客户端引用
pdfView?.webViewClient = WebViewClient()
pdfView?.destroy()
}
}
}

注意

销毁时释放Webview资源,避免内存泄漏导致的加载异常。

使用

1
2
3
4
ZPdfViewComp(
pdfUrl = CommonData.fileShowUrl+ file.pdfUrl,
modifier = Modifier.fillMaxSize()
)