Integrated Tencent tbs browsing service x5 kernel

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.

Keywords: Android kotlin

Added by gloveny on Fri, 28 Jan 2022 07:53:18 +0200