1.为何使用WebView?
我们先来探讨一下,为何好端端的一个app,嵌入了WebVIew这种控件。
WebVIew控件带来的好处:
- 复用性高。 只需要写一次 HTML5 代码,就可以在 Android 和 iOS 平台上运行,这就是所谓的「 跨平台 」。
- 动态更新。 由于页面是由HTML5实现的,出现的大部分改动问题,只需要修改服务器对应的HTML5页面,而不需要移动端重新打包编译上架。
但为什么它并没有被广泛地使用呢?
WebVIew控件使用的缺点:
- 兼容性差。 由于Android版本碎片化严重,无法很好的向下兼容,运行效率不一。
- 内存泄露。 webView内部的一些线程持有activity对象,导致activity无法释放。继而内存泄漏。
- 加载速度慢。 加载速度比原生慢上许多,造成了不好的用户体验。
适用场景:
- 简单的界面,不涉及复杂功能。 因为涉及到一些比较复杂的需求,依赖WebVIew,交互过多,对性能和兼容性的要求较高。
2.如何使用WebView?
2.1 基本用法
(1)获取网络权限
<uses-permission android:name="android.permission.INTERNET"/>
(2)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="match_parent"
android:orientation="vertical">
<WebView
android:id="@+id/wv"
android:layout_width="match_parent"
android:layout_height="300dp"/>
</LinearLayout>
(3)代码实现:
class WebViewActivity:Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_webview)
wv.loadUrl("https://blog.csdn.net/QQ1010110087")
}
}
(4)实现效果:
- 点击其中的链接,默认会跳转到网页显示,而不是在WebVIew上直接显示。
原因
- 不提供WebVIewClient,默认情况下,WebView将要求Activity Manager 为URL选择适合的处理程序。
- 提供了WebVIewClient,如果shouldOverrideUrlLoading方法
返回true,宿主应用程序处理URL,返回false,WebView处理URL。
因此,在WebVIew上显示,需要设置WebViewClient对象
class WebViewActivity:Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_webview)
wv.loadUrl("https://blog.csdn.net/QQ1010110087")
//设置WebViewClient对象
wv.webViewClient= WebViewClient() ;
}
2.1.1 获取并显示标题栏
class WebViewActivity:Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_webview)
wv.loadUrl("https://blog.csdn.net/QQ1010110087")
//设置WebViewClient
wv.webViewClient= WebViewClient() ;
//设置WebChromeClient
wv.webChromeClient = object : WebChromeClient() {
//获取HTML标题
override fun onReceivedTitle(view: WebView?, title: String?) {
tvTitle.setText(title)
super.onReceivedTitle(view, title)
}
}
}
}
2.1.2 WebViewClient和WebChromeClient用法
我们在处理URL和获取标题栏,分别使用到了WebViewClient、WebChromeClient.
Android应用开发的时候可能会用到WebView这个组件,使用过程中可能会接触到WebViewClient与WebChromeClient,那么这两个类到底有什么不同呢?
WebViewClient主要帮助WebView处理各种通知、请求事件的,比如:
- onLoadResource
- onPageStart
- onPageFinish
- onReceiveError
- onReceivedHttpAuthRequest
WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等比如
- onCloseWindow(关闭WebView)
- onCreateWindow()
- onJsAlert (WebView上alert无效,需要定制WebChromeClient处理弹出)
- onJsPrompt
- onJsConfirm
- onProgressChanged
- onReceivedIcon
- onReceivedTitle
看上去他们有很多不同,实际使用上:
- 如果你的WebView只是用来处理一些html的页面内容,只用WebViewClient就行了。
- 如果需要更丰富的处理效果,比如JS、进度条等,就要用到WebChromeClient。
2.1.3 WebViewClient和WebChromeClient简单使用
wv.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
Log.v("WebViewActivity", "onPageStarted")
super.onPageStarted(view, url, favicon)
}
override fun onPageFinished(view: WebView?, url: String?) {
Log.v("WebViewActivity", "onPageFinished")
super.onPageFinished(view, url)
}
}
wv.webChromeClient = object : WebChromeClient() {
//获取HTML标题
override fun onReceivedTitle(view: WebView?, title: String?) {
Log.v("WebViewActivity", "onReceivedTitle:title=" + title)
super.onReceivedTitle(view, title)
}
}
wv.loadUrl("https://blog.csdn.net/QQ1010110087")
打印日志:
2.2 WebView下载文件
//允许与JS进行交互
wv.settings.javaScriptEnabled=true
//加载百度手机助手页面
wv.loadUrl("https://shouji.baidu.com/appsearch/")
//设置下载监听事件
wv.setDownloadListener(object:DownloadListener{
override fun onDownloadStart(
url: String?,
userAgent: String?,
contentDisposition: String?,
mimetype: String?,
contentLength: Long
) {
//限制加载文件为apk或带有.apk
url?.run {
if(contains(".apk")){
val uri=Uri.parse(url)
//调用系统自带的下载界面
val intent=Intent(Intent.ACTION_VIEW,uri)
startActivity(intent)
}
}
}
})
- 在onDownloadStart方法中,我们也可以自己利用流去下载保存文件。
2.3 WebView与Js交互
2.3.1 Android调用Js方法
- WebView的loadUrl()方法
html页面:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>webview</title>
</head>
<body>
<p>Hello world! This is WebView Test</p>
<script> function callJs() {
alert("调用js的callJs方法")
}
</script>
</body>
</html>
在app/src/main目录下,创建assets目录,复制这个html文件到这个目录下。
XML文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:background="#3AA9F1"
android:layout_height="40dp"
android:gravity="center"
android:textColor="#ffffff"
tools:text="标题"/>
<WebView
android:id="@+id/wv"
android:layout_width="match_parent"
android:layout_height="300dp"/>
<Button
android:id="@+id/btCallJS"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="调用js方法"/>
</LinearLayout>
代码实现:
class WebViewActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_webview)
/**
* 调用Js方法
*/
callJSMethod()
}
fun callJSMethod() {
/**
* file:///android_asset 代表app/src/main/assets这个目录
*/
wv.loadUrl("file:///android_asset/index.html")
//允许与Js交互
wv.settings.javaScriptEnabled = true
wv.settings.javaScriptCanOpenWindowsAutomatically = true
wv.webChromeClient = object : WebChromeClient() {
override fun onReceivedTitle(view: WebView?, title: String?) {
super.onReceivedTitle(view, title)
//设置标题栏标题
tvTitle.setText(title)
}
//拦截Js的alert方法,可在该方法内重写alert的布局实现
override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
val alertDialog = AlertDialog.Builder(this@WebViewActivity)
alertDialog.setTitle("onJsAlert")
alertDialog.setMessage(message)
alertDialog.setPositiveButton("确认", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface?, which: Int) {
//dialog.dismiss() 若调用这个方法而不调用result的confirm方法,会出现只能调用一次警告框的情况
result?.confirm()
}
})
alertDialog.setCancelable(false)
alertDialog.show()
return true
}
}
btCallJS.setOnClickListener {
wv.loadUrl("javascript:callJs()")
}
}
}
效果图:
- 需要在每次调完onJsAlert后设置参数JsResult调用cancel()或者confirm()方法,否则再去调用时,警告框不会出现。
2.3.2 JS调用Android方法
- WebView的addJavascriptInterface()方法 对象映射
addJavascriptInterface(obj:Object, interfaceName:String)
(1)HTML页面添加button回调android方法
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>webview</title>
</head>
<body>
<p>Hello world! This is WebView Test</p>
<button type="button" style="width:150px;height:50px;"
onclick="javascript:callAndroidMethod();" >调用android方法</button>
<script> function callJs() {
alert("调用js的callJs方法")
}
</script>
<script> function callAndroidMethod() {
wvActivity.androidMethod("js处调用androidMethod方法")
}
</script>
</body>
</html>
(2)根据HTML中的对象名和对象方法设置对象名称和对象回调方法
class WebViewActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_webview)
callAndroidSetting()
}
fun callAndroidSetting(){
//对象为该activity,该activity对应html文件中wvActivity对象
wv.settings.javaScriptEnabled=true
wv.loadUrl("file:///android_asset/index.html")
wv.addJavascriptInterface(this,"wvActivity")
}
@JavascriptInterface
fun androidMethod(jsParams:String){
Log.v("WebViewActivity","androidMethod:"+jsParams)
}
}
(3)实现效果
点击Button,打印日志如下:
2.4 处理系统返回键
问题:系统返回键,调用的是Activity的finish方法,而不是页面的回退操作。
解决方案:Activity的onBackPressed方法中判断webview是否有上一个页面?
- 有,则返回到上一个HTML页面。
- 没有,则执行父类方法。
override fun onBackPressed() {
if (wv.canGoBack()) {
wv.goBack()
}else
super.onBackPressed()
}
2.4 WebView的Cookie处理
- 获取CookieSyncManager
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
- 设置Cookie
//帐号的cookie设置规则
String mCookie = "wv.token=xxxxxxx";
//设置Cookie
cookieManager.setCookie(Url, mCookie);
//同步Cookie
CookieSyncManager.getInstance().sync();
- 获取Cookie
String mCookie = cookieManager.getCookie(Url);
- 清除Cookie
cookieManager.removeAllCookie();
CookieSyncManager.getInstance().sync();
总结
后续待补充。