Android WebView(系统自带)

加载本地

加载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();
};