├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── github
│ │ └── leavesczy
│ │ └── robustwebview
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── ic_launcher.webp
│ │ └── javascript.html
│ ├── java
│ │ └── github
│ │ │ └── leavesczy
│ │ │ └── robustwebview
│ │ │ ├── JsInterface.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── MainApplication.kt
│ │ │ ├── base
│ │ │ ├── RobustWebView.kt
│ │ │ ├── WebViewCacheHolder.kt
│ │ │ ├── WebViewInitTask.kt
│ │ │ └── WebViewInterceptRequestProxy.kt
│ │ │ └── utils
│ │ │ ├── ContextHolder.kt
│ │ │ └── Utils.kt
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ └── network_security_config.xml
│ └── test
│ └── java
│ └── github
│ └── leavesczy
│ └── robustwebview
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .DS_Store
5 | /build
6 | /captures
7 | .externalNativeBuild
8 | .cxx
9 | /.idea/
10 | /app/release/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RobustWebView
2 |
3 | 文章讲解:
4 |
5 | - [Android WebView H5 秒开方案总结](https://juejin.cn/post/7016883220025180191)
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("org.jetbrains.kotlin.android")
4 | }
5 |
6 | android {
7 | namespace = "github.leavesczy.robustwebview"
8 | compileSdk = 35
9 | defaultConfig {
10 | applicationId = "github.leavesczy.robustwebview"
11 | minSdk = 21
12 | targetSdk = 35
13 | versionCode = 1
14 | versionName = "1.0.0"
15 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
16 | }
17 | buildTypes {
18 | release {
19 | isMinifyEnabled = false
20 | proguardFiles(
21 | getDefaultProguardFile("proguard-android-optimize.txt"),
22 | "proguard-rules.pro"
23 | )
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_11
28 | targetCompatibility = JavaVersion.VERSION_11
29 | }
30 | kotlinOptions {
31 | jvmTarget = "11"
32 | }
33 | }
34 |
35 | dependencies {
36 | testImplementation("junit:junit:4.13.2")
37 | androidTestImplementation("androidx.test.ext:junit:1.2.1")
38 | androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
39 | implementation("androidx.appcompat:appcompat:1.7.0")
40 | implementation("com.google.android.material:material:1.12.0")
41 | implementation("com.squareup.okhttp3:okhttp:4.12.0")
42 | val chuckerVersion = "4.0.0"
43 | debugImplementation("com.github.chuckerteam.chucker:library:${chuckerVersion}")
44 | releaseImplementation("com.github.chuckerteam.chucker:library-no-op:${chuckerVersion}")
45 | implementation("com.tencent.tbs:tbssdk:44286")
46 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -dontwarn dalvik.**
24 | -dontwarn com.tencent.smtt.**
25 | -keep class com.tencent.smtt.** {
26 | *;
27 | }
28 | -keep class com.tencent.tbs.** {
29 | *;
30 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/github/leavesczy/robustwebview/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("github.leavesczy.robustwebview", appContext.packageName)
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/assets/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leavesCZY/RobustWebView/a8cee699ad99f59f0b0ee14ee082798b4f96358d/app/src/main/assets/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/assets/javascript.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Android WebView H5 秒开方案总结
7 |
12 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/java/github/leavesczy/robustwebview/JsInterface.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview
2 |
3 | import android.webkit.JavascriptInterface
4 | import github.leavesczy.robustwebview.utils.log
5 | import github.leavesczy.robustwebview.utils.showToast
6 |
7 | /**
8 | * @Author: leavesCZY
9 | * @Date: 2021/9/21 15:08
10 | * @Desc:
11 | * @Github:https://github.com/leavesCZY
12 | */
13 | class JsInterface {
14 |
15 | @JavascriptInterface
16 | fun showToastByAndroid(log: String) {
17 | log("showToastByAndroid:$log")
18 | showToast(log)
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/leavesczy/robustwebview/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview
2 |
3 | import android.Manifest
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.LinearLayout
9 | import android.widget.TextView
10 | import androidx.activity.OnBackPressedCallback
11 | import androidx.activity.result.contract.ActivityResultContracts
12 | import androidx.appcompat.app.AppCompatActivity
13 | import androidx.core.app.NotificationManagerCompat
14 | import github.leavesczy.robustwebview.base.RobustWebView
15 | import github.leavesczy.robustwebview.base.WebViewCacheHolder
16 | import github.leavesczy.robustwebview.base.WebViewListener
17 | import github.leavesczy.robustwebview.utils.showToast
18 |
19 | /**
20 | * @Author: leavesCZY
21 | * @Date: 2021/10/1 23:08
22 | * @Desc:
23 | * @Github:https://github.com/leavesCZY
24 | */
25 | class MainActivity : AppCompatActivity() {
26 |
27 | private val webViewContainer by lazy {
28 | findViewById(R.id.webViewContainer)
29 | }
30 |
31 | private val tvTitle by lazy {
32 | findViewById(R.id.tvTitle)
33 | }
34 |
35 | private val tvProgress by lazy {
36 | findViewById(R.id.tvProgress)
37 | }
38 |
39 | private val requestNotificationPermissionLauncher = registerForActivityResult(
40 | ActivityResultContracts.RequestPermission()
41 | ) {
42 | checkNotificationPermission()
43 | }
44 |
45 | private val url1 = "https://juejin.cn/post/7016883220025180191"
46 |
47 | private val url2 = "https://www.bilibili.com/"
48 |
49 | private val url3 =
50 | "https://p26-passport.byteacctimg.com/img/user-avatar/6019f80db5be42d33c31c98adaf3fa8c~300x300.image"
51 |
52 | private lateinit var webView: RobustWebView
53 |
54 | private val webViewListener = object : WebViewListener {
55 | override fun onProgressChanged(webView: RobustWebView, progress: Int) {
56 | tvProgress.text = progress.toString()
57 | }
58 |
59 | override fun onReceivedTitle(webView: RobustWebView, title: String) {
60 | tvTitle.text = title
61 | }
62 |
63 | override fun onPageFinished(webView: RobustWebView, url: String) {
64 |
65 | }
66 | }
67 |
68 | override fun onCreate(savedInstanceState: Bundle?) {
69 | super.onCreate(savedInstanceState)
70 | setContentView(R.layout.activity_main)
71 | webView = WebViewCacheHolder.acquireWebViewInternal(this)
72 | webView.webViewListener = webViewListener
73 | val layoutParams = LinearLayout.LayoutParams(
74 | ViewGroup.LayoutParams.MATCH_PARENT,
75 | ViewGroup.LayoutParams.MATCH_PARENT
76 | )
77 | webViewContainer.addView(webView, layoutParams)
78 | findViewById(R.id.tvBack).setOnClickListener {
79 | onBackPressedDispatcher.onBackPressed()
80 | }
81 | findViewById(R.id.btnOpenUrl1).setOnClickListener {
82 | webView.loadUrl(url1)
83 | }
84 | findViewById(R.id.btnOpenUrl2).setOnClickListener {
85 | webView.loadUrl(url2)
86 | }
87 | findViewById(R.id.btnOpenUrl3).setOnClickListener {
88 | webView.toLoadUrl(url3, "")
89 | }
90 | findViewById(R.id.btnReload).setOnClickListener {
91 | webView.reload()
92 | }
93 | findViewById(R.id.btnOpenHtml).setOnClickListener {
94 | webView.loadUrl("""file:/android_asset/javascript.html""")
95 | }
96 | findViewById(R.id.btnCallJsByAndroid).setOnClickListener {
97 | val parameter = "\"业志陈\""
98 | webView.evaluateJavascript(
99 | "javascript:callJsByAndroid(${parameter})"
100 | ) {
101 | showToast("evaluateJavascript: $it")
102 | }
103 | // webView.loadUrl("javascript:callJsByAndroid(${parameter})")
104 | }
105 | findViewById(R.id.btnShowToastByAndroid).setOnClickListener {
106 | webView.loadUrl("javascript:showToastByAndroid()")
107 | }
108 | findViewById(R.id.btnCallJsPrompt).setOnClickListener {
109 | webView.loadUrl("javascript:callJsPrompt()")
110 | }
111 | onBackPressedObserver()
112 | requestNotificationPermission()
113 | }
114 |
115 | private fun requestNotificationPermission() {
116 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
117 | requestNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
118 | } else {
119 | checkNotificationPermission()
120 | }
121 | }
122 |
123 | private fun checkNotificationPermission() {
124 | if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) {
125 | showToast("请开启消息通知权限,以便查看网络请求")
126 | }
127 | }
128 |
129 | private fun onBackPressedObserver() {
130 | onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
131 | override fun handleOnBackPressed() {
132 | if (webView.canGoBack()) {
133 | webView.toGoBack()
134 | } else {
135 | finish()
136 | }
137 | }
138 | })
139 | }
140 |
141 | override fun onDestroy() {
142 | super.onDestroy()
143 | WebViewCacheHolder.prepareWebView()
144 | }
145 |
146 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/leavesczy/robustwebview/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview
2 |
3 | import android.app.Application
4 | import github.leavesczy.robustwebview.base.WebViewInitTask
5 | import github.leavesczy.robustwebview.utils.ContextHolder
6 |
7 | /**
8 | * @Author: leavesCZY
9 | * @Date: 2021/9/12 22:22
10 | * @Desc:
11 | * @Github:https://github.com/leavesCZY
12 | */
13 | class MainApplication : Application() {
14 |
15 | override fun onCreate() {
16 | super.onCreate()
17 | ContextHolder.init(application = this)
18 | WebViewInitTask.init(application = this)
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/leavesczy/robustwebview/base/RobustWebView.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview.base
2 |
3 | import android.content.Context
4 | import android.content.MutableContextWrapper
5 | import android.graphics.Bitmap
6 | import android.util.AttributeSet
7 | import android.view.ViewGroup
8 | import androidx.lifecycle.DefaultLifecycleObserver
9 | import androidx.lifecycle.LifecycleOwner
10 | import com.tencent.smtt.export.external.interfaces.JsPromptResult
11 | import com.tencent.smtt.export.external.interfaces.JsResult
12 | import com.tencent.smtt.export.external.interfaces.SslError
13 | import com.tencent.smtt.export.external.interfaces.SslErrorHandler
14 | import com.tencent.smtt.export.external.interfaces.WebResourceRequest
15 | import com.tencent.smtt.export.external.interfaces.WebResourceResponse
16 | import com.tencent.smtt.sdk.CookieManager
17 | import com.tencent.smtt.sdk.WebChromeClient
18 | import com.tencent.smtt.sdk.WebSettings
19 | import com.tencent.smtt.sdk.WebView
20 | import com.tencent.smtt.sdk.WebViewClient
21 | import github.leavesczy.robustwebview.JsInterface
22 | import github.leavesczy.robustwebview.utils.log
23 | import java.io.File
24 |
25 | /**
26 | * @Author: leavesCZY
27 | * @Date: 2021/9/20 22:45
28 | * @Desc:
29 | * @Github:https://github.com/leavesCZY
30 | */
31 | interface WebViewListener {
32 |
33 | fun onProgressChanged(webView: RobustWebView, progress: Int) {
34 |
35 | }
36 |
37 | fun onReceivedTitle(webView: RobustWebView, title: String) {
38 |
39 | }
40 |
41 | fun onPageFinished(webView: RobustWebView, url: String) {
42 |
43 | }
44 |
45 | }
46 |
47 | class RobustWebView(context: Context, attributeSet: AttributeSet? = null) :
48 | WebView(context, attributeSet) {
49 |
50 | private val baseCacheDir by lazy {
51 | File(context.cacheDir, "webView")
52 | }
53 |
54 | private val databaseCachePath by lazy {
55 | File(baseCacheDir, "databaseCache").absolutePath
56 | }
57 |
58 | private val appCachePath by lazy {
59 | File(baseCacheDir, "appCache").absolutePath
60 | }
61 |
62 | var hostLifecycleOwner: LifecycleOwner? = null
63 |
64 | var webViewListener: WebViewListener? = null
65 |
66 | private val mWebChromeClient = object : WebChromeClient() {
67 |
68 | override fun onProgressChanged(webView: WebView, newProgress: Int) {
69 | super.onProgressChanged(webView, newProgress)
70 | log("onProgressChanged-$newProgress")
71 | webViewListener?.onProgressChanged(this@RobustWebView, newProgress)
72 | }
73 |
74 | override fun onReceivedTitle(webView: WebView, title: String?) {
75 | super.onReceivedTitle(webView, title)
76 | log("onReceivedTitle-$title")
77 | webViewListener?.onReceivedTitle(this@RobustWebView, title ?: "")
78 | }
79 |
80 | override fun onJsAlert(
81 | webView: WebView,
82 | url: String?,
83 | message: String?,
84 | result: JsResult
85 | ): Boolean {
86 | log("onJsAlert: $webView $message")
87 | return super.onJsAlert(webView, url, message, result)
88 | }
89 |
90 | override fun onJsConfirm(
91 | webView: WebView,
92 | url: String?,
93 | message: String?,
94 | result: JsResult
95 | ): Boolean {
96 | log("onJsConfirm: $url $message")
97 | return super.onJsConfirm(webView, url, message, result)
98 | }
99 |
100 | override fun onJsPrompt(
101 | webView: WebView,
102 | url: String?,
103 | message: String?,
104 | defaultValue: String?,
105 | result: JsPromptResult?
106 | ): Boolean {
107 | log("onJsPrompt: $url $message $defaultValue")
108 | return super.onJsPrompt(webView, url, message, defaultValue, result)
109 | }
110 | }
111 |
112 | private val mWebViewClient = object : WebViewClient() {
113 |
114 | private var startTime = 0L
115 |
116 | override fun shouldOverrideUrlLoading(webView: WebView, url: String): Boolean {
117 | webView.loadUrl(url)
118 | return true
119 | }
120 |
121 | override fun onPageStarted(webView: WebView, url: String?, favicon: Bitmap?) {
122 | super.onPageStarted(webView, url, favicon)
123 | startTime = System.currentTimeMillis()
124 | }
125 |
126 | override fun onPageFinished(webView: WebView, url: String?) {
127 | super.onPageFinished(webView, url)
128 | log("onPageFinished-$url")
129 | webViewListener?.onPageFinished(this@RobustWebView, url ?: "")
130 | log("onPageFinished duration: " + (System.currentTimeMillis() - startTime))
131 | }
132 |
133 | override fun onReceivedSslError(
134 | webView: WebView,
135 | handler: SslErrorHandler?,
136 | error: SslError?
137 | ) {
138 | log("onReceivedSslError-$error")
139 | super.onReceivedSslError(webView, handler, error)
140 | }
141 |
142 | override fun shouldInterceptRequest(webView: WebView, url: String): WebResourceResponse? {
143 | return super.shouldInterceptRequest(webView, url)
144 | }
145 |
146 | override fun shouldInterceptRequest(
147 | webView: WebView,
148 | request: WebResourceRequest
149 | ): WebResourceResponse? {
150 | return WebViewInterceptRequestProxy.shouldInterceptRequest(request)
151 | ?: super.shouldInterceptRequest(webView, request)
152 | }
153 | }
154 |
155 | init {
156 | webViewClient = mWebViewClient
157 | webChromeClient = mWebChromeClient
158 | initWebViewSettings(this)
159 | initWebViewSettingsExtension(this)
160 | addJavascriptInterface(JsInterface(), "android")
161 | setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
162 | log(
163 | "setDownloadListener: $url \n" +
164 | "$userAgent \n " +
165 | " $contentDisposition \n" +
166 | " $mimetype \n" +
167 | " $contentLength"
168 | )
169 | }
170 | }
171 |
172 | fun toLoadUrl(url: String, cookie: String) {
173 | val mCookieManager = CookieManager.getInstance()
174 | mCookieManager?.setCookie(url, cookie)
175 | mCookieManager?.flush()
176 | loadUrl(url)
177 | }
178 |
179 | fun toGoBack(): Boolean {
180 | if (canGoBack()) {
181 | goBack()
182 | return false
183 | }
184 | return true
185 | }
186 |
187 | private fun initWebViewSettings(webView: WebView) {
188 | val settings = webView.settings
189 | // settings.userAgentString = "android-leavesCZY"
190 | settings.javaScriptEnabled = true
191 | settings.pluginsEnabled = true
192 | settings.useWideViewPort = true
193 | settings.loadWithOverviewMode = true
194 | settings.setSupportZoom(false)
195 | settings.builtInZoomControls = false
196 | settings.displayZoomControls = false
197 | settings.allowFileAccess = true
198 | settings.allowContentAccess = true
199 | settings.loadsImagesAutomatically = true
200 | settings.safeBrowsingEnabled = false
201 | settings.domStorageEnabled = true
202 | settings.databaseEnabled = true
203 | settings.databasePath = databaseCachePath
204 | settings.setAppCacheEnabled(true)
205 | settings.setAppCachePath(appCachePath)
206 | settings.cacheMode = WebSettings.LOAD_DEFAULT
207 | settings.javaScriptCanOpenWindowsAutomatically = true
208 | settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
209 | }
210 |
211 | private fun initWebViewSettingsExtension(webView: WebView) {
212 | val settingsExtension = webView.settingsExtension ?: return
213 | //开启后前进后退将不再重新加载页面
214 | settingsExtension.setContentCacheEnable(true)
215 | //对于刘海屏机器如果WebView被遮挡会自动padding
216 | settingsExtension.setDisplayCutoutEnable(true)
217 | settingsExtension.setDayOrNight(true)
218 | }
219 |
220 | override fun onAttachedToWindow() {
221 | super.onAttachedToWindow()
222 | log("onAttachedToWindow : $context")
223 | (hostLifecycleOwner ?: findLifecycleOwner(context))?.let {
224 | addHostLifecycleObserver(it)
225 | }
226 | }
227 |
228 | private fun findLifecycleOwner(context: Context): LifecycleOwner? {
229 | if (context is LifecycleOwner) {
230 | return context
231 | }
232 | if (context is MutableContextWrapper) {
233 | val baseContext = context.baseContext
234 | if (baseContext is LifecycleOwner) {
235 | return baseContext
236 | }
237 | }
238 | return null
239 | }
240 |
241 | private fun addHostLifecycleObserver(lifecycleOwner: LifecycleOwner) {
242 | log("addLifecycleObserver")
243 | lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
244 | override fun onResume(owner: LifecycleOwner) {
245 | onHostResume()
246 | }
247 |
248 | override fun onPause(owner: LifecycleOwner) {
249 | onHostPause()
250 | }
251 |
252 | override fun onDestroy(owner: LifecycleOwner) {
253 | onHostDestroy()
254 | }
255 | })
256 | }
257 |
258 | private fun onHostResume() {
259 | log("onHostResume")
260 | onResume()
261 | }
262 |
263 | private fun onHostPause() {
264 | log("onHostPause")
265 | onPause()
266 | }
267 |
268 | private fun onHostDestroy() {
269 | log("onHostDestroy")
270 | release()
271 | }
272 |
273 | private fun release() {
274 | hostLifecycleOwner = null
275 | webViewListener = null
276 | webChromeClient = null
277 | webViewClient = null
278 | (parent as? ViewGroup)?.removeView(this)
279 | destroy()
280 | }
281 |
282 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/leavesczy/robustwebview/base/WebViewCacheHolder.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview.base
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.content.MutableContextWrapper
6 | import android.os.Looper
7 | import github.leavesczy.robustwebview.utils.log
8 | import java.util.Stack
9 |
10 | /**
11 | * @Author: leavesCZY
12 | * @Date: 2021/10/4 18:57
13 | * @Desc:
14 | * @Github:https://github.com/leavesCZY
15 | */
16 | object WebViewCacheHolder {
17 |
18 | private val webViewCacheStack = Stack()
19 |
20 | private const val CACHED_WEB_VIEW_MAX_NUM = 4
21 |
22 | private lateinit var application: Application
23 |
24 | fun init(application: Application) {
25 | this.application = application
26 | prepareWebView()
27 | }
28 |
29 | fun prepareWebView() {
30 | if (webViewCacheStack.size < CACHED_WEB_VIEW_MAX_NUM) {
31 | Looper.myQueue().addIdleHandler {
32 | log("WebViewCacheStack Size: " + webViewCacheStack.size)
33 | if (webViewCacheStack.size < CACHED_WEB_VIEW_MAX_NUM) {
34 | webViewCacheStack.push(createWebView(MutableContextWrapper(application)))
35 | }
36 | false
37 | }
38 | }
39 | }
40 |
41 | fun acquireWebViewInternal(context: Context): RobustWebView {
42 | if (webViewCacheStack.isEmpty()) {
43 | return createWebView(context)
44 | }
45 | val webView = webViewCacheStack.pop()
46 | val contextWrapper = webView.context as MutableContextWrapper
47 | contextWrapper.baseContext = context
48 | return webView
49 | }
50 |
51 | private fun createWebView(context: Context): RobustWebView {
52 | return RobustWebView(context)
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/leavesczy/robustwebview/base/WebViewInitTask.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview.base
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import com.tencent.smtt.export.external.TbsCoreSettings
6 | import com.tencent.smtt.sdk.QbSdk
7 | import github.leavesczy.robustwebview.utils.log
8 |
9 | /**
10 | * @Author: leavesCZY
11 | * @Date: 2021/9/20 23:47
12 | * @Desc:
13 | * @Github:https://github.com/leavesCZY
14 | */
15 | object WebViewInitTask {
16 |
17 | fun init(application: Application) {
18 | initWebView(application)
19 | WebViewCacheHolder.init(application)
20 | WebViewInterceptRequestProxy.init(application)
21 | }
22 |
23 | private fun initWebView(context: Context) {
24 | QbSdk.setDownloadWithoutWifi(true)
25 | val map = mutableMapOf()
26 | map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true
27 | map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true
28 | map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true
29 | QbSdk.initTbsSettings(map)
30 | val callback = object : QbSdk.PreInitCallback {
31 | override fun onViewInitFinished(isX5Core: Boolean) {
32 | log("onViewInitFinished: $isX5Core")
33 | }
34 |
35 | override fun onCoreInitFinished() {
36 | log("onCoreInitFinished")
37 | }
38 | }
39 | QbSdk.initX5Environment(context, callback)
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/leavesczy/robustwebview/base/WebViewInterceptRequestProxy.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview.base
2 |
3 | import android.app.Application
4 | import com.chuckerteam.chucker.api.ChuckerCollector
5 | import com.chuckerteam.chucker.api.ChuckerInterceptor
6 | import com.tencent.smtt.export.external.interfaces.WebResourceRequest
7 | import com.tencent.smtt.export.external.interfaces.WebResourceResponse
8 | import github.leavesczy.robustwebview.utils.log
9 | import okhttp3.Cache
10 | import okhttp3.Interceptor
11 | import okhttp3.OkHttpClient
12 | import okhttp3.Request
13 | import java.io.File
14 |
15 | /**
16 | * @Author: leavesCZY
17 | * @Date: 2021/10/4 18:56
18 | * @Desc:
19 | * @Github:https://github.com/leavesCZY
20 | */
21 | object WebViewInterceptRequestProxy {
22 |
23 | private lateinit var application: Application
24 |
25 | private val webViewResourceCacheDir by lazy {
26 | File(application.cacheDir, "RobustWebView")
27 | }
28 |
29 | private val okHttpClient by lazy {
30 | OkHttpClient.Builder().cache(Cache(webViewResourceCacheDir, 600L * 1024 * 1024))
31 | .followRedirects(false)
32 | .followSslRedirects(false)
33 | .addInterceptor(getChuckerInterceptor(application = application))
34 | .addNetworkInterceptor(getWebViewCacheInterceptor())
35 | .build()
36 | }
37 |
38 | private fun getChuckerInterceptor(application: Application): Interceptor {
39 | return ChuckerInterceptor.Builder(application)
40 | .collector(ChuckerCollector(application))
41 | .maxContentLength(250000L)
42 | .alwaysReadResponseBody(true)
43 | .build()
44 | }
45 |
46 | private fun getWebViewCacheInterceptor(): Interceptor {
47 | return Interceptor { chain ->
48 | val request = chain.request()
49 | val response = chain.proceed(request)
50 | response.newBuilder()
51 | .removeHeader("pragma")
52 | .removeHeader("Cache-Control")
53 | .header("Cache-Control", "max-age=" + (360L * 24 * 60 * 60))
54 | .build()
55 | }
56 | }
57 |
58 | fun init(application: Application) {
59 | this.application = application
60 | }
61 |
62 | fun shouldInterceptRequest(webResourceRequest: WebResourceRequest): WebResourceResponse? {
63 | if (toProxy(webResourceRequest)) {
64 | return getHttpResource(webResourceRequest)
65 | }
66 | return null
67 | }
68 |
69 | private fun toProxy(webResourceRequest: WebResourceRequest): Boolean {
70 | if (webResourceRequest.isForMainFrame) {
71 | return false
72 | }
73 | val url = webResourceRequest.url ?: return false
74 | if (!webResourceRequest.method.equals("GET", true)) {
75 | return false
76 | }
77 | if (url.scheme == "https" || url.scheme == "http") {
78 | val urlString = url.toString()
79 | if (urlString.endsWith(".js", true) ||
80 | urlString.endsWith(".css", true) ||
81 | urlString.endsWith(".jpg", true) ||
82 | urlString.endsWith(".png", true) ||
83 | urlString.endsWith(".webp", true) ||
84 | urlString.endsWith(".awebp", true)
85 | ) {
86 | return true
87 | }
88 | }
89 | return false
90 | }
91 |
92 | private fun getHttpResource(webResourceRequest: WebResourceRequest): WebResourceResponse? {
93 | try {
94 | val url = webResourceRequest.url.toString()
95 | val requestBuilder = Request
96 | .Builder()
97 | .url(url)
98 | .method(webResourceRequest.method, null)
99 | webResourceRequest.requestHeaders?.forEach {
100 | requestBuilder.addHeader(it.key, it.value)
101 | }
102 | val response = okHttpClient
103 | .newCall(requestBuilder.build())
104 | .execute()
105 | val body = response.body
106 | val code = response.code
107 | if (body == null || code != 200) {
108 | return null
109 | }
110 | val mimeType = response.header("content-type", body.contentType()?.type)
111 | val encoding = response.header("content-encoding", "utf-8")
112 | val responseHeaders = buildMap {
113 | response.headers.map {
114 | put(it.first, it.second)
115 | }
116 | }
117 | var message = response.message
118 | if (message.isBlank()) {
119 | message = "OK"
120 | }
121 | val resourceResponse = WebResourceResponse(mimeType, encoding, body.byteStream())
122 | resourceResponse.responseHeaders = responseHeaders
123 | resourceResponse.setStatusCodeAndReasonPhrase(code, message)
124 | return resourceResponse
125 | } catch (e: Throwable) {
126 | e.printStackTrace()
127 | }
128 | return null
129 | }
130 |
131 | private fun getAssetsImage(url: String): WebResourceResponse? {
132 | if (url.contains(".jpg")) {
133 | try {
134 | val inputStream = application.assets.open("ic_launcher.webp")
135 | return WebResourceResponse(
136 | "image/webp",
137 | "utf-8", inputStream
138 | )
139 | } catch (e: Throwable) {
140 | log("Throwable: $e")
141 | }
142 | }
143 | return null
144 | }
145 |
146 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/leavesczy/robustwebview/utils/ContextHolder.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview.utils
2 |
3 | import android.app.Application
4 |
5 | /**
6 | * @Author: leavesCZY
7 | * @Date: 2021/10/1 23:16
8 | * @Desc:
9 | * @Github:https://github.com/leavesCZY
10 | */
11 | object ContextHolder {
12 |
13 | lateinit var application: Application
14 | private set
15 |
16 | fun init(application: Application) {
17 | this.application = application
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/leavesczy/robustwebview/utils/Utils.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview.utils
2 |
3 | import android.os.Build
4 | import android.util.Log
5 | import android.view.View
6 | import android.view.ViewParent
7 | import android.widget.Toast
8 | import java.lang.reflect.Method
9 |
10 | /**
11 | * @Author: leavesCZY
12 | * @Date: 2021/9/20 0:11
13 | * @Desc:
14 | * @Github:https://github.com/leavesCZY
15 | */
16 | fun log(log: Any?) {
17 | Log.e("RobustWebView-" + Thread.currentThread().name, log.toString())
18 | }
19 |
20 | fun showToast(msg: String) {
21 | Toast.makeText(ContextHolder.application, msg, Toast.LENGTH_SHORT).show()
22 | }
23 |
24 | /**
25 | * 让 activity transition 动画过程中可以正常渲染页面
26 | */
27 | fun setDrawDuringWindowsAnimating(view: View) {
28 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M
29 | || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1
30 | ) {
31 | //小于 4.3 和大于 6.0 时不存在此问题,无须处理
32 | return
33 | }
34 | try {
35 | val rootParent: ViewParent = view.rootView.parent
36 | val method: Method = rootParent.javaClass
37 | .getDeclaredMethod("setDrawDuringWindowsAnimating", Boolean::class.javaPrimitiveType)
38 | method.isAccessible = true
39 | method.invoke(rootParent, true)
40 | } catch (e: Throwable) {
41 | e.printStackTrace()
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
29 |
30 |
36 |
37 |
43 |
44 |
50 |
51 |
57 |
58 |
64 |
65 |
71 |
72 |
73 |
74 |
80 |
81 |
87 |
88 |
96 |
97 |
107 |
108 |
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leavesCZY/RobustWebView/a8cee699ad99f59f0b0ee14ee082798b4f96358d/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leavesCZY/RobustWebView/a8cee699ad99f59f0b0ee14ee082798b4f96358d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leavesCZY/RobustWebView/a8cee699ad99f59f0b0ee14ee082798b4f96358d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leavesCZY/RobustWebView/a8cee699ad99f59f0b0ee14ee082798b4f96358d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF0277BD
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RobustWebView
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/test/java/github/leavesczy/robustwebview/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package github.leavesczy.robustwebview
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application").version("8.7.3").apply(false)
3 | id("org.jetbrains.kotlin.android").version("2.1.0").apply(false)
4 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonFinalResIds=true
24 | android.nonTransitiveRClass=true
25 | android.enableR8.fullMode=true
26 | android.defaults.buildfeatures.buildconfig=false
27 | android.defaults.buildfeatures.resvalues=false
28 | android.defaults.buildfeatures.shaders=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leavesCZY/RobustWebView/a8cee699ad99f59f0b0ee14ee082798b4f96358d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/
2 | env sh
3 |
4 | #
5 |
6 | # Copyright 2015 the original author or authors.
7 | #
8 |
9 | # Licensed under the Apache License, Version 2.0 (the "License");
10 | # you may not use this file except in compliance with the License.
11 | # You may obtain a copy of the License at
12 | #
13 |
14 | # https://www.apache.org/licenses/LICENSE-2.0
15 | #
16 |
17 | # Unless required by applicable law or agreed to in writing, software
18 | # distributed under the License is distributed on an "AS IS" BASIS,
19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | # See the License for the specific language governing permissions and
21 | # limitations under the License.
22 | #
23 |
24 | ##############################################################################
25 | ##
26 | ##
27 | Gradle start
28 | up script
29 | for
30 | UN *X
31 | ##
32 | ##############################################################################
33 |
34 | # Attempt to set APP_HOME
35 | # Resolve links: $0 may be a link
36 | PRG = "$0"
37 | # Need this for relative symlinks.
38 | while [ -h "$PRG" ]; do
39 | ls =
40 | `ls -ld "$PRG"`
41 | link =
42 | `expr "$ls" : '.*-> \(.*\)$'`
43 | if expr "$link" : '/.*' > /dev/
44 | null;
45 | then
46 | PRG = "$link"
47 | else
48 | PRG =
49 | `dirname "$PRG"`"/$link"
50 | fi
51 | done
52 | SAVED = "`pwd`"
53 | cd "`dirname \"$PRG\"`/" >/dev/
54 | null
55 | APP_HOME = "`pwd -P`"
56 | cd "$SAVED" >/dev/
57 | null
58 |
59 | APP_NAME = "Gradle"
60 | APP_BASE_NAME =
61 | `basename "$0"`
62 |
63 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
64 | DEFAULT_JVM_OPTS = '"-Xmx64m" "-Xms64m"'
65 |
66 | # Use the maximum available, or set MAX_FD != -1 to use that value.
67 | MAX_FD = "maximum"
68 |
69 | warn() {
70 | echo
71 | "$*"
72 | }
73 |
74 | die() {
75 | echo
76 | echo
77 | "$*"
78 | echo
79 | exit
80 | 1
81 | }
82 |
83 | # OS specific support (must be 'true' or 'false').
84 | cygwin = false
85 | msys = false
86 | darwin = false
87 | nonstop = false
88 | case "`uname`"
89 | in
90 | CYGWIN
91 | * )
92 | cygwin = true;;
93 | Darwin* )
94 | darwin = true;;
95 | MINGW* )
96 | msys = true;;
97 | NONSTOP* )
98 | nonstop = true;;
99 | esac
100 |
101 | CLASSPATH = $APP_HOME / gradle / wrapper / gradle - wrapper.jar
102 |
103 |
104 | # Determine the Java command to use to start the JVM.
105 | if [ -n "$JAVA_HOME" ]; then
106 | if [ -x "$JAVA_HOME/jre/sh/java" ];
107 | then
108 | # IBM's JDK on AIX uses strange locations for the executables
109 | JAVACMD = "$JAVA_HOME/jre/sh/java"
110 | else
111 | JAVACMD = "$JAVA_HOME/bin/java"
112 | fi
113 | if [ ! -x "$JAVACMD" ];
114 | then
115 | die
116 | "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
117 |
118 | Please set
119 | the JAVA_HOME
120 | variable in
121 | your environment
122 | to match
123 | the
124 | location
125 | of your
126 | Java installation
127 | ."
128 | fi
129 | else
130 | JAVACMD = "java"
131 | which java
132 | >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
133 |
134 | Please set
135 | the JAVA_HOME
136 | variable in
137 | your environment
138 | to match
139 | the
140 | location
141 | of your
142 | Java installation
143 | ."
144 | fi
145 |
146 | # Increase the maximum file descriptors if we can.
147 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ];
148 | then
149 | MAX_FD_LIMIT =
150 | `ulimit -H -n`
151 | if [ $? -eq 0 ]; then
152 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ];
153 | then
154 | MAX_FD = "$MAX_FD_LIMIT"
155 | fi
156 | ulimit
157 | -
158 | n $MAX_FD
159 | if [ $? -ne 0 ];
160 | then
161 | warn
162 | "Could not set maximum file descriptor limit: $MAX_FD"
163 | fi
164 | else
165 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
166 | fi
167 | fi
168 |
169 | # For Darwin, add options to specify how the application appears in the dock
170 | if
171 | $darwin;
172 | then
173 | GRADLE_OPTS = "$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
174 | fi
175 |
176 | # For Cygwin or MSYS, switch paths to Windows format before running java
177 | if [ "$cygwin" = "true" -o "$msys" = "true" ];
178 | then
179 | APP_HOME =
180 | `cygpath --path --mixed "$APP_HOME"`
181 | CLASSPATH =
182 | `cygpath --path --mixed "$CLASSPATH"`
183 |
184 | JAVACMD =
185 | `cygpath --unix "$JAVACMD"`
186 |
187 | # We build the pattern for arguments to be converted via cygpath
188 | ROOTDIRSRAW =
189 | `find -L / -maxdepth 1 -mindepth 1 -
190 | type d
191 | 2>/dev/null`
192 | SEP = ""
193 | for
194 | dir in
195 | $ROOTDIRSRAW;
196 | do
197 | ROOTDIRS = "$ROOTDIRS$SEP$dir"
198 | SEP = "|"
199 | done
200 | OURCYGPATTERN = "(^($ROOTDIRS))"
201 | # Add a user-defined pattern to the cygpath arguments
202 | if [ "$GRADLE_CYGPATTERN" != "" ];
203 | then
204 | OURCYGPATTERN = "$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
205 | fi
206 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
207 | i = 0
208 | for
209 | arg in
210 | "$@"; do
211 | CHECK =
212 | `echo "$arg"|egrep -c "$OURCYGPATTERN" -`
213 | CHECK2 =
214 | `echo "$arg"|egrep -c "^-"` ### Determine if
215 | an option
216 |
217 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ###
218 | Added a
219 | condition
220 | eval
221 | `
222 | echo args$i
223 | `=`cygpath --path --ignore --mixed "$arg"`
224 | else
225 | eval `
226 | echo args$i
227 | `="\"$arg\""
228 | fi
229 | i =
230 | `
231 | expr $i
232 | + 1`
233 | done
234 | case
235 | $i in
236 | 0) set --;;
237 | 1) set -- "$args0";;
238 | 2) set -- "$args0" "$args1";;
239 | 3) set -- "$args0" "$args1" "$args2";;
240 | 4) set -- "$args0" "$args1" "$args2" "$args3";;
241 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4";;
242 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5";;
243 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6";;
244 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7";;
245 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8";;
246 | esac
247 | fi
248 |
249 | # Escape application args
250 |
251 | save() {
252 | for
253 | i
254 | do printf % s\\n
255 | "$i" | sed
256 | "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/";
257 | done
258 | echo
259 | " "
260 | }
261 |
262 | APP_ARGS =
263 | `save "$@"`
264 |
265 | # Collect all arguments for the java command, following the shell quoting and substitution rules
266 | eval set
267 | --
268 | $DEFAULT_JVM_OPTS $JAVA_OPTS
269 | $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
270 |
271 | exec "$JAVACMD" "$@"
272 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | pluginManagement {
4 | repositories {
5 | google()
6 | mavenCentral()
7 | gradlePluginPortal()
8 | }
9 | }
10 | dependencyResolutionManagement {
11 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
12 | repositories {
13 | google()
14 | mavenCentral()
15 | }
16 | }
17 |
18 | rootProject.name = "RobustWebView"
19 | include(":app")
--------------------------------------------------------------------------------