preface:
With the improvement of android version, there are some inexplicable bug s in the native webview of android, such as: virtual mouse can't click, h5 compatibility problem, unable to pull up and pull down, pull-up and pull-down conflict, and so on. So I think of using the third-party replacement scheme. The first is the x5 kernel respected on the Internet. It is said to be very easy to use. It can share and use wechat and QQ kernel without downloading or occupying the apk size. It is very fast, and it supports rich functions, and it is still being maintained and updated. Another is the need for static integration of crosswalk. The most fatal disadvantage is that it will increase the apk package, and it seems that the official website has stopped maintenance. So I must have chosen to use x5 first.
Notice the feeling of use in advance:
x5 kernel: all kinds of pits, and it is not possible to share and use wechat and QQ kernel as the documents and rumors say. According to the actual measurement, it can be shared and used only when the QQ browser is installed. Even if wechat and QQ are installed, it still needs to download the kernel again to load successfully. Sometimes the process of downloading and loading for the first time is extremely slow, and there is a probability of failure. After a series of modifications, it can be used normally.
crosswalk kernel: just when you are at a loss when using x5, think of trying this product. As a result, this product is not a fuel-efficient lamp It doesn't fit well. I'm in Android 8 The project of 1 can be used normally. Although there are some minor defects, it can be repaired. However, when Android projects above 10 and 11 are completed, they will collapse directly. As soon as the library is loaded, they will collapse...
To put it simply, my x5 loading scheme: initialize, load, register and monitor the loading results the first time. The function of opening h5 page is written into a unified static global method. Each time the method is called, judge whether it is loaded successfully. If it is not loaded successfully x5, open it with the native WebViewActivity. After loading successfully, open it with X5WebViewActivity. If loading x5 fails, initialize the sdk in the failed callback and start the download manually.
1, Integrated sdk package
Download: Tencent browsing service - SDK download (tencent.com)
Official documents: Tencent browsing service (tencent.com)
//x5 kernel tbs browsing service implementation files('libs/tbs_sdk_thirdapp_v4.3.0.151_44051.jar')
AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- tencent H5 --> <service android:name="com.tencent.smtt.export.external.DexClassLoaderProviderService" android:label="dexopt" android:process=":dexopt"> </service>
Confuse Proguard cfg
-dontwarn dalvik.** -dontwarn com.tencent.smtt.** -keep class com.tencent.smtt.** { *; } -keep class com.tencent.tbs.** { *; }
2, Loading and using
Load directly in the Application or start the service for loading. The measured effect is no different
2.1 load directly in Application
class App : Application() { private var isLoadingFinished = false //Load x5 end private var isLoadingSuccess = false //Load x5 successfully val isDebug = false override fun onCreate() { super.onCreate() // Before calling TBS initialization and creating WebView, perform the following configuration val map = HashMap<String, Any>() map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true // map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true QbSdk.initTbsSettings(map) // X5CorePreLoadService.enqueueWork(this, Intent()) / / load in the service initX5()//Direct loading } fun initX5() { val cb: PreInitCallback = object : PreInitCallback { override fun onViewInitFinished(arg0: Boolean) { // TODO Auto-generated method stub //The callback after x5 kernel initialization is completed. If it is true, it indicates that x5 kernel loading is successful. Otherwise, it indicates that x5 kernel loading fails, and //Automatically switch to the system kernel. Log.d("x5", " onViewInitFinished is $arg0") if (arg0) { //When loading is successful isLoadingFinished = true isLoadingSuccess = true sendBroadcast(Intent("closemyloading")) showDebug("load h5 Kernel success") } else { isLoadingFinished = true isLoadingSuccess = false sendBroadcast(Intent("closemyloading")) showDebug("load h5 Kernel failed to download again") reDownloadX5()//Download again } } override fun onCoreInitFinished() { // TODO Auto-generated method stub } } //x5 kernel initialization interface QbSdk.initX5Environment(applicationContext, cb) QbSdk.setDownloadWithoutWifi(true)//Set to download x5 kernel without wifi QbSdk.setTbsListener(object : TbsListener { override fun onDownloadFinish(i: Int) { //tbs kernel download completion callback //However, only when i is equal to 100 can it be completed, otherwise it will fail //At this time, the probability may be due to network problems //If it fails, the network listener can be added showDebug("download x5Core complete") } override fun onInstallFinish(i: Int) { //The callback of kernel installation completion is usually completed here, but in //Loading failure may also occur in very few cases. For example, the author occasionally appears under the company's intranet, which can be ignored showDebug("install x5Core complete") } override fun onDownloadProgress(i: Int) { Log.d("x5", "progress: " + i) //Download progress monitoring sendBroadcast(Intent("updateProgress").putExtra("progress", i)) } }) } fun reDownloadX5(){ // Before calling TBS initialization and creating WebView, perform the following configuration val map = HashMap<String, Any>() map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true // map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true QbSdk.initTbsSettings(map) //Determine whether to download the kernel by yourself //false indicates that the download is complete or part of it has been downloaded. true indicates that it has not been downloaded at all val needDownload = TbsDownloader.needDownload( applicationContext, TbsDownloader.DOWNLOAD_OVERSEA_TBS ) Log.d("X5", needDownload.toString() + "") //Reset QbSdk.reset(applicationContext) // Start download TbsDownloader.startDownload(applicationContext) } fun showDebug(message:String){ if (isDebug){ shortShow(message) } } fun isX5LoadingFinished(): Boolean { return isLoadingFinished } fun setIsX5LoadingFinished(isFinished: Boolean) { isLoadingFinished = isFinished } fun isX5LoadingSuccess(): Boolean { return isLoadingSuccess } fun setIsX5LoadingSuccess(isFinished: Boolean) { isLoadingSuccess = isFinished } }
2.2 start the service to load
Release the notes in the Application:
X5CorePreLoadService.enqueueWork(this, Intent())//Load in service
class X5CorePreLoadService : JobIntentService() { companion object { val JOB_ID = 1 private var mContext: Context? = null fun enqueueWork(context: Context, work: Intent) { mContext = context enqueueWork(context, X5CorePreLoadService::class.java, JOB_ID, work) } } override fun onHandleWork(intent: Intent) { //Add the code we want to execute here, and the data we need can be saved in Intent, //Each command sent through Intent will be executed sequentially initX5() Log.d("x5", "initX5()") } /** * Initialize X5 kernel */ private fun initX5() { QbSdk.setTbsListener(object : TbsListener { override fun onDownloadFinish(i: Int) { Log.d("x5", "onDownloadFinish: " + i) //tbs kernel download completion callback //However, only when i is equal to 100 can it be completed, otherwise it will fail //At this time, the probability may be due to network problems //If it fails, the network listener can be added } override fun onInstallFinish(i: Int) { Log.d("x5", "onInstallFinish: " + i) //The callback of kernel installation completion is usually completed here, but in //Loading failure may also occur in very few cases. For example, the author occasionally appears under the company's intranet, which can be ignored } override fun onDownloadProgress(i: Int) { Log.d("x5", "progress: " + i) //Download progress monitoring sendBroadcast(Intent("updateProgress").putExtra("progress", i)) } }) //x5 kernel initialization interface QbSdk.initX5Environment(applicationContext, cb) QbSdk.setDownloadWithoutWifi(true)//Set to download x5 kernel without wifi } val cb: PreInitCallback = object : PreInitCallback { override fun onViewInitFinished(arg0: Boolean) { // TODO Auto-generated method stub //The callback after x5 kernel initialization is completed. If it is true, it indicates that x5 kernel loading is successful. Otherwise, it indicates that x5 kernel loading fails, and it will automatically switch to the system kernel. Log.d("x5", " onViewInitFinished is $arg0") if (arg0) { //When loading is successful App.instance?.setIsX5LoadingFinished(true) App.instance?.setIsX5LoadingSuccess(true) sendBroadcast(Intent("closemyloading")) } else { App.instance?.setIsX5LoadingFinished(true) App.instance?.setIsX5LoadingSuccess(false) sendBroadcast(Intent("closemyloading")) // Before calling TBS initialization and creating WebView, perform the following configuration val map = HashMap<String, Any>() map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true // map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true QbSdk.initTbsSettings(map) //Determine whether to download the kernel by yourself //false indicates that the download is complete or part of it has been downloaded. true indicates that it has not been downloaded at all val needDownload = TbsDownloader.needDownload(mContext, TbsDownloader.DOWNLOAD_OVERSEA_TBS) Log.d("X5", needDownload.toString() + "") //Reset QbSdk.reset(mContext) // Start download TbsDownloader.startDownload(mContext) } } override fun onCoreInitFinished() { } } }
Registration:
<service android:name=".app.X5CorePreLoadService" android:enabled="true" android:permission="android.permission.BIND_JOB_SERVICE"/>
Global open method:
fun showH5(context: Context, path:String){ if (App.instance?.isX5LoadingFinished()!!){ if (App.instance?.isX5LoadingSuccess()!!){ Log.d("x5", "x5 will be use to show H5!") context.startActivity(Intent(context, X5WebViewActivity::class.java) .putExtra("path", path)) }else{ Log.d("x5", "default core will be use to show H5!") context.startActivity(Intent(context, WebViewActivity::class.java) .putExtra("path", path)) } }else{ // ToastUtil.shortShow("loading h5 kernel at first boot, please click later") Log.d("x5", "default core will be use to show H5!") context.startActivity(Intent(context, WebViewActivity::class.java) .putExtra("path", path)) } }
class X5WebViewActivity : SimpleActivity(){ private lateinit var binding: ActivityX5WebviewBinding private var mUploadMessage: ValueCallback<Uri>? = null private var mUploadCallbackAboveL: ValueCallback<Array<Uri>>? = null private var webView:WebView? = null var isBottom = false companion object { private val FILE_CHOOSER_RESULT_CODE = 10000 } override fun getLayout(): View { binding = ActivityX5WebviewBinding.inflate(layoutInflater) return binding.root } override fun initEventAndData() { webView = WebView(applicationContext) binding.webView.addView(webView, 0) val url = intent.getStringExtra("path") initWebViewSetting() //Override WebView's default behavior of opening web pages using a third party or the system's default browser, so that web pages can be opened with WebView webView?.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean { // TODO Auto-generated method stub //When the return value is true, control to open WebView, and call system browser or third-party browser when it is false view.loadUrl(url!!) return true } } webView?.webChromeClient = chromeClient webView?.setBackgroundColor(Color.argb(1, 0, 0, 0)) //WebView loads web resources webView?.loadUrl(url!!) } @SuppressLint("SetJavaScriptEnabled") fun initWebViewSetting() { val mWebSettings = webView?.settings!! mWebSettings.javaScriptEnabled = true mWebSettings.defaultTextEncodingName = "utf-8" mWebSettings.useWideViewPort = true mWebSettings.loadWithOverviewMode = true mWebSettings.cacheMode = WebSettings.LOAD_NO_CACHE mWebSettings.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS mWebSettings.domStorageEnabled = true mWebSettings.javaScriptCanOpenWindowsAutomatically = true mWebSettings.setNeedInitialFocus(true) mWebSettings.allowFileAccess = true mWebSettings.allowContentAccess = true mWebSettings.setAppCacheEnabled(true) mWebSettings.setAllowUniversalAccessFromFileURLs(true) mWebSettings.setAllowFileAccessFromFileURLs(true) mWebSettings.setAppCacheMaxSize(Long.MAX_VALUE) mWebSettings.setAppCachePath(this.getDir("appcache", Context.MODE_PRIVATE).path) mWebSettings.databasePath = this.getDir("databases", Context.MODE_PRIVATE).path mWebSettings.pluginState = WebSettings.PluginState.ON_DEMAND mWebSettings.databaseEnabled = true mWebSettings.blockNetworkImage = false //Do not display the webview zoom button mWebSettings.setSupportZoom(false)//Supports scaling. The default value is true. Is the premise of the following API. mWebSettings.builtInZoomControls = false// Set the built-in zoom control. If false, the WebView is not scalable mWebSettings.displayZoomControls = false val mAndroidJsUtils = AndroidJsUtils(this) webView?.addJavascriptInterface(mAndroidJsUtils, "mobile") } @OnClick(R.id.back_btn) fun back() { if (webView?.canGoBack()!!) { webView?.goBack() } else { finish() } } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { if (webView?.canGoBack()!!) { webView?.goBack() //Return to previous page return true } else { finish() //sign out } } return super.onKeyDown(keyCode, event) } private var chromeClient = object: WebChromeClient(){ override fun openFileChooser( uploadFile: ValueCallback<Uri>?, acceptType: String?, captureType: String? ) { //Save the corresponding valuecallback for selection //Start the file selection window or customize file selection through startActivityForResult mUploadMessage = uploadFile if (acceptType!!.contains("image")){ openImageChooserActivity() }else{ openFileChooserActivity() } } } fun openImageChooserActivity() { val i = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) i.type = "image/*" startActivityForResult(Intent.createChooser(i, "FILE_CHOOSER_RESULT_CODE) } fun openFileChooserActivity() { val i = Intent(Intent.ACTION_GET_CONTENT) i.addCategory(Intent.CATEGORY_OPENABLE) i.type = "*/*" startActivityForResult(Intent.createChooser(i, "Select file"), FILE_CHOOSER_RESULT_CODE) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == FILE_CHOOSER_RESULT_CODE) { if (resultCode == RESULT_OK) { //Set the onReceiveValue value value for the ValueCallback selected for the file mUploadMessage?.onReceiveValue(data?.data!!) mUploadMessage = null } else if (resultCode == RESULT_CANCELED) { //Set a null value for the ValueCallback selected by the file mUploadMessage?.onReceiveValue(null) mUploadMessage = null } } } override fun onDestroy() { webView?.destroy() super.onDestroy() } }
<FrameLayout android:id="@+id/web_view" android:layout_width="match_parent" android:layout_height="match_parent" />
Conclusion:
WebViewActivity uses the native WebView to load the url, which will not be repeated here. The above is the main core code. There is also no judgment and Optimization on network disconnection and downloading again after network failure. If you want to improve it, you can optimize it. At present, several tested models of Android 10 and Android 11 can be loaded successfully. I hope to help you. The code is a little long, and some redundant ones haven't been cut in time. If there are errors, please give me more advice.