Android WebView(腾讯TBS)

集成TBS

自带的webview使用起来很多网页不兼容,有很多坑,这里推荐使用TBS,也就是X5内核。

https://x5.tencent.com/tbs/sdk.html

接入文档:

https://x5.tencent.com/docs/access.html

SDK接入

jar包方式集成 您可将官网下载的jar包复制到您的App的libs目录,并且通过Add As Library的方式集成TBS SDK

Gradle方式集成 您可以在使用SDK的模块的dependencies中添加引用进行集成:

1
api 'com.tencent.tbs:tbssdk:44286'

kts

1
api("com.tencent.tbs:tbssdk:44286")

使用到的网址 如果设备有防火墙 需要把下面的地址加入白名单,否则无法下载X5内核。

soft.imtt.qq.com

wss.tim.qq.com

log.tbs.qq.com

cfg.imtt.qq.com

tbs.imtt.qq.com

cdntips.net

tcdnos.com

TBS下载失败

TBS是分发的,也就是你调用下载,也不一定下载,这就导致内核安装变得让人难以捉摸。

这里建议下载成功的时候,不要重启找到下载的内核复制出来,千万不要重启,重启自动安装就会把下载的删除。

下载目录

假如我的APP的包名是com.xhkjedu.xh_control_browser

下载成功后会在下面的目录中产生一个x5.tbs文件

1
/data/data/com.xhkjedu.xh_control_browser/app_tbs_64/core_private

本地安装

1
2
3
4
5
6
7
8
9
val canLoadX5 = QbSdk.canLoadX5(context)
Log.i(TAG, "canLoadX5:$canLoadX5")
if (!canLoadX5) {
val appSavePath = getAppSavePath(context, "x5.tbs")
Log.i(TAG, "initTBS: appSavePath:$appSavePath")
copyFilesFassets(context, "x5.tbs", appSavePath)
QbSdk.reset(context);
QbSdk.installLocalTbsCore(context, 46281, appSavePath);
}

引用的

1
api("com.tencent.tbs:tbssdk:44286")

实际下载的是46281版本的x5.tbs

百度网盘下载x5.tbs

链接:https://pan.baidu.com/s/1Ly-ZQxv2nLtFG4txDic0Wg
提取码:psvm

混淆配置

为了保障X5功能的正常使用,您需要在您的proguard-rules.pro文件中添加如下配置:

1
2
3
4
5
6
7
8
9
10
-dontwarn dalvik.**
-dontwarn com.tencent.smtt.**

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

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

添加权限和服务

在您的AndroidManifest.xml增加如下权限:

1
2
3
4
5
6
7
<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" />
<!-- 硬件加速对X5视频播放有利,建议开启 -->
<uses-permission android:name="android.permission.GET_TASKS" />

在AndroidManifest.xml中增加内核首次加载时优化Service声明。

该Service仅在TBS内核首次Dex加载时触发并执行dex2oat任务,任务完成后自动结束。

1
2
3
4
5
<service
android:name="com.tencent.smtt.export.external.DexClassLoaderProviderService"
android:label="dexopt"
android:process=":dexopt" >
</service>

安装TBS内核

官方下载

Application的onCreate中进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import android.content.Context
import android.util.Log
import com.tencent.smtt.export.external.TbsCoreSettings
import com.tencent.smtt.sdk.QbSdk
import com.tencent.smtt.sdk.TbsDownloader
import com.tencent.smtt.sdk.TbsListener

object TBSUtils {
var isUseX5 = false
fun initTBS(conntext: Context) {
QbSdk.setDownloadWithoutWifi(true)
val map = HashMap<String, Any>()
map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true
map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true
map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true
QbSdk.initTbsSettings(map)
QbSdk.setTbsListener(
object : TbsListener {
override fun onDownloadProgress(progress: Int) {
Log.i("TBS_X5", "onDownloadProgress -->下载X5内核进度:$progress")
}

override fun onDownloadFinish(progress: Int) {
Log.i("TBS_X5", "onDownloadFinish -->下载X5内核完成:$progress")
}

override fun onInstallFinish(progress: Int) {
Log.i("TBS_X5", "onInstallFinish -->安装X5内核完成")
msgCallback?.let {
it(true, "安装内核完成请重启应用")
}

GlobalScope.launch {
delay(10000)
withContext(Dispatchers.Main) {
restartApp(activity)
}
}
}
}
)


QbSdk.initX5Environment(conntext, object : QbSdk.PreInitCallback {
override fun onCoreInitFinished() {
// 内核初始化完成,可能为X5内核,也可能为系统内核
Log.i("TBS_X5", "内核初始化完成")
}

/**
* 预初始化结束
* 由于X5内核体积较大,需要依赖网络动态下发,所以当内核不存在的时候,默认会回调false,此时将会使用系统内核代替
* @param isX5 是否使用X5内核
*/
override fun onViewInitFinished(isX5: Boolean) {
Log.i("TBS_X5", "isX5: $isX5")
isUseX5 = isX5
}
})
}

fun restartApp(activity: Activity) {
// 获取当前应用程序的包名
val packageName: String = activity.getPackageName()

// 创建一个 Intent 对象,用于重新启动应用程序
val intent: Intent? = activity.getBaseContext().getPackageManager().getLaunchIntentForPackage(packageName)
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// 重新启动应用程序
intent?.let { activity.startActivity(intent) }

// 关闭当前的 Activity
activity.finish()
}
}

注意

不要在onViewInitFinished中自己调用下载,会和本身的下载冲突,导致下载失败。

1
2
3
4
5
6
7
override fun onViewInitFinished(isX5: Boolean) {
Log.i("TBS_X5", "isX5: $isX5")
isUseX5 = isX5
if (!isX5) {
TbsDownloader.startDownload(conntext);
}
}

使用本地文件

src/main下添加assets文件夹

文件夹中放入x5.tbs文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import android.content.Context
import android.os.Environment
import android.util.Log
import com.tencent.smtt.export.external.TbsCoreSettings
import com.tencent.smtt.sdk.QbSdk
import com.tencent.smtt.sdk.TbsListener
import kotlinx.coroutines.DelicateCoroutinesApi
import java.io.File
import java.io.FileOutputStream


object TBSUtils {
var isUseX5 = false
var TAG = "TBS_X5"

var msgCallback: ((show: Boolean, msg: String) -> Unit)? = null
@OptIn(DelicateCoroutinesApi::class)
fun initTBS(context: Context) {
val canLoadX5 = QbSdk.canLoadX5(context)
Log.i(TAG, "canLoadX5:$canLoadX5")
if (!canLoadX5) {
val appSavePath = getAppSavePath(context, "x5.tbs")
Log.i(TAG, "initTBS: appSavePath:$appSavePath")
copyFilesFassets(context, "x5.tbs", appSavePath)
QbSdk.reset(context);
QbSdk.installLocalTbsCore(context, 46281, appSavePath);
}
QbSdk.setDownloadWithoutWifi(true)
val map = HashMap<String, Any>()
map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true
map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true
map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true
QbSdk.initTbsSettings(map)
QbSdk.setTbsListener(
object : TbsListener {
override fun onDownloadProgress(progress: Int) {
Log.i("TBS_X5", "onDownloadProgress -->下载X5内核进度:$progress")
msgCallback?.let {
it(true, "下载内核进度:$progress%")
}
}

override fun onDownloadFinish(progress: Int) {
Log.i("TBS_X5", "onDownloadFinish -->下载X5内核完成:$progress")
if (progress == 100) {
msgCallback?.let {
it(true, "下载内核成功")
}
}
}

override fun onInstallFinish(progress: Int) {
Log.i("TBS_X5", "onInstallFinish -->安装X5内核完成")
msgCallback?.let {
it(true, "安装极速内核完成")
}
}
}
)


QbSdk.initX5Environment(context, object : QbSdk.PreInitCallback {
override fun onCoreInitFinished() {
// 内核初始化完成,可能为X5内核,也可能为系统内核
Log.i("TBS_X5", "内核初始化完成")
}

/**
* 预初始化结束
* 由于X5内核体积较大,需要依赖网络动态下发,所以当内核不存在的时候,默认会回调false,此时将会使用系统内核代替
* @param isX5 是否使用X5内核
*/
override fun onViewInitFinished(isX5: Boolean) {
Log.i("TBS_X5", "isX5: $isX5")
isUseX5 = isX5
msgCallback?.let {
it(false, "")
}
}
})
}

private fun copyFilesFassets(context: Context, oldPath: String, newPath: String) {
try {
val fileNames = context.assets.list(oldPath) //获取assets目录下的所有文件及目录名
if (fileNames!!.isNotEmpty()) { //如果是目录
val file = File(newPath)
file.mkdirs() //如果文件夹不存在,则递归
for (fileName in fileNames) {
copyFilesFassets(context, "$oldPath/$fileName", "$newPath/$fileName")
}
} else { //如果是文件
val inputStream = context.assets.open(oldPath)
val fos = FileOutputStream(File(newPath))
val buffer = ByteArray(1024)
var byteCount: Int
while (inputStream.read(buffer).also { byteCount = it } != -1) { //循环从输入流读取 buffer字节
fos.write(buffer, 0, byteCount) //将读取的输入流写入到输出流
}
fos.flush() //刷新缓冲区
inputStream.close()
fos.close()
}
} catch (e: Exception) {
e.printStackTrace()
}
}

private fun getFilePath(context: Context, dir: String): String {
//判断外部存储是否可用
var directoryPath: String = if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
context.getExternalFilesDir(dir)!!.absolutePath
} else { //没外部存储就使用内部存储
context.filesDir.toString() + File.separator + dir
}
val file = File(directoryPath)
if (!file.exists()) { //判断文件目录是否存在
file.mkdirs()
}
return directoryPath
}

private fun getAppSavePath(context: Context, appname: String): String {
val folderName = getFilePath(context, "app")
return folderName + File.separator + appname
}
}

其中复制的目标位置是

1
/sdcard/Android/data/com.xhkjedu.xh_control_browser/files/app/x5.tbs

使用自己的下载地址

默认使用官方下载,失败再使用自己的镜像。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Environment
import android.util.Log
import com.billbook.lib.downloader.Download
import com.billbook.lib.downloader.Downloader
import com.tencent.smtt.export.external.TbsCoreSettings
import com.tencent.smtt.sdk.QbSdk
import com.tencent.smtt.sdk.TbsListener
import kotlinx.coroutines.DelicateCoroutinesApi
import java.io.File


object TBSUtils {
var isUseX5 = false
var TAG = "TBS_X5"
var tbsDownloadUrl = "https://xxx.oss-cn-huhehaote.aliyuncs.com/appmanager/tbscore/1/1/x5.tbs.apk"
var msgCallback: ((show: Boolean, msg: String) -> Unit)? = null

@OptIn(DelicateCoroutinesApi::class)
fun initTBS(context: Context) {
QbSdk.setDownloadWithoutWifi(true)
QbSdk.setOnlyDownload(false)
val map = HashMap<String, Any>()
map[TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER] = true
map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true
map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true
QbSdk.initTbsSettings(map)
QbSdk.setTbsListener(
object : TbsListener {
override fun onDownloadProgress(progress: Int) {
Log.i("TBS_X5", "onDownloadProgress -->下载X5内核进度:$progress")
msgCallback?.let {
it(true, "下载内核进度:$progress%")
}
}

override fun onDownloadFinish(progress: Int) {
Log.i("TBS_X5", "onDownloadFinish -->下载X5内核完成:$progress")
if (progress == 100) {
msgCallback?.let {
it(true, "下载内核成功")
}
}
}

override fun onInstallFinish(progress: Int) {
Log.i("TBS_X5", "onInstallFinish -->安装X5内核完成")
msgCallback?.let {
it(true, "安装极速内核完成")
reStartApp(context)
}
}
}
)


QbSdk.initX5Environment(context, object : QbSdk.PreInitCallback {
override fun onCoreInitFinished() {
// 内核初始化完成,可能为X5内核,也可能为系统内核
Log.i("TBS_X5", "内核初始化完成")
}

/**
* 预初始化结束
* 由于X5内核体积较大,需要依赖网络动态下发,所以当内核不存在的时候,默认会回调false,此时将会使用系统内核代替
* @param isX5 是否使用X5内核
*/
override fun onViewInitFinished(isX5: Boolean) {
Log.i("TBS_X5", "isX5: $isX5")
isUseX5 = isX5
msgCallback?.let {
it(false, "")
}
if(!isX5){
downloadTbsCore(context)
}
}
})
}

private fun downloadTbsCore(context: Context) {
val appSavePath = getAppSavePath(context, "x5.tbs")
val downloader = Downloader.Builder().build()
val request = Download.Request.Builder()
.url(tbsDownloadUrl)
.into(appSavePath) // or into(file)
.build()
val call = downloader.newCall(request)
call.enqueue(object : Download.Callback {
override fun onStart(call: Download.Call) {

}

override fun onSuccess(call: Download.Call, response: Download.Response) {
val canLoadX5 = QbSdk.canLoadX5(context)
Log.i(TAG, "canLoadX5:$canLoadX5")
if (!canLoadX5) {
QbSdk.reset(context);
QbSdk.installLocalTbsCore(context, 46281, appSavePath);
}
}

override fun onFailure(call: Download.Call, response: Download.Response) {

}

override fun onLoading(call: Download.Call, current: Long, total: Long) {
val progress = current * 100 / total
Log.i(TAG, "镜像下载 -->下载X5内核完成:$progress")
msgCallback?.let {
it(true, "下载内核进度:${progress}%")
}
}
})
}

private fun getFilePath(context: Context, dir: String): String {
//判断外部存储是否可用
val directoryPath: String = if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
context.getExternalFilesDir(dir)!!.absolutePath
} else { //没外部存储就使用内部存储
context.filesDir.toString() + File.separator + dir
}
val file = File(directoryPath)
if (!file.exists()) { //判断文件目录是否存在
file.mkdirs()
}
return directoryPath
}

private fun getAppSavePath(context: Context, appname: String): String {
val folderName = getFilePath(context, "app")
return folderName + File.separator + appname
}

fun reStartApp(context: Context){
val intent: Intent? = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName())
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager?
alarmManager!![AlarmManager.RTC, System.currentTimeMillis() + 1000] = pendingIntent
System.exit(0)
}
}

重启APP

安装内核后重启才生效

1
2
3
4
5
6
7
fun reStartApp(context: Context){
val intent: Intent? = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName())
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager?
alarmManager!![AlarmManager.RTC, System.currentTimeMillis() + 1000] = pendingIntent
System.exit(0)
}

方式2

1
2
3
4
5
6
7
8
fun reStartApp2(){
try {
Runtime.getRuntime().exec("am start -n com.xhkjedu.xh_control_browser/.MainActivity")
Process.killProcess(Process.myPid())
} catch (e: IOException) {
e.printStackTrace()
}
}

退出到主Launcher

我们应用的Activity都退出后,应用本身不一定退出,这是Android本身方便下次快速启动导致的。

但是TBS有时必须重启应用才能生效,但是直接退出应用,在管控平板上没有退出到上一个应用中,所以这里退出到主Launcher,再结束本应用。

1
2
3
4
5
6
7
8
9
override fun onBackPressed() {
super.onBackPressed()
if(!TBSUtils.isUseX5){
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_HOME)
startActivity(intent)
System.exit(0)
}
}

测试是否生效

在没有自定义UA的情况下,使用您的app打开网页

1
http://soft.imtt.qq.com/browser/tes/feedback.html

显示000000表示加载的是系统内核,显示大于零的数字表示加载了x5内核(该数字是x5内核版本号)

替换

包名替换

将源码和XML里的系统包和类替换为SDK里的包和类,具体对应如下:

系统内核 SDK内核
android.webkit.ConsoleMessage com.tencent.smtt.export.external.interfaces.ConsoleMessage
android.webkit.CacheManager com.tencent.smtt.sdk.CacheManager(deprecated)
android.webkit.CookieManager com.tencent.smtt.sdk.CookieManager
android.webkit.CookieSyncManager com.tencent.smtt.sdk.CookieSyncManager
android.webkit.CustomViewCallback com.tencent.smtt.export.external.interfaces.IX5WebChromeClient.CustomViewCallback
android.webkit.DownloadListener com.tencent.smtt.sdk.DownloadListener
android.webkit.GeolocationPermissions com.tencent.smtt.export.external.interfaces.GeolocationPermissionsCallback
android.webkit.HttpAuthHandler com.tencent.smtt.export.external.interfaces.HttpAuthHandler
android.webkit.JsPromptResult com.tencent.smtt.export.external.interfaces.JsPromptResult
android.webkit.JsResult com.tencent.smtt.export.external.interfaces.JsResult
android.webkit.SslErrorHandler com.tencent.smtt.export.external.interfaces.SslErrorHandler
android.webkit.ValueCallback com.tencent.smtt.sdk.ValueCallback
android.webkit.WebBackForwardList com.tencent.smtt.sdk.WebBackForwardList
android.webkit.WebChromeClient com.tencent.smtt.sdk.WebChromeClient
android.webkit.WebHistoryItem com.tencent.smtt.sdk.WebHistoryItem
android.webkit.WebIconDatabase com.tencent.smtt.sdk.WebIconDatabase
android.webkit.WebResourceResponse com.tencent.smtt.export.external.interfaces.WebResourceResponse
android.webkit.WebSettings com.tencent.smtt.sdk.WebSettings
android.webkit.WebSettings.LayoutAlgorithm com.tencent.smtt.sdk.WebSettings.LayoutAlgorithm
android.webkit.WebStorage com.tencent.smtt.sdk.WebStorage
android.webkit.WebView com.tencent.smtt.sdk.WebView
android.webkit.WebViewClient com.tencent.smtt.sdk.WebViewClient

XML替换

布局xml里的声明也需要替换,例如:

1
2
3
4
5
6
<com.tencent.smtt.sdk.WebView
android:id="@+id/mWebView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="5dp"
android:paddingRight="5dp" />

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@SuppressLint("SetJavaScriptEnabled")
private fun loadUrl(url: String) {
val webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setUseWideViewPort(true);//关键点
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
webSettings.setDisplayZoomControls(false);
webSettings.setJavaScriptEnabled(true); // 设置支持javascript脚本
webSettings.setAllowFileAccess(true); // 允许访问文件
webSettings.setBuiltInZoomControls(true); // 设置显示缩放按钮
webSettings.setSupportZoom(true); // 支持缩放
webSettings.setLoadWithOverviewMode(true);
webSettings.setMixedContentMode(WebSettings.LOAD_NORMAL);
// webSettings.setUserAgentString("Microsoft Edge");
webSettings.setDomStorageEnabled(true);//网页视频播放器才能正常加载
webSettings.setGeolocationEnabled(true);
webSettings.setAppCacheMaxSize(Long.MAX_VALUE);
webSettings.setPluginState(WebSettings.PluginState.ON_DEMAND);
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);

try {
mWebView.getSettingsExtension().setContentCacheEnable(true);
} catch (e: Exception) {

}
val jsstr = AndroidFileUtil.getFromAssets(this, "m_inject.js")
mWebView.setWebChromeClient(object : WebChromeClient() {
override fun onProgressChanged(p0: WebView?, p1: Int) {
super.onProgressChanged(p0, p1)
Log.e(TAG, "页面加载" + p1)
mWebView.evaluateJavascript(jsstr, object : ValueCallback<String> {
override fun onReceiveValue(p0: String?) {
p0?.let { Log.e(TAG, it) }
}
})
}
});

//mWebView.setWebViewClient(WebViewClient())

mWebView.setWebViewClient(object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
view?.loadUrl(request?.url.toString())
return true
}
});

mWebView.loadUrl(url)
}

override fun onDestroy() {
if (mWebView != null) {
mWebView.destroy()
}
super.onDestroy()
}

override fun onBackPressed() {
if (mWebView.canGoBack()) {
mWebView.goBack();
} else {
super.onBackPressed()
}
}

JS注入

src/main下添加assets文件夹

添加m_inject.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var dom = document.getElementById("menu-btn");
if (dom) {
dom.style.display = "none";
}

var header = document.getElementsByClassName("top-menu-basic");
if (header) {
header[0].style.width = "100%";
}

var back = document.getElementsByClassName("top3-back");
if (back) {
var back_item = back[0];
back_item.onclick = function (event) {
history.back(-1);
event.preventDefault();
};
}

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import android.content.Context;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class AndroidFileUtil {

/**
* 从Raw文件中读取
*
* @param mContext
* @param id
* @return
*/
public static String getFromRaw(Context mContext, int id) {
try {
InputStreamReader inputReader = new InputStreamReader(mContext.getResources().openRawResource(id));
BufferedReader bufReader = new BufferedReader(inputReader);
String line = "";
StringBuilder Result = new StringBuilder();
while ((line = bufReader.readLine()) != null)
Result.append(line);
return Result.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}

/**
* 从assets读取
*
* @param mContext
* @param fileName
* @return
*/
public static String getFromAssets(Context mContext, String fileName) {
try {
InputStreamReader inputReader = new InputStreamReader(mContext.getResources().getAssets().open(fileName));
BufferedReader bufReader = new BufferedReader(inputReader);
String line = "";
StringBuilder Result = new StringBuilder();
while ((line = bufReader.readLine()) != null)
Result.append(line);
return Result.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}

调用

1
2
3
4
5
6
val jsstr = AndroidFileUtil.getFromAssets(this, "m_inject.js")
mWebView.evaluateJavascript(jsstr, object : ValueCallback<String> {
override fun onReceiveValue(p0: String?) {
p0?.let { Log.e(TAG, it) }
}
})

我们可以在下面的回调中调用注入方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mWebView.setWebChromeClient(object : WebChromeClient() {
override fun onProgressChanged(p0: WebView?, p1: Int) {
super.onProgressChanged(p0, p1)
Log.e(TAG, "页面加载" + p1)
if(p1 == 100){

}
}
});


mWebView.setWebViewClient(object : WebViewClient() {
override fun onPageFinished(webView: WebView?, s: String?) {
Log.e(TAG, "页面加载完成")
super.onPageFinished(webView, s)

}
})

返回上一页

1
2
3
4
5
6
7
override fun onBackPressed() {
if (mWebView.canGoBack()){
mWebView.goBack();
}else{
super.onBackPressed()
}
}

申请权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private val REQUEST_EXTERNAL_STORAGE = 1
private val PERMISSIONS_STORAGE = arrayOf(
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_PHONE_STATE"
)


fun verifyStoragePermissions(activity: Activity?) {
try {
//检测是否有写的权限
val permission = ActivityCompat.checkSelfPermission(
activity!!,
"android.permission.WRITE_EXTERNAL_STORAGE"
)
if (permission != PackageManager.PERMISSION_GRANTED) {
// 没有写的权限,去申请写的权限,会弹出对话框
ActivityCompat.requestPermissions(
activity,
PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE
)
}
} catch (e: Exception) {
e.printStackTrace()
}
}