Android WebView(TBS)

加载本地

加载HTML文本

加载网页数据和本地文件合并后显示

src=>main=>assets目录下创建news_top.htmlnews_bottom.html

news_top.html

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
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<style>
body{
font-size: 16px;
line-height:200% !important;
letter-spacing:2px !important;
text-align: start;
word-wrap:break-word !important;
}

img{
max-width: 100% !important;
height: auto;
border-radius: 4px;
}

p{
padding: 0 !important;
}
</style>
</head>

<body>

<script type='text/javascript'>
window.onload = function()
{
var news_imgs = document.getElementsByTagName('img');
for(var i in news_imgs){
news_img[i].src = news_img[i].src+"!newinfo";
}
}
</script>

news_bottom.html

1
2
</body>
</html>

XML

1
2
3
4
5
6
7
8
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_weight="1"
android:background="@color/zj_gay_f9" />

Activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun initWebView(html: String) {
val webSettings = webView.getSettings();
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setDefaultTextEncodingName("UTF -8");//设置默认为utf-8
// 设置可以访问文件
webSettings.setAllowFileAccess(true);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowContentAccess(true);
webSettings.setDomStorageEnabled(true);
webView.getSettings().setAllowFileAccessFromFileURLs(true);
val tophtml = ZJFileUtils.ReadTxtFile(mContext, "news_top.html")
val bottomhtml = ZJFileUtils.ReadTxtFile(mContext, "news_bottom.html")
val html_all = tophtml + html + bottomhtml
webView.loadData(html_all, "text/html; charset=UTF-8", null)
}

中文乱码

使用 loadData方法官方推荐的写法在部分手机上会中文乱码,即使指定utf-8gbkgb2312也一样。

1
webView.loadData(data, "text/html", "UTF -8");

解决方法

1
2
webView.getSettings().setDefaultTextEncodingName("UTF -8");//设置默认为utf-8
webView.loadData(data, "text/html; charset=UTF-8", null);//这种写法可以正确解码

官方真是坑啊!!!

Utils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ZJFileUtils {
//读取文本文件中的内容
public static String ReadTxtFile(Context mContext, String filename) {
StringBuilder sb = new StringBuilder();
//打开文件
try {
InputStream instream = mContext.getAssets().open(filename);
InputStreamReader inputreader = new InputStreamReader(instream);
BufferedReader buffreader = new BufferedReader(inputreader);
String line;
//分行读取
while ((line = buffreader.readLine()) != null) {
sb.append(line + "\n");
}
instream.close();
} catch (java.io.FileNotFoundException e) {
Log.d("TestFile", "The File doesn't not exist.");
} catch (IOException e) {
Log.d("TestFile", e.getMessage());
}
return sb.toString();
}
}

加载本地HTML文件

xieyi.html 放在 src=>main=>assets目录下

1
2
3
4
5
6
7
8
9
10
11
fun initWebView() {
var webSettings = webView.getSettings();
webView.getSettings().setJavaScriptEnabled(false);
// 设置可以访问文件
webSettings.setAllowFileAccess(true);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowContentAccess(true);
webSettings.setDomStorageEnabled(true);
webView.getSettings().setAllowFileAccessFromFileURLs(true);
webView.loadUrl("file:///android_asset/xieyi.html");
}

加载URL

权限

application同级添加

1
<uses-permission android:name="android.permission.INTERNET"/>

application属性添加下面代码来允许http请求

1
android:usesCleartextTraffic="true"

模板

1
2
3
4
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

加载网址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SuppressLint("SetJavaScriptEnabled")
private fun loadUrl(url: String) {
val webSettings = webView.getSettings();
webSettings.javaScriptEnabled = true;//启用js
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);//解决视频无法播放问题
webView.setWebChromeClient(WebChromeClient());
webView.setWebViewClient(WebViewClient());
webView.loadUrl(url);//覆盖WebView默认使用第三方或系统默认浏览器打开网页的行为,使网页用WebView打开
webView.setWebViewClient(object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
view?.loadUrl(request?.url.toString())
return true
}
});
}

原生WebView

页面

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
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.LinearLayoutCompat 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activity.MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="@color/purple_700">
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@color/purple_700"
android:gravity="center_vertical">

<RelativeLayout
android:id="@+id/back_btn"
android:layout_width="44dp"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:clickable="true"
android:focusable="true">

<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_centerInParent="true"
android:src="@mipmap/iv_back_withe" />
</RelativeLayout>

<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="国家中小学网络云平台"
android:textColor="@color/white"
android:layout_centerHorizontal="true"
android:gravity="center_vertical"
android:textSize="18sp" />

<android.support.v7.widget.LinearLayoutCompat
android:layout_width="wrap_content"
android:layout_alignParentEnd="true"
android:layout_height="match_parent"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/home_btn"
android:layout_width="64dp"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true">

<TextView
android:text="首页"
android:textSize="16sp"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/refresh_btn"
android:layout_width="64dp"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true">

<TextView
android:text="刷新"
android:textSize="16sp"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>
</RelativeLayout>
</android.support.v7.widget.LinearLayoutCompat>
</RelativeLayout>
<WebView
android:id="@+id/mWebView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>

</android.support.v7.widget.LinearLayoutCompat>

Activity

引用

1
2
3
4
5
6
7
8
9
import android.webkit.*

import android.view.KeyEvent

import android.app.ProgressDialog
import android.content.DialogInterface

import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread

代码

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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ProgressDialog
import android.content.DialogInterface
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.widget.Toast
import com.xhkjedu.eduyun_android.R
import com.xhkjedu.eduyun_android.utils.AndroidFileUtil
import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread

import android.webkit.*


class MainActivity : AppCompatActivity() {
var TAG = "MainActivity"

var mUrl = "https://h5.zxx.edu.cn/syncClassroom"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
verifyStoragePermissions(this)
loadUrl(mUrl);
back_btn.setOnClickListener {
onBackPressed()
}

refresh_btn.setOnClickListener {
mWebView.reload()
}

home_btn.setOnClickListener {
loadUrl(mUrl)
mWebView.postDelayed(
Runnable()
{
mWebView.clearHistory()
}, 1000
);
}
}

@SuppressLint("SetJavaScriptEnabled")
private fun loadUrl(url: String) {
val webSettings = mWebView.settings;
webSettings.javaScriptEnabled = true;
webSettings.javaScriptCanOpenWindowsAutomatically = true;
webSettings.useWideViewPort = true;//关键点
webSettings.layoutAlgorithm = WebSettings.LayoutAlgorithm.NORMAL;
webSettings.displayZoomControls = false;
webSettings.allowFileAccess = true; // 允许访问文件

webSettings.builtInZoomControls = false; // 设置显示缩放按钮
webSettings.setSupportZoom(false); // 支持缩放
webSettings.loadWithOverviewMode = true;
webSettings.mixedContentMode = WebSettings.LOAD_NORMAL;
webSettings.userAgentString = "Microsoft Edge";
webSettings.domStorageEnabled = true;//网页视频播放器才能正常加载
webSettings.setGeolocationEnabled(true);
webSettings.setAppCacheMaxSize(Long.MAX_VALUE);
webSettings.setPluginState(WebSettings.PluginState.ON_DEMAND);
webSettings.cacheMode = WebSettings.LOAD_DEFAULT;

val jsstr = AndroidFileUtil.getFromAssets(this, "m_inject.js")

mWebView.webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(p0: WebView?, p1: Int) {
super.onProgressChanged(p0, p1)
if (p1 == 100) {
mWebView.evaluateJavascript(jsstr, object : ValueCallback<String> {
override fun onReceiveValue(p0: String?) {
p0?.let {
doAsync {
Thread.sleep(300)
uiThread {
mWebView.visibility = View.VISIBLE
hideProgress()
}
}
}
}
})
}
}
};

mWebView.webViewClient = object : WebViewClient() {
override fun onPageStarted(p0: WebView?, p1: String?, p2: Bitmap?) {
super.onPageStarted(p0, p1, p2)
Log.e(TAG, "onPageStarted")
showProgress()
mWebView.visibility = View.INVISIBLE;
}

override fun onPageFinished(p0: WebView?, p1: String?) {
super.onPageFinished(p0, p1)
Log.e(TAG, "onPageFinished")
}
};

mWebView.loadUrl(url)
}

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

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()
}
}

private var progressDialog: ProgressDialog? = null

fun showProgress() {
if (progressDialog == null) {
progressDialog = ProgressDialog.show(this@MainActivity, "", "页面加载中")
progressDialog?.setOnKeyListener(object : DialogInterface.OnKeyListener {
override fun onKey(dialog: DialogInterface?, keyCode: Int, p2: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
dialog?.cancel();
return true;
}
return false;
}
})
}
}

fun hideProgress() {
progressDialog?.dismiss()
progressDialog = null
}

private var exitTime: Long = 0
override fun onBackPressed() {
if (mWebView.canGoBack()) {
mWebView.goBack();
} else {
if ((System.currentTimeMillis() - exitTime) > 1200) {
Toast
.makeText(
applicationContext,
"再按一次退出程序",
Toast.LENGTH_SHORT
)
.show();
exitTime = System.currentTimeMillis();
} else {
finish();
System.exit(0);
}
}
}
}

注意几点

拦截弹窗

1
2
3
4
5
6
7
8
9
mWebView.webChromeClient = object : WebChromeClient() {
override fun onJsAlert(wv: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
if(message!=null){
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
}
result?.cancel()//一定要cancel,否则会出现各种奇怪问题
return true
}
};

拦截请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mWebView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
if(request?.url != null){
val url = request.url.toString()
if(url.contains("www.psvmc.cn")){
view?.loadUrl(url)
}
}
// WebView不加载该Url 自己处理
return true
}
}

加载中Dialog

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
mWebView.webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(p0: WebView?, p1: Int) {
super.onProgressChanged(p0, p1)
if (p1 == 100) {
mWebView.evaluateJavascript(jsstr, object : ValueCallback<String> {
override fun onReceiveValue(p0: String?) {
p0?.let {
doAsync {
Thread.sleep(200)
//异步分线程操作
uiThread {
//主线程操作
mWebView.visibility = View.VISIBLE
hideProgress()
}
}
}
}
})
}
}
};

mWebView.webViewClient = object : WebViewClient() {
override fun onPageStarted(p0: WebView?, p1: String?, p2: Bitmap?) {
super.onPageStarted(p0, p1, p2)
Log.e(TAG, "onPageStarted")
showProgress()
mWebView.visibility = View.INVISIBLE;
}

override fun onPageFinished(p0: WebView?, p1: String?) {
super.onPageFinished(p0, p1)
Log.e(TAG, "onPageFinished")
}
};

private var progressDialog: ProgressDialog? = null

fun showProgress() {
if (progressDialog == null) {
progressDialog = ProgressDialog.show(this@MainActivity, "", "页面加载中")
progressDialog?.setOnKeyListener( object:DialogInterface.OnKeyListener{
override fun onKey(dialog: DialogInterface?, keyCode: Int, p2: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
dialog?.cancel();
return true;
}
return false;
}
})
}
}

fun hideProgress() {
progressDialog?.dismiss()
progressDialog = null
}

注意

不建议在onPageFinished中调用,这个方法调用时页面未必加载完毕,并且可能被回调多次。

建议在onProgressChanged回调中,并且进度为100的时候调用,进度100同样会被调用多次,所以我们要进行判断,保证加载中弹窗唯一。

注入JS的时候尽管我们在JS执行返回的时候调用Webview显示,但仍会出现看到注入前的页面样式,所以这里延迟显示了。

Dialog不能通过当前页面的onBackPressed事件中取消,只能在dialog上进行监听事件。

显示进度条

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
fun showProgress() {
if (progressDialog == null) {
progressDialog = ProgressDialog(this)
progressDialog?.setTitle("请稍后");
progressDialog?.setMessage("加载中...");
progressDialog?.max = 100;
progressDialog?.progress = 0;
progressDialog?.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog?.setCanceledOnTouchOutside(false)
progressDialog?.show();

progressDialog?.setOnKeyListener(object : DialogInterface.OnKeyListener {
override fun onKey(dialog: DialogInterface?, keyCode: Int, p2: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
dialog?.cancel();
return true;
}
return false;
}
})

doAsync {
Thread.sleep(10000)
//异步分线程操作
uiThread {
//主线程操作
hideProgress()
}
}
}
}

fun setProgressValue(p:Int) {
progressDialog?.progress = p
}

点击外部不隐藏

1
progressDialog?.setCanceledOnTouchOutside(false)

处理返回按键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private var exitTime: Long = 0
override fun onBackPressed() {
if (mWebView.canGoBack()) {
mWebView.goBack();
} else {
if ((System.currentTimeMillis() - exitTime) > 1200) {
Toast
.makeText(
applicationContext,
"再按一次退出程序",
Toast.LENGTH_SHORT
)
.show();
exitTime = System.currentTimeMillis();
} else {
finish();
System.exit(0);
}
}
}

返回首页

为了让返回按钮控制WebView“返回某一个指定页”,我处理了按钮消息并加入相关逻辑。

我需要在特定的时候调用WebView.clearHistory(),不料clearHistory()并未起作用。

原因是clearHistory()只清除当前页之前的历史记录。

假设当前页面为A,我调用WebView.clearHistory()然后loadUrl(B),接着回退还是会退到A。

所以正确的调用时机是在B完全载入之后再调用WebView.clearHistory()

1
2
3
4
5
6
7
8
home_btn.setOnClickListener {
mWebView.loadUrl(homeurl)
mWebView.postDelayed(
Runnable()
{
mWebView.clearHistory()
}, 1000);
}

刷新

1
2
3
refresh_btn.setOnClickListener {
mWebView.reload()
}

注入脚本

隐藏元素

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
function hide_dom(){
var cls_arr_delay = [
".fm-navbar"
];
let hasload = false;
for (var i in cls_arr_delay) {
var dom_all = document.querySelectorAll(cls_arr_delay[i]);
if (dom_all && dom_all.length>0) {
dom_all.forEach(dom => {
dom.style.display = "none";
});
hasload = true;
}
}

let course_detail_player = document.querySelector(".course-detail-player");
course_detail_player.style.height = "auto";

if(hasload && window.myinter){
clearInterval(window.myinter);
}
};


window.myinter = setInterval(()=>{
hide_dom();
},100);

hide_dom();

document.oncontextmenu = function (ev) {
ev.preventDefault();
};

document.onselectstart = function (ev) {
ev.preventDefault();
};

window.onhashchange = function () {
let dom = document.querySelector("body");
dom.style.display = "none";
hide_dom();
setTimeout(()=>{
dom.style.display = "block";
},100);
};

删除

1
2
3
4
5
6
7
8
9
10
11
12
var remove_arr = [
".shici_tab",
".action"
];
for (var i in remove_arr) {
var dom_all = document.querySelectorAll(remove_arr[i]);
if (dom_all) {
dom_all.forEach(dom => {
dom.remove();
});
}
}

其它

1
2
3
4
var dom7 = document.querySelector(".comm-content-left");
if (dom7) {
dom7.style.width = "100%";
}

注意

有时我们发现有些页面的跳转,虽然URL发生变化了,但是Android回调没有触发,这是因为页面内使用的hash跳转,这时候就只能使用JS监听了。

示例

1
2
3
4
5
6
7
8
window.onhashchange = function () {
let dom = document.querySelector("body");
dom.style.display = "none";
hide_dom();
setTimeout(()=>{
dom.style.display = "block";
},100);
};

禁用复制事件

1
2
3
4
5
6
7
document.oncontextmenu = function (ev) {
ev.preventDefault();
};

document.onselectstart = function (ev) {
ev.preventDefault();
};

集成TBS

自带的webview使用起来很多网页不兼容,有很多坑,这里推荐使用TBS

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:44085'

添加权限

在您的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" />

首次初始化冷启动优化

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
import android.app.Activity;
import android.app.Application;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
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;

import java.util.HashMap;

public class MyApplication extends Application {
String TAG = "MyApplication";

@Override
public void onCreate() {
super.onCreate();
initTBS();
}


private void initTBS() {
QbSdk.setDownloadWithoutWifi(true);
HashMap map = new HashMap();
map.put(TbsCoreSettings.TBS_SETTINGS_USE_PRIVATE_CLASSLOADER, true);
map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
QbSdk.initTbsSettings(map);
QbSdk.setTbsListener(
new TbsListener() {

@Override
public void onDownloadProgress(int progress) {
Log.d("TBS_X5", "onDownloadProgress -->下载X5内核进度:" + progress);
}

@Override
public void onDownloadFinish(int progress) {
Log.d("TBS_X5", "onDownloadFinish -->下载X5内核完成:" + progress);
}

@Override
public void onInstallFinish(int progress) {
Log.d("TBS_X5", "onInstallFinish -->安装X5内核进度:" + progress);
}
});

//回调接口初始化完成接口回调
QbSdk.PreInitCallback pcb = new QbSdk.PreInitCallback() {
@Override
public void onCoreInitFinished() {

}

@Override
public void onViewInitFinished(boolean b) {
//x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。
Log.e("TBS_X5", " X5内核加载成功:" + b);
if(!b){
TbsDownloader.startDownload(getApplicationContext());
}
}
};

//x5内核预加载,异步初始化x5 webview所需环境
QbSdk.initX5Environment(getApplicationContext(), pcb);
}
}

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

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

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

增加Service声明

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

  2. 该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>

替换

包名替换

将源码和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里的声明也需要替换,例如:

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()
}
}