andriod集成x5内核

365bet投注在线 admin 2026-01-13 21:51:03

说明

因为手机自带的webview内核不统一,而且大都版本过低。

为了更好的体验,选择了x5。

虽然x5内置的chrome版本不是最新的,但是也是相当新了(截止目前为109)!

x5内核的集成方式分为两种,在线版本(也叫公网版)和离线版本!

不过后来又增加了自运营版本(方便部署到内网服务器等不方便访问外网的环境) 并把离线版改名为 自运营静态!

3者区别是

公网版:App在启动后,从腾讯服务器动态下载并共享X5内核,接入简单,APK体积小;内核自动更新,无需随App升级。

自运营静态内核版:启动快,无网络依赖;与App绑定将X5内核的.so库文件直接打包到APK中,体积大。

自运营动态内核版:将X5内核服务部署在自有或内网服务器上,完全可控,不依赖外网,而且安装包也小,启动后从私有服务器动态下载并共享X5内核。

这里我们讲的是公网版!

前置工作

注册、实名认真和创建APP,下载内核(因为是在线版,所以非常小)和 配置文件

上传apk的时候,记得使用v1签名,不支持v2和v3!

开始编码

创建项目名字随意 我这里叫 x5demo。

kotlin(java)代码

java/com/example/x5demo 目录有3个文件

MainActivity.kt

package com.example.x5demo

import android.content.Intent

import android.os.Bundle

import android.util.Log

import android.view.View

import androidx.appcompat.app.AppCompatActivity

import com.tencent.smtt.export.external.TbsCoreSettings

import com.tencent.smtt.sdk.QbSdk

import com.tencent.smtt.sdk.TbsFramework

import com.tencent.smtt.sdk.core.dynamicinstall.DynamicInstallManager

import java.io.File

import java.io.FileOutputStream

import java.io.IOException

import java.io.InputStream

import com.tencent.smtt.sdk.ProgressListener;

import android.widget.ProgressBar;

class MainActivity : AppCompatActivity() {

private val TAG = "MainActivity"

private lateinit var progressBar: ProgressBar

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

progressBar = findViewById(R.id.progress)

findViewById(R.id.public_btn).setOnClickListener {

initPublicTBS()

}

}

private fun saveInputStreamToFile(inputStream: InputStream, filePath: String): File? {

return try {

// 1. 创建目标文件对象 - 在应用的内部存储目录中

val file = File(applicationContext.filesDir, filePath)

// 最终路径类似:/data/data/你的包名/files/config/config_47405.tbs

// 2. 创建输出流准备写入

val out = FileOutputStream(file)

// 3. 创建缓冲区,提高复制效率

val buffer = ByteArray(1024) // 1KB 缓冲区

var bytesRead: Int

// 4. 循环读取输入流并写入输出流

while (inputStream.read(buffer).also { bytesRead = it } != -1) {

out.write(buffer, 0, bytesRead) // 将读取的数据写入文件

}

// 5. 关闭流,释放资源

out.close()

inputStream.close()

// 6. 记录成功信息

Log.e(TAG, "saveInputStreamToFile: ${file.path}")

file // 返回复制后的文件对象

} catch (e: IOException) {

Log.e(TAG, "出现异常: ${e.localizedMessage}")

null // 出错时返回 null

}

}

private fun getConfigFile(): File? {

return try {

val inputStream = assets.open(TBSEnv.CONFIG_PATH)

val inputFileName = TBSEnv.CONFIG_PATH.substringAfter("/")

saveInputStreamToFile(inputStream, inputFileName)

} catch (e: Exception) {

e.printStackTrace()

null

}

}

// 预初始化回调

private val preInitCallback = object : QbSdk.PreInitCallback {

override fun onCoreInitFinished() {

Log.e(TAG, "onCoreInitFinished: 初始化成功")

val intent = Intent(this@MainActivity, WebActivity::class.java)

startActivity(intent)

finish()

}

override fun onViewInitFinished(isX5Code: Boolean) {

Log.e(TAG, "是否使用X5内核: $isX5Code")

}

}

private fun downloadConfigTBS(configFile: File) {

// 3. 设置TBS框架

TbsFramework.setUp(this, configFile)

// 4. 动态安装管理

val manager = DynamicInstallManager(applicationContext)

manager.registerListener(object : ProgressListener {

override fun onProgress(progress: Int) {

Log.i(TAG, "downloadConfigTBS: $progress")

runOnUiThread {

progressBar.progress = progress

}

}

override fun onFinished() {

Log.i(TAG, "下载完成,开始预初始化")

QbSdk.preInit(this@MainActivity, preInitCallback)

}

override fun onFailed(code: Int, msg: String) {

Log.i(TAG, "onError: $code; msg: $msg")

}

})

manager.startInstall()

}

private fun initPublicTBS() {

// 1. 初始化TBS设置

val map = HashMap()

map[TbsCoreSettings.MULTI_PROCESS_ENABLE] = 1

QbSdk.initTbsSettings(map)

// 2. 获取配置文件

val configFile = getConfigFile()

if (configFile != null && configFile.exists()) {

Log.e(TAG, "拿到文件")

Log.e(TAG, "文件路径: ${configFile.absolutePath}")

downloadConfigTBS(configFile)

} else {

Log.e(TAG, "未拿到文件")

}

}

}

TBSEnv.kt

package com.example.x5demo

object TBSEnv {

const val CONFIG_PATH = "config.tbs"

const val LOAD_URL = "https://www.baidu.com"

}

WebActivity.kt

package com.example.x5demo

import android.content.DialogInterface

import android.os.Bundle

import android.util.Log

import android.widget.Toast

import androidx.appcompat.app.AlertDialog

import androidx.appcompat.app.AppCompatActivity

import com.tencent.smtt.export.external.interfaces.JsResult

import com.tencent.smtt.sdk.QbSdk

import com.tencent.smtt.sdk.WebChromeClient

import com.tencent.smtt.sdk.WebView

import com.tencent.smtt.sdk.WebViewClient

class WebActivity : AppCompatActivity() {

private var webView: WebView? = null

private val TAG = "WebActivity"

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_web)

webView = findViewById(R.id.webview)

webView?.let { web ->

val settings = web.settings

settings.javaScriptEnabled = true

settings.allowFileAccess = true

settings.setSupportZoom(true)

settings.databaseEnabled = true

settings.allowFileAccess = true

settings.domStorageEnabled = true

WebView.setWebContentsDebuggingEnabled(true)

web.loadUrl(TBSEnv.LOAD_URL)

val tbsVersion = QbSdk.getTbsVersion(this)

Log.e("webActivity", "QbSdk.getTbsVersion: $tbsVersion")

Toast.makeText(

this@WebActivity,

"内核版本:" + tbsVersion + web.isX5Core,

Toast.LENGTH_LONG

).show()

web.webChromeClient = object : WebChromeClient() {

override fun onJsAlert(

webView: WebView,

url: String,

message: String,

result: JsResult

): Boolean {

AlertDialog.Builder(this@WebActivity).setTitle("JS弹窗Override")

.setMessage(message)

.setPositiveButton(

"OK"

) { _: DialogInterface?, _: Int -> result.confirm() }

.setCancelable(false)

.show()

return true

}

}

web.webViewClient = object : WebViewClient() {

override fun shouldOverrideUrlLoading(webView: WebView, url: String): Boolean {

Log.e(TAG, "overrideUrlLoading: $url")

return !url.startsWith("http")

}

}

}

}

override fun onDestroy() {

webView?.destroy()

super.onDestroy()

}

}

配置文件

AndroidManifest.xml

xmlns:tools="http://schemas.android.com/tools">

android:allowBackup="true"

android:dataExtractionRules="@xml/data_extraction_rules"

android:fullBackupContent="@xml/backup_rules"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:roundIcon="@mipmap/ic_launcher_round"

android:supportsRtl="true"

android:theme="@style/Theme.X5demo"

tools:targetApi="31">

android:name=".WebActivity"

android:exported="false" />

android:name=".MainActivity"

android:exported="true">

android:name="com.tencent.smtt.services.ChildProcessService$Privileged0"

android:exported="false"

android:isolatedProcess="false"

android:process=":privileged_process0" />

/x5demo/app/build.gradle.kts

plugins {

alias(libs.plugins.android.application)

alias(libs.plugins.kotlin.android)

}

android {

namespace = "com.example.x5demo"

compileSdk = 36

defaultConfig {

applicationId = "com.example.x5demo"

minSdk = 24

targetSdk = 36

versionCode = 1

versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

}

// 增加这一段(填写自己的证书信息)

signingConfigs {

create("release") {

storeFile = file("wutong.jks")

storePassword = "123456789"

keyAlias = "cert"

keyPassword = "123456789"

enableV1Signing = true

}

getByName("debug") {

storeFile = file("wutong.jks")

storePassword = "123456789"

keyAlias = "cert"

keyPassword = "123456789"

enableV1Signing = true

}

}

// 增加这一段

buildTypes {

getByName("release") {

isMinifyEnabled = false

proguardFiles(

getDefaultProguardFile("proguard-android-optimize.txt"),

"proguard-rules.pro"

)

signingConfig = signingConfigs.getByName("release")

}

getByName("debug") {

signingConfig = signingConfigs.getByName("debug")

}

}

compileOptions {

sourceCompatibility = JavaVersion.VERSION_11

targetCompatibility = JavaVersion.VERSION_11

}

kotlinOptions {

jvmTarget = "11"

}

}

dependencies {

implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar")))) // 增加这一行

implementation(libs.androidx.core.ktx)

implementation(libs.androidx.appcompat)

implementation(libs.material)

implementation(libs.androidx.activity)

implementation(libs.androidx.constraintlayout)

testImplementation(libs.junit)

androidTestImplementation(libs.androidx.junit)

androidTestImplementation(libs.androidx.espresso.core)

}

x5demo/app/src/main/assets/config.tbs

将前置工作中下载的配置文件复制到此处!

xxx.jks证书文件

比如我这里是wutong.jsk,复制到 app 目录下!

布局文件

在x5demo/app/src/main/res/layout新增两个文件

activity_main.xml

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/main"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MainActivity"

android:orientation="vertical"

android:gravity="center">

android:id="@+id/public_btn"

android:text="初始化X5"

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

android:id="@+id/progress"

style="?android:attr/progressBarStyleHorizontal"

android:layout_width="200dp"

android:layout_height="30dp"

android:layout_gravity="center"

android:max="100"

android:layout_marginTop="40dp"/>

activity_web.xml

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/main"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".WebActivity">

android:id="@+id/webview"

android:layout_width="match_parent"

android:layout_height="match_parent"/>

预览

其它

以上我的代码都是kotlin,如果你是java,那正好官网提供了java版本的demo!

这里是公网版本的官方文档!

Android Kotlin 中页面跳转的标准方式

Android 页面跳转的基本方式

// 1. 创建 Intent(意图)对象

val intent = Intent(mainActivity, WebActivity::class.java)

// 2. 启动目标 Activity

mainActivity.startActivity(intent)

// 3. 关闭当前 Activity(可选)

mainActivity.finish()

Intent(意图)

val intent = Intent(mainActivity, WebActivity::class.java)

Intent 是 Android 中用于组件间通信的对象

第一个参数:mainActivity - 当前的上下文(Context)

第二个参数:WebActivity::class.java - 目标 Activity 的 Class 对象

::class.java 是 Kotlin 获取 Java Class 对象的语法

startActivity()

mainActivity.startActivity(intent)

启动目标 Activity

会打开 WebActivity 页面

finish()

mainActivity.finish()

关闭当前 Activity(MainActivity)

调用后,用户按返回键不会回到 MainActivity

如果不调用 finish(),返回键会回到 MainActivity

其他常见的跳转方式

带参数跳转

val intent = Intent(this, WebActivity::class.java)

intent.putExtra("url", "https://www.baidu.com")

intent.putExtra("title", "百度")

startActivity(intent)

简化写法

startActivity(Intent(this, WebActivity::class.java))

带动画跳转

val intent = Intent(this, WebActivity::class.java)

startActivity(intent)

overridePendingTransition(R.anim.slide_in, R.anim.slide_out)

获取返回结果(新方式)

val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->

if (result.resultCode == RESULT_OK) {

// 处理返回结果

}

}

优化版本

弹窗展示加载内核,加载完成自动打开页面。

MainActivity.kt

package com.example.x5demo

import android.content.DialogInterface

import android.os.Bundle

import android.util.Log

import android.widget.FrameLayout

import androidx.appcompat.app.AlertDialog

import androidx.appcompat.app.AppCompatActivity

import com.tencent.smtt.export.external.interfaces.JsResult

import com.tencent.smtt.sdk.QbSdk

import com.tencent.smtt.sdk.WebChromeClient

import com.tencent.smtt.sdk.WebView

import com.tencent.smtt.sdk.WebViewClient

class MainActivity : AppCompatActivity() {

private var webView: WebView? = null

private val TAG = "MainActivity"

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

// 初始化 X5 内核

TbsHelper(this, this@MainActivity) {

// 初始化成功后的回调,创建并加载 WebView

Log.e(TAG, "初始化成功后的回调,创建并加载 WebView")

initWebView()

}.initPublicTBS()

}

private fun initWebView() {

// X5 已经初始化完成后,动态创建 WebView

// (不能直接写到activity_main里,然后 webView = findViewById(R.id.webview)

// 因为这样会被提前自动提前加载(则会启用系统自带的),必须在这里显式手动加载)

webView = WebView(this)

val container = findViewById(R.id.webview_container)

container.addView(webView, FrameLayout.LayoutParams(

FrameLayout.LayoutParams.MATCH_PARENT,

FrameLayout.LayoutParams.MATCH_PARENT

))

// 设置webView

webView?.let { web ->

val settings = web.settings

settings.javaScriptEnabled = true

settings.allowFileAccess = true

settings.setSupportZoom(true)

settings.databaseEnabled = true

settings.domStorageEnabled = true

WebView.setWebContentsDebuggingEnabled(true)

val tbsVersion = QbSdk.getTbsVersion(this)

val isX5Core = web.isX5Core

Log.e(TAG, "=== X5内核状态 ===")

Log.e(TAG, "TBS版本: $tbsVersion")

Log.e(TAG, "是否X5内核: $isX5Core")

web.loadUrl("file:///android_asset/index.html")

web.webChromeClient = object : WebChromeClient() {

override fun onJsAlert(

webView: WebView,

url: String,

message: String,

result: JsResult

): Boolean {

AlertDialog.Builder(this@MainActivity)

.setTitle("JS弹窗")

.setMessage(message)

.setPositiveButton("OK") { _: DialogInterface?, _: Int ->

result.confirm()

}

.setCancelable(false)

.show()

return true

}

}

web.webViewClient = object : WebViewClient() {

override fun shouldOverrideUrlLoading(webView: WebView, url: String): Boolean {

Log.e(TAG, "overrideUrlLoading: $url")

return !url.startsWith("http")

}

}

}

}

override fun onDestroy() {

webView?.destroy()

super.onDestroy()

}

}

TbsHelper.kt

package com.example.x5demo

import android.content.Context

import android.util.Log

import android.view.LayoutInflater

import android.widget.ProgressBar

import android.widget.TextView

import android.widget.Toast

import androidx.appcompat.app.AlertDialog

import com.example.x5demo.TBSEnv.CONFIG_PATH

import com.tencent.smtt.export.external.TbsCoreSettings

import com.tencent.smtt.sdk.QbSdk

import com.tencent.smtt.sdk.TbsFramework

import com.tencent.smtt.sdk.core.dynamicinstall.DynamicInstallManager

import java.io.File

import java.io.FileOutputStream

import java.io.IOException

import java.io.InputStream

import com.tencent.smtt.sdk.ProgressListener

class TbsHelper (

private val applicationContext: Context,

private val mainActivity: MainActivity,

private val onInitSuccess: () -> Unit // 初始化成功的回调

) {

private val TAG = "MainActivity"

private var progressDialog: AlertDialog? = null

private var progressBar: ProgressBar? = null

private var progressText: TextView? = null

companion object {

private const val PREF_NAME = "tbs_config"

private const val KEY_FIRST_INSTALL = "first_install_done"

}

private fun saveInputStreamToFile(inputStream: InputStream, filePath: String): File? {

return try {

// 1. 创建目标文件对象 - 在应用的内部存储目录中

val file = File(applicationContext.filesDir, filePath)

// 最终路径类似:/data/data/你的包名/files/config/config_47405.tbs

// 2. 创建输出流准备写入

val out = FileOutputStream(file)

// 3. 创建缓冲区,提高复制效率

val buffer = ByteArray(1024) // 1KB 缓冲区

var bytesRead: Int

// 4. 循环读取输入流并写入输出流

while (inputStream.read(buffer).also { bytesRead = it } != -1) {

out.write(buffer, 0, bytesRead) // 将读取的数据写入文件

}

// 5. 关闭流,释放资源

out.close()

inputStream.close()

// 6. 记录成功信息

Log.e(TAG, "saveInputStreamToFile: ${file.path}")

file // 返回复制后的文件对象

} catch (e: IOException) {

Log.e(TAG, "出现异常: ${e.localizedMessage}")

null // 出错时返回 null

}

}

private fun getConfigFile(): File? {

return try {

val inputStream = applicationContext.assets.open(CONFIG_PATH)

val inputFileName = CONFIG_PATH.substringAfter("/")

saveInputStreamToFile(inputStream, inputFileName)

} catch (e: Exception) {

e.printStackTrace()

null

}

}

// 预初始化回调

private val preInitCallback = object : QbSdk.PreInitCallback {

override fun onCoreInitFinished() {

Log.e(TAG, "onCoreInitFinished: 初始化成功")

mainActivity.runOnUiThread {

progressDialog?.dismiss()

}

}

override fun onViewInitFinished(isX5Code: Boolean) {

Log.e(TAG, "是否使用X5内核: $isX5Code")

mainActivity.runOnUiThread {

val kernelType = if (isX5Code) "X5内核" else "系统WebView内核"

val message = "初始化完成\n使用: $kernelType"

Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()

// 调用初始化成功的回调

onInitSuccess()

}

}

}

fun initPublicTBS() {

// 1. 初始化TBS设置

val map = HashMap()

map[TbsCoreSettings.MULTI_PROCESS_ENABLE] = 1

QbSdk.initTbsSettings(map)

// 2. 获取配置文件

val configFile = getConfigFile()

// 3. 设置TBS框架

TbsFramework.setUp(applicationContext, configFile)

// 显示进度对话框

mainActivity.runOnUiThread {

val dialogView = LayoutInflater.from(mainActivity).inflate(R.layout.dialog_progress, null)

progressBar = dialogView.findViewById(R.id.progress_bar)

progressText = dialogView.findViewById(R.id.progress_percent)

progressDialog = AlertDialog.Builder(mainActivity)

.setView(dialogView)

.setCancelable(false)

.create()

progressDialog?.show()

}

// 4. 动态安装管理

val manager = DynamicInstallManager(applicationContext)

manager.registerListener(object : ProgressListener {

override fun onProgress(progress: Int) {

Log.i(TAG, "downloadConfigTBS: $progress")

mainActivity.runOnUiThread {

progressBar?.progress = progress

progressText?.text = "$progress%"

}

}

override fun onFinished() {

Log.i(TAG, "下载完成,开始预初始化")

mainActivity.runOnUiThread {

val messageText = progressDialog?.findViewById(R.id.progress_message)

messageText?.text = "初始化中..."

progressBar?.isIndeterminate = true

}

QbSdk.preInit(mainActivity, preInitCallback)

}

override fun onFailed(code: Int, msg: String) {

Log.e(TAG, "onError: $code; msg: $msg")

}

})

manager.startInstall()

}

}

activity_main.xml

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/webview_container"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MainActivity">

dialog_progress.xml

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical"

android:padding="24dp">

android:id="@+id/progress_title"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="正在加载X5内核"

android:textSize="18sp"

android:textStyle="bold"

android:textColor="@android:color/black"

android:layout_marginBottom="16dp"/>

android:id="@+id/progress_message"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="请稍候..."

android:textSize="14sp"

android:textColor="@android:color/darker_gray"

android:layout_marginBottom="16dp"/>

android:id="@+id/progress_bar"

style="?android:attr/progressBarStyleHorizontal"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:max="100"

android:progress="0"/>

android:id="@+id/progress_percent"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="0%"

android:textSize="12sp"

android:textColor="@android:color/darker_gray"

android:layout_gravity="end"

android:layout_marginTop="8dp"/>

相关文章

泡菜几天能吃最安全

为什么App里的内容分享不了

猫薄荷是什么?猫薄荷怎么种?(猫薄荷到底是啥玩意)

QQ闪照破解:Android版QQ闪照的持久化存储探索

贸易b2b是什么意思?一文详解B2B的含义及内容

电脑微信双开怎么操作?6种方法多账号同时登录(附防封号技巧)

从中国大山深处走到世界杯!央媒关注贵州这名足球少年

女团选秀,全员非人

解决手机闪退问题的终极指南:如何让你的手机更流畅