andriod集成x5内核
说明
因为手机自带的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<View>(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<String, Any>()
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<WebView>(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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
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">
<activity
android:name=".WebActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data android:name="com.tencent.smtt.multiprocess.NUM_PRIVILEGED_SERVICES" android:value="1" />
<service
android:name="com.tencent.smtt.services.ChildProcessService$Privileged0"
android:exported="false"
android:isolatedProcess="false"
android:process=":privileged_process0" />
</application>
</manifest>
/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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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">
<Button
android:id="@+id/public_btn"
android:text="初始化X5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ProgressBar
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"/>
</LinearLayout>
activity_web.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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">
<com.tencent.smtt.sdk.WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
预览

其它
以上我的代码都是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<WebView>(R.id.webview)
// 因为这样会被提前自动提前加载(则会启用系统自带的),必须在这里显式手动加载)
webView = WebView(this)
val container = findViewById<FrameLayout>(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<String, Any>()
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<TextView>(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
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
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">
<!-- WebView 将在 X5 初始化完成后动态添加 -->
</FrameLayout>
dialog_progress.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
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"/>
<TextView
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"/>
<ProgressBar
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"/>
<TextView
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"/>
</LinearLayout>

浙公网安备 33010602011771号