集成腾讯tbs浏览服务x5内核

前言

由于android 原生的webview随着android版本的提高,出现了一些莫名奇妙的bug,例如:虚拟鼠标无法点击,h5兼容问题,无法上拉下拉,上拉下拉冲突,等等。所以就让我想到了使用第三方的替换方案,首先是网上推崇的x5内核,据说非常好用,可以共享使用微信和QQ内核,无需下载,也不占用apk大小,速度极快,而且支持的功能也丰富,而且目前还在维护更新中。还有一个是需要静态集成的crosswalk,最致命的缺点是会使apk包增大,而且好像官网也停止维护了。所以肯定是首先选择使用了x5。

提前预告使用感受

x5内核:各种坑,而且并没有像文档和传闻说的那样,可以共享使用微信和QQ内核,实测只有当安装了QQ浏览器的时候,才能共享使用,纵然安装了微信和QQ,它还是需要重新下载内核才能加载成功。有时候首次下载进行加载的过程奇慢无比,还存在失败的概率。经过一系列的修改,勉强算是达到了可以正常使用。

crosswalk内核:就在使用x5遇到坑一筹莫展的时候,想起来试试这个货。结果这货也不是个省油的灯...适配性不好,我在android8.1的项目上可以正常使用,虽然有些小毛病,但是还能修修改改。但是整到android10和11以上项目之后,直接就崩了,一加载库就崩。。。

简单说下我的x5加载方案:第一次的时候进行初始化,加载,注册监听加载结果。打开h5页面功能写成统一的静态全局方法,每次调用方法的时候判断是否加载成功,未加载成功x5的时候使用原生的WebViewActivity打开,加载成功后使用X5WebViewActivity打开。如果加载x5失败了呢,就在失败的回调里初始化sdk并且手动启动下载。

一,集成sdk包

下载:腾讯浏览服务-SDK下载 (tencent.com)

官方文档:腾讯浏览服务 (tencent.com)

//x5内核 tbs浏览服务
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>

混淆proguard.cfg

-dontwarn dalvik.**
-dontwarn com.tencent.smtt.**

-keep class com.tencent.smtt.** {
    *;
}

-keep class com.tencent.tbs.** {
    *;
}

二,加载和使用

在Application里直接加载,或者启动服务进行加载,实测效果没什么区别

2.1在Application里直接加载

class App : Application() {

 private var isLoadingFinished = false //加载x5结束
 private var isLoadingSuccess = false  //加载x5是否成功
 val isDebug = false

override fun onCreate() {
        super.onCreate()
            

        // 在调用TBS初始化、创建WebView之前进行如下配置
        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())//服务中进行加载

        initX5()//直接加载

}

fun initX5() {
        val cb: PreInitCallback = object : PreInitCallback {
            override fun onViewInitFinished(arg0: Boolean) {
                // TODO Auto-generated method stub
                //x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会        
                //自动切换到系统内核。
                Log.d("x5", " onViewInitFinished is $arg0")
                if (arg0) {
                    //加载成功时
                    isLoadingFinished = true
                    isLoadingSuccess = true
                    sendBroadcast(Intent("closemyloading"))
                    showDebug("加载h5内核成功")
                } else {
                    isLoadingFinished = true
                    isLoadingSuccess = false
                    sendBroadcast(Intent("closemyloading"))
                    showDebug("加载h5内核失败 重新下载")
                    reDownloadX5()//重新下载
                }
            }

            override fun onCoreInitFinished() {
                // TODO Auto-generated method stub
            }
        }
        //x5内核初始化接口
        QbSdk.initX5Environment(applicationContext, cb)
        QbSdk.setDownloadWithoutWifi(true)//设置没有wifi也下载x5内核

        QbSdk.setTbsListener(object : TbsListener {

            override fun onDownloadFinish(i: Int) {
                //tbs内核下载完成回调
                //但是只有i等于100才算完成,否则失败
                //此时大概率可能由于网络问题
                //如果失败可增加网络监听器
                showDebug("下载x5Core完成")
            }


            override fun onInstallFinish(i: Int) {
                //内核安装完成回调,通常到这里也算安装完成,但是在
                //极个别情况也会出现加载失败,比如笔者在公司内网下偶现,可以忽略
                showDebug("安装x5Core完成")
            }


            override fun onDownloadProgress(i: Int) {
                Log.d("x5", "progress: " + i)
                //下载进度监听
                sendBroadcast(Intent("updateProgress").putExtra("progress", i))
            }
        })

    }

    fun reDownloadX5(){
        // 在调用TBS初始化、创建WebView之前进行如下配置
        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)

        //判断是否要自行下载内核
        //false表示下载完成 或者 下载了一部分  true表示完全没下载
        val needDownload = TbsDownloader.needDownload(
            applicationContext,
            TbsDownloader.DOWNLOAD_OVERSEA_TBS
        )
        Log.d("X5", needDownload.toString() + "")
        //重置
        QbSdk.reset(applicationContext)
        // 启动下载
        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启动服务进行加载

放开Application里面的注释:

X5CorePreLoadService.enqueueWork(this, Intent())//服务中进行加载
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) {
        //在这里添加我们要执行的代码,Intent中可以保存我们所需的数据,
        //每一次通过Intent发送的命令将被顺序执行
        initX5()
        Log.d("x5", "initX5()")
    }

    /**
     * 初始化X5内核
     */
    private fun initX5() {
        QbSdk.setTbsListener(object : TbsListener {

            override fun onDownloadFinish(i: Int) {
                Log.d("x5", "onDownloadFinish: " + i)
                //tbs内核下载完成回调
                //但是只有i等于100才算完成,否则失败
                //此时大概率可能由于网络问题
                //如果失败可增加网络监听器

            }

            override fun onInstallFinish(i: Int) {
                Log.d("x5", "onInstallFinish: " + i)
                //内核安装完成回调,通常到这里也算安装完成,但是在
                //极个别情况也会出现加载失败,比如笔者在公司内网下偶现,可以忽略

            }

            override fun onDownloadProgress(i: Int) {
                Log.d("x5", "progress: " + i)
                //下载进度监听
                sendBroadcast(Intent("updateProgress").putExtra("progress", i))
            }
        })


        //x5内核初始化接口
        QbSdk.initX5Environment(applicationContext, cb)
        QbSdk.setDownloadWithoutWifi(true)//设置没有wifi也下载x5内核
    }

    val cb: PreInitCallback = object : PreInitCallback {
        override fun onViewInitFinished(arg0: Boolean) {
            // TODO Auto-generated method stub
            //x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。
            Log.d("x5", " onViewInitFinished is $arg0")
            if (arg0) {
                //加载成功时
                App.instance?.setIsX5LoadingFinished(true)
                App.instance?.setIsX5LoadingSuccess(true)
                sendBroadcast(Intent("closemyloading"))
                
            } else {
                App.instance?.setIsX5LoadingFinished(true)
                App.instance?.setIsX5LoadingSuccess(false)
                sendBroadcast(Intent("closemyloading"))
         
                // 在调用TBS初始化、创建WebView之前进行如下配置
                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)

                //判断是否要自行下载内核
                //false表示下载完成 或者 下载了一部分  true表示完全没下载
                val needDownload =
                    TbsDownloader.needDownload(mContext, TbsDownloader.DOWNLOAD_OVERSEA_TBS)
                Log.d("X5", needDownload.toString() + "")
                //重置
                QbSdk.reset(mContext)
                // 启动下载
                TbsDownloader.startDownload(mContext)
            }
        }

        override fun onCoreInitFinished() {

        }
    }
}

注册:

<service
            android:name=".app.X5CorePreLoadService"
            android:enabled="true"
            android:permission="android.permission.BIND_JOB_SERVICE"/>

 

全局打开方法:

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("首次启动正在加载h5内核,请稍后点击")
            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()
        //覆盖WebView默认使用第三方或系统默认浏览器打开网页的行为,使网页用WebView打开
        webView?.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
                // TODO Auto-generated method stub
                //返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
                view.loadUrl(url!!)
                return true
            }
        }
        webView?.webChromeClient = chromeClient

        webView?.setBackgroundColor(Color.argb(1, 0, 0, 0))
        //WebView加载web资源
        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

        //不显示webview缩放按钮
        mWebSettings.setSupportZoom(false)//支持缩放,默认为true。是下面这个API的前提。
        mWebSettings.builtInZoomControls = false// 设置内置的缩放控件。若为false,则该WebView不可缩放
        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 true
            } else {
                finish() //退出
            }
        }
        return super.onKeyDown(keyCode, event)
    }


    private var chromeClient = object: WebChromeClient(){


        override fun openFileChooser(
            uploadFile: ValueCallback<Uri>?,
            acceptType: String?,
            captureType: String?
        ) {
            //保存对应的valuecallback供选择后使用
            //通过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, "选择文件"), 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) {
                //给文件选择的ValueCallback设置onReceiveValue值
                mUploadMessage?.onReceiveValue(data?.data!!)
                mUploadMessage = null
            } else if (resultCode == RESULT_CANCELED) {
                //给文件选择的ValueCallback设置null值
                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" />

 

结语:

WebViewActivity是使用原生WebView加载url,这里就不进行赘述了。以上是主要核心代码了。还有关于网络断开,网络失败后重新下载之类的没有进行判断和优化,如果自己想更完善可以优化一下。目前测试的几个型号android10和android11的机器都能加载成功了。希望对大家有所帮助,代码篇幅有点长,有些多余的也没来得及裁剪,如有错误,请大家多多指教。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值