Android本地浏览PDF(Android PDF.js 简要学习手册)
环境
Min SDK: 21
依赖:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"
implementation "androidx.webkit:webkit:1.12.0"
权限:
<uses-permission android:name="android.permission.INTERNET" />
下载pdf.js :https://github.com/mozilla/pdf.js/tree/v2.10.377/web
引入步骤
app/src/main/assets/pdfjs/
├── web/
│ ├── viewer.html
│ ├── viewer.js
│ └── ...
├── build/
│ ├── pdf.js
│ ├── pdf.worker.js
│ └── ...
实现
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.content.ContextCompat
import androidx.webkit.WebViewAssetLoader
import com.gyf.immersionbar.ImmersionBar
import com.sunward.markettools.R
import com.sunward.markettools.base.BaseActivity
import com.sunward.markettools.databinding.ActivityWebviewBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.net.URLEncoder
import java.nio.channels.Channels/***@dateTime:2025/4/29*@作者:MaoYoung*/
class WebViewPDFActivity : BaseActivity<MarketViewModel, ActivityWebviewBinding>(R.layout.activity_webview
) {companion object {fun newInstance(activity: Context?, url: String, fileName: String) {activity?.startActivity(Intent(activity, WebViewPDFActivity::class.java).apply {putExtra(activity.getString(R.string.params_web_url), url)putExtra(activity.getString(R.string.params_file_name), fileName)})}}private val coroutineScope = MainScope()private val domain = "appassets.androidplatform.net"override fun initView(savedInstanceState: Bundle?) {val pdfUrl =intent.getStringExtra(getString(R.string.params_web_url)) ?: ""val fileName = intent.getStringExtra(getString(R.string.params_file_name)) ?: ""window.setFlags(WindowManager.LayoutParams.FLAG_SECURE,WindowManager.LayoutParams.FLAG_SECURE)mBinding.apply {tvTitle.text = fileNametoolbar.setNavigationOnClickListener { finishActivity() }}val downloadDir = saveDownloadFilePath()val assetLoader = WebViewAssetLoader.Builder().setDomain(domain).addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(this)).addPathHandler("/Download/",WebViewAssetLoader.InternalStoragePathHandler(this, downloadDir)).build()mBinding.apply {webview.apply {settings.apply {javaScriptEnabled = truedomStorageEnabled = trueallowFileAccess = false // 不再需要 file 访问}//设置WebViewClient与AssetLoaderwebViewClient = object : WebViewClient() {override fun shouldInterceptRequest(view: WebView?,request: WebResourceRequest?): android.webkit.WebResourceResponse? {return assetLoader.shouldInterceptRequest(request?.url!!)}override fun shouldOverrideUrlLoading(view: WebView?,request: WebResourceRequest?): Boolean {return super.shouldOverrideUrlLoading(view, request)}override fun onPageFinished(view: WebView?, url: String?) {view?.loadUrl("javascript:(function() {" +"var rightToolbar = document.getElementById('toolbarViewerRight');" +"if (rightToolbar) { rightToolbar.style.display = 'none'; }" +"})()")}override fun onReceivedError(view: WebView?,errorCode: Int,description: String?,failingUrl: String?) {Log.e("WebViewActivity", "Error: $description, URL: $failingUrl")}}webChromeClient = WebChromeClient()downloadAndDisplayPdf(pdfUrl, fileName)}}}private fun saveDownloadFilePath(): File {val downloadDir = File(this.filesDir, "Download")if (!downloadDir.exists()) {downloadDir.mkdirs()}return downloadDir}private fun downloadAndDisplayPdf(pdfUrl: String, fileName: String) {coroutineScope.launch {try {val finalFileName = if (fileName.endsWith(".pdf")) fileName else "$fileName.pdf"val downloadedFileName =withContext(Dispatchers.IO) {val downloadDir = saveDownloadFilePath()val file = File(downloadDir, finalFileName)if (file.exists()) file.delete()val url = URL(pdfUrl)val connection = url.openConnection()connection.connect()val inputStream = connection.getInputStream()FileOutputStream(file).use { output ->Channels.newChannel(inputStream).use { inputChannel ->output.channel.use { outputChannel ->outputChannel.transferFrom(inputChannel, 0, Long.MAX_VALUE)}}}finalFileName}val encodedPath = URLEncoder.encode("$downloadedFileName", "UTF-8")val viewerUrl ="https://$domain/assets/pdfjs/web/viewer.html?file=%2FDownload%2F$encodedPath"mBinding.webview.loadUrl(viewerUrl)} catch (e: Exception) {e.printStackTrace()val encodedUrl = try {URLEncoder.encode(pdfUrl, "UTF-8")} catch (e: Exception) {pdfUrl}val fallbackUrl = "file:///android_asset/pdfjs/web/viewer.html?file=$encodedUrl"mBinding.webview.loadUrl(fallbackUrl)}}}override fun onResume() {super.onResume()mBinding.webview.onResume()}override fun onPause() {super.onPause()mBinding.webview.onPause()}override fun onDestroy() {mBinding.webview.destroy()coroutineScope.cancel()super.onDestroy()}override fun initImmersionBar() {ImmersionBar.with(this).statusBarColorTransformEnable(false).keyboardEnable(false).statusBarDarkFont(true).navigationBarDarkIcon(true).fitsSystemWindowsInt(true, ContextCompat.getColor(this, R.color.color_fa)).navigationBarColor(R.color.color_fa).init()}
}
WebViewActivity.newInstance(context, "这里传递下载PDF的地址")
工作原理
-
初始化
- 从intent中获取PDF 的URL
- 配置域名: .setDomain(domain)
-
下载
使用协成下载到/Download/文件夹中 -
显示
将文件加载到本地PDF路径(file://…/temp.pdf)的"https://$domain/assets/pdfjs/web/viewer.html