18034727885
2026-09-02 9851743770909686532d711aa7f318a990a04da0
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
package com.example.mylibrary
 
import android.app.Activity
import android.app.Application
import android.content.ComponentCallbacks
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.WindowInsets
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.webkit.CookieManager
import android.webkit.JavascriptInterface
import android.webkit.WebChromeClient
import android.webkit.WebStorage
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.FrameLayout
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
 
//网页支付加载页
class WebPayActivity : AppCompatActivity() {
    companion object {
        //获取跳转此页面intent
        fun getIntent(context: Context, url: String, title: String? = null): Intent {
            return Intent(context, WebPayActivity::class.java).apply {
                putExtra("url", url)
                putExtra("title", title)
            }
        }
    }
 
    //屏幕密度
    private var density = 0f
 
    //缩放屏幕密度
    private var scaledDensity = 0f
 
    private val ua by lazy {
        intent.getStringExtra("ua")
    }
    private val url by lazy {
        intent.getStringExtra("url")
    }
    private val title by lazy {
        intent.getStringExtra("title")
    }
    private val ivClose by lazy {
        findViewById<View>(R.id.iv_close)
    }
 
    private val tvTitle by lazy {
        findViewById<TextView>(R.id.tv_title)
    }
 
    private val webView by lazy {
        findViewById<WebView>(R.id.web)
    }
 
    // 全屏相关状态
    private var mCustomView: View? = null
    private var mCustomViewCallback: WebChromeClient.CustomViewCallback? = null
    private var mOriginalOrientation: Int = 0
    private var mOriginalSystemUiVisibility: Int = 0
 
    override fun onCreate(savedInstanceState: Bundle?) {
        setCustomDensity(this, application)
        super.onCreate(savedInstanceState)
        val transparentStyle = SystemBarStyle.light(
            scrim = Color.TRANSPARENT,      // 浅色主题下 scrim
            darkScrim = Color.TRANSPARENT   // 深色主题下 scrim
        )
 
        enableEdgeToEdge(statusBarStyle = transparentStyle, navigationBarStyle = transparentStyle)
        setContentView(R.layout.activity_web)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        ivClose.setOnClickListener { finish() }
        clearWebViewCache()
        initWebView()
        // 在 Activity 中使用 OnBackPressedDispatcher
        onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (mCustomView != null) {
                    // 通知网页退出全屏,网页会再次调用 onHideCustomView
                    mCustomViewCallback?.onCustomViewHidden()
                } else {
                    if (webView.canGoBack()) {
                        webView.goBack()
                    } else {
                        finish()
                    }
                }
            }
        })
    }
 
    //设置自定义的屏幕密度,解决屏幕适配问题
    private fun setCustomDensity(activity: Activity, application: Application) {
        val displayMetrics = application.resources.displayMetrics
        if (density == 0f) {
            density = displayMetrics.density
            scaledDensity = displayMetrics.scaledDensity
            application.registerComponentCallbacks(object : ComponentCallbacks {
                override fun onConfigurationChanged(newConfig: Configuration) {
                    if (newConfig.fontScale > 0) {
                        scaledDensity = application.resources.displayMetrics.scaledDensity
                    }
                }
 
                @Deprecated("Deprecated in Java")
                override fun onLowMemory() {
 
                }
            })
        }
        val targetDensity: Float = displayMetrics.widthPixels / 375f
        val targetScaledDensity = targetDensity * (scaledDensity / density)
        val targetDensityDpi = (160 * targetDensity).toInt()
        displayMetrics.density = targetDensity
        displayMetrics.scaledDensity = targetScaledDensity
        displayMetrics.densityDpi = targetDensityDpi
        val activityDisplayMetrics = activity.resources.displayMetrics
        activityDisplayMetrics.density = targetDensity
        activityDisplayMetrics.scaledDensity = targetScaledDensity
        activityDisplayMetrics.densityDpi = targetDensityDpi
    }
 
    private fun clearWebViewCache() {
        // 1. 清除资源缓存(HTML、JS、CSS、图片等)
        webView.clearCache(true)
 
        // 2. 清除 DOM 存储(localStorage / sessionStorage)
        //    注意:必须在主线程调用,且 WebView 的 settings 中已开启 domStorageEnabled
        WebStorage.getInstance().deleteAllData()
 
        // 3. 清除 IndexedDB / Web SQL Database(如果 H5 使用了)
        deleteDatabase("webview.db")
        deleteDatabase("webviewCache.db")
 
        // 4. 清除历史记录(非必需,但可以避免回退到旧页面)
        webView.clearHistory()
 
        // 5. 可选:清除 Cookie
        CookieManager.getInstance().removeAllCookies(null)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            CookieManager.getInstance().flush()
        }
    }
 
    private fun initWebView() {
 
        val cookieManager = CookieManager.getInstance()
 
        // 1. 允许 WebView 接受 Cookie(默认其实是 true,但显式声明更安全)
        cookieManager.setAcceptCookie(true)
 
        // 2. 允许第三方 Cookie —— 这是关键!
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            cookieManager.setAcceptThirdPartyCookies(webView, true)
        }
 
        // 3. WebSettings 关键开关
        webView.settings.apply {
            javaScriptEnabled = true //允许js调用
            domStorageEnabled = true //部分 H5 框架把状态存在 DOM Storage
            databaseEnabled = true
            allowContentAccess = true //是否允许在WebView中访问内容URL(Content Url),默认允许
 
            setSupportZoom(true) //支持缩放,默认为true。是下面那个的前提。
            builtInZoomControls = true //设置内置的缩放控件。若为false,则该WebView不可缩放
            displayZoomControls = false //隐藏原生的缩放控件
            javaScriptCanOpenWindowsAutomatically = true //支持通过JS打开新窗口
            loadsImagesAutomatically = true //支持自动加载图片
            defaultTextEncodingName = "utf-8" //设置编码格式
            userAgentString =
                "$userAgentString webchatPay/wx074c1f0cf5a0ecf4 InAppPay Vzan/miniAppPayment vzanApp navigator.userAgent zhiboApp"
        }
 
        webView.addJavascriptInterface(this, "android")
        webView.webChromeClient = object : WebChromeClient() {
            override fun onReceivedTitle(view: WebView?, title: String?) {
                super.onReceivedTitle(view, title)
                tvTitle.text = title?.ifEmpty { this@WebPayActivity.title } ?: ""
            }
 
            /**
             * 网页请求进入全屏时回调
             * @param view 代表全屏内容的视图(通常是视频 Surface 或播放器容器)
             * @param callback 退出全屏时的回调句柄,必须保存
             */
            override fun onShowCustomView(view: View, callback: WebChromeClient.CustomViewCallback) {
                // 如果已有全屏视图,先回调隐藏掉新的请求
                if (mCustomView != null) {
                    callback.onCustomViewHidden()
                    return
                }
 
                // 1. 保存状态和回调
                mCustomView = view
                mCustomViewCallback = callback
                mOriginalOrientation = requestedOrientation
                mOriginalSystemUiVisibility = window.decorView.systemUiVisibility
 
                // 2. 将全屏视图添加到 Window 的 DecorView 顶层
                val decorView = window.decorView as FrameLayout
                decorView.addView(
                    view,
                    FrameLayout.LayoutParams(
                        FrameLayout.LayoutParams.MATCH_PARENT,
                        FrameLayout.LayoutParams.MATCH_PARENT
                    )
                )
 
                // 3. 隐藏状态栏 + 导航栏,实现真正的沉浸式全屏
                hideSystemUI()
 
                // 4. 强制横屏(直播/视频全屏的最佳体验)
                requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
            }
 
            /**
             * 网页请求退出全屏时回调
             */
            override fun onHideCustomView() {
                val customView = mCustomView ?: return
 
                // 1. 从 DecorView 移除全屏视图
                val decorView = window.decorView as FrameLayout
                decorView.removeView(customView)
                mCustomView = null
                mCustomViewCallback?.onCustomViewHidden()
                mCustomViewCallback = null
 
                // 2. 彻底清理全屏 Window flag(关键!)
                //    很多人只恢复了 systemUiVisibility,但忘了清除 Window flag
                window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
                window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
                window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
 
                // 3. 恢复非全屏的 UI 可见性
                window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
 
                // 4. 恢复屏幕方向
                requestedOrientation = mOriginalOrientation
 
                // 5. 关键:重建 IME 关联
                //    先让 WebView 完全失去焦点
                webView.clearFocus()
 
                // 6. 下一帧再重新获得焦点,触发 IMM 重建 served view
                webView.post {
                    webView.requestFocus()
                }
            }
        }
        webView.webViewClient = object : WebViewClient() {
            override fun onPageFinished(view: WebView?, url: String?) {
                super.onPageFinished(view, url)
                val cookieManager = CookieManager.getInstance()
                // 获取该域名下的所有 Cookie
                val cookies = cookieManager.getCookie(url)
                Log.d("xxx", "URL: $url")
                Log.d("xxx", "Cookies: $cookies")
            }
        }
        url?.also { webView.loadUrl(it) }
    }
 
    /**
     * 跳转小程序支付
     */
    @JavascriptInterface
    fun jumpToMiniProgramPay(json: String) {
        runOnUiThread {
            Log.d("xxx", json)
            BusinessModule.webPayCallback?.invokeAndKeepAlive(json)
        }
    }
 
    /**
     * 隐藏状态栏和导航栏,进入沉浸式全屏
     */
    private fun hideSystemUI() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            window.decorView.systemUiVisibility = (
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            or View.SYSTEM_UI_FLAG_FULLSCREEN
                            or View.SYSTEM_UI_FLAG_IMMERSIVE  // 不再使用 STICKY
                    )
        } else {
            window.setFlags(
                WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN
            )
        }
    }
 
    override fun finish() {
        BusinessModule.webPayCallback = null
        super.finish()
    }
 
    override fun onDestroy() {
        // 清理:防止内存泄漏
        mCustomView?.let {
            (window.decorView as FrameLayout).removeView(it)
        }
        mCustomView = null
        mCustomViewCallback = null
        super.onDestroy()
    }
}