【Android WebView】WebView基础

dev晴天 2024-06-23 08:33:02 阅读 98

在这里插入图片描述

一、简介

WebView是一个基于webkit引擎、展现web页面的控件。Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。

二、重要类

以WebView类为基础,WebSettings、WebViewClient、WebChromeClient为辅助共同完成安卓段加载网页的操作。

1、WebView

//1、加载网页相关 //直接加载服务器网页 my_web_view.loadUrl("https://www.baidu.com") //直接加载本地网页file:///android_asset/为固定写法) my_web_view.loadUrl("file:///android_asset/1.html") // 缓存历史清理 //Clears the resource cache. Note that the cache is per-application, // so this will clear the cache for all WebViews used. my_web_view.clearCache(true) // 清空历史(本webView浏览的) my_web_view.clearHistory() // 结合工具类 my_web_view.webViewClient = object : WebViewClient() // 设置webViewClient my_web_view.webChromeClient = object : WebChromeClient()// 设置WebChromeClient /** * 返回键处理,网页中可返回上一页,不处理返回键直接退出App * */ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { //when user press back code and canGoBack if (keyCode == KeyEvent.KEYCODE_BACK && my_web_view.canGoBack()) { my_web_view.goBack() return true } return super.onKeyDown(keyCode, event) }

2、WebSettings

/** * WebSettings类:对webView 进行配置管理 * */ @SuppressLint("SetJavaScriptEnabled", "ObsoleteSdkInt") private fun webViewSettings() { val webSettings = my_web_view.settings // js交互控制 //支持js交互,动画等操作会造成cpu、电量消耗可在activity、 fragment的onResume、onStop进行开关控制 webSettings.javaScriptEnabled = true // 适配 webSettings.useWideViewPort = true//自适应屏幕->自动将图片调整到自适应webView 大小 webSettings.loadWithOverviewMode = true//自适应屏幕->缩放到屏幕大小 //缩放 webSettings.setSupportZoom(true)//支持缩放,默认为true。 webSettings.builtInZoomControls = true//设置是否展示内置的缩放控件,默认为false webSettings.displayZoomControls = true//显示原生的缩放控件 //其他 webSettings.allowFileAccess = true // 可访问文件 webSettings.javaScriptCanOpenWindowsAutomatically = true // 支持js 自动打开新窗口 webSettings.loadsImagesAutomatically = true // 支持自动加载图片 webSettings.defaultTextEncodingName = "UTF-8" // 默认值也是UTF-8 //缓存控制 webSettings.domStorageEnabled = true // 开启dom缓存功能 webSettings.databaseEnabled = true // 开启数据库缓存功能 webSettings.setAppCacheEnabled(true)// 开启application 缓存功能 webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK //设置缓存模式 (LOAD_CACHE_ELSE_NETWORK,有缓存时加载缓存,即使缓存过期,没有时从网络加载) webSettings.setAppCachePath("") // 设置app缓存目录,api已弃用。 // 5.1以上默认禁止了https和http混用,开启如下 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW } }

3、WebViewClient

协助WebView工作,有一些列回调方法,用于处理各种通知 & 请求事件等。

my_web_view.webViewClient = object : WebViewClient() { //加载url、返回false 代表使用webView 加载url 不使用系统浏览器。 override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { // 如下loadUrl,return true 这种也代表不使用系统浏览器。但是官方建议直接 return false // view?.loadUrl("https://www.baidu.com") // return true return false// 直接return false即可 } // 页面加载时回调 override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) } // 页面加载完成时回调 override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) } // 加载页面资源时调用(如页面上有好多图片,没加载一张就会回调一次) override fun onLoadResource(view: WebView?, url: String?) { super.onLoadResource(view, url) } // 加载页面时服务器出现错误(例如404) override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) { super.onReceivedError(view, request, error) // todo 展示自定义html页面,提示错误。 } // 处理https请求 override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) { // 不要使用super,否则有些手机访问不了,因为包含了一条 handler.cancel() // super.onReceivedSslError(view, handler, error); // 接受所有网站的证书,忽略SSL错误,执行访问网页。这种方式在发生ssl不会影响网页加载。 // 但是google play 认为这是不安全的操作。会给你警告。 handler?.proceed() // handler?.cancel() // 发生ssl时挂起连接表现为白屏,默认方式。 } }

4、WebChromeClient

/** * WebChromeClient类:辅助webView 处理js dialog、网站icon、网站title。 * js的各种dialog事件触发时可由webChromeClient的响应方法回调到。 * */ private fun webChromeClient() { my_web_view.webChromeClient = object : WebChromeClient() { // 获得网页进度时回调 override fun onProgressChanged(view: WebView?, newProgress: Int) { super.onProgressChanged(view, newProgress) } // 获取网页的标题时回调 override fun onReceivedTitle(view: WebView?, title: String?) { super.onReceivedTitle(view, title) } // 是否由客户端处理js Alert 事件,true 代表客户端处理,false 代表客户端不处理。 // 一般客户端处理时客户端 展示对话框让用户进行选择 override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean { AlertDialog.Builder(mContext) .setTitle("Title") .setMessage(message) .setPositiveButton("确定") { dialog, which -> result?.confirm() }.setCancelable(false) .show() return true } // js Confirm 对话框拦截,true代表点击确定,false 代表点击取消。 override fun onJsConfirm(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean { return super.onJsConfirm(view, url, message, result) } // 输入框拦截,true 代表返回输入框的值,false 代表返回null override fun onJsPrompt( view: WebView?, url: String?, message: String?, defaultValue: String?, result: JsPromptResult? ): Boolean { return super.onJsPrompt(view, url, message, defaultValue, result) } } }

三、Android 调用 JS 代码

方式
webView的loadUrl(" javascript:js方法()")webView的evaluateJavascript()

<html><head> <meta charset="UTF-8"> <title>SunnyDay</title> <!--内部引入法--> <script> function showAlertDialog(result){ alert("i am js alert dialog,i am js method!"+result) return "i am js method!" } </script></head><body> <h3>i am a simple html page</h3></body></html>>

如上准备个1.html文件

1、webView#loadUrl 加载原理及其代码

借助webChromeClient 的各种回调处理。一般为js的各种dialog 方法触发时被安卓webViewClient的各种回调监听方法拦截消费。

webSettings = web_view.settings webSettings.javaScriptEnabled = true//允许启用js功能 webSettings.javaScriptCanOpenWindowsAutomatically = true // 允许js弹窗 // 加载Assets下html文件(这时js代码也就载入了) //加载assets文件夹下的html文件时使用固定格式语法即可:file:///android_asset/文件名.html web_view.loadUrl("file:///android_asset/1.html") web_view.webViewClient = object : WebViewClient(){ } callByLoadUrl() //webView只是载体,内容的渲染需要使用webViewChromeClient类去实现,所以在这里写回调处理。 web_view.webChromeClient = object : WebChromeClient() { // 此方法可回调到alert警告框的信息。 override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean { val build = AlertDialog.Builder(mContext) .setTitle("Js showAlertDialog Method") .setMessage(message) .setPositiveButton("确定") { dialog, which -> result?.confirm() }.setCancelable(false) build.create().show() return true } } /** * 方式1:webView的loadUrl() * */ private fun callByLoadUrl() { // 点击安卓按钮加载js方法 btn_call_js_method.setOnClickListener { // 调用js时要保证网页加载完成,否则js代码调用失败。因为html中也是页面加载完成才响应的js。 web_view.loadUrl("javascript:showAlertDialog(\"result\")") } }

(2)webView#evaluateJavascript

webSettings = web_view.settings webSettings.javaScriptEnabled = true//允许启用js功能 webSettings.javaScriptCanOpenWindowsAutomatically = true // 允许js弹窗 // 加载Assets下html文件(这时js代码也就载入了) web_view.loadUrl("file:///android_asset/1.html")//加载assets文件夹下的html文件时使用固定格式语 法即可:file:///android_asset/文件名.html web_view.webViewClient = object : WebViewClient(){ } callByEvaluateJavascript() /** *方式2:webView的evaluateJavascript * js 方法无返回值时it为 null */ private fun callByEvaluateJavascript() { btn_call_js_method.setOnClickListener { web_view.evaluateJavascript("javascript:showAlertDialog()") { Log.d("AndroidCallJsActivity", "" + it)//AndroidCallJsActivity"i am js method!" } } }

(3)总结

/** * Android call js method practice * 两种方式: * 1、webView的loadUri(" javascript:js方法()") * 这种方式一般调用的为js的dialog方法,使用安卓的WebChromeClient 对应回调方法进行拦截处理。 * 2、webView的evaluateJavascript * 直接调用js的方法,还可以获得js方法返回值回调 * js方法结果回调为String 类型值 * js方法无返回值,这里回调为null * * 建议以这种方式,既可快捷获得返回值,又可通过1中的dialog回调处理。 * * 小结:Android call js method * 安卓调用js方法后可以在安卓端获得结果回调处理。 * */

调用方式 优点 缺点 适用场景
loadUrl 方便简洁 效率低,获取返回值麻烦 不需要获取返回值时
evaluateJavascript 效率高 安卓4.4以上才能使用 安卓4.4以上

四、Js调用安卓代码

方式
通过webView的addJavascriptInterface方法通过重写webViewClient的shouldOverrideUrlLoading方法筛选url调用通过 WebChromeClient 的dialog 回调判断筛选url调用

<html><head> <meta charset="UTF-8"> <title>SunnyDay</title> <!--内部引入法--> <script> //方式1:对象映射方式 function callAndroid(){ // 调用安卓的hello方法,由于安卓中添加了对象映射,test.hello()就相当于安卓的 //JsInterface().hello() test.hello(" i am js code! "); } //方式2:重写shouldOverrideUrlLoading 进行拦截 function callAndroid2(){ document.location = "js://webview?arg1=111&arg2=222"; } //方式3:WebChromeClient 的 onJsAlert、onJsConfirm、onJsPrompt 方法回调拦截 JS 对话框 function callAndroid3(){ //1、首先搞个输入框,触发时安卓可获得回调 //2、收到回调后安卓处理,点击确定返回输入框值,点击取消返回null var result=prompt("js://demo?arg1=111&arg2=222"); alert("demo " + result); } </script></head><body><h3>i am a simple html page</h3>//方式1<button type="button" id="btn" onclick="callAndroid()">点我即可调用Android的hello方法</button>//方式2<button type="button" id="btn2" onclick="callAndroid2()">方式2</button>//方式3<button type="button" id="button3" onclick="callAndroid3()">方式3</button></body></html>>

1、addJavascriptInterface

(1)定义对象映射方法

/** * 1、自定义对象用于对象映射 * 2、自定义方法,映射后供js对象使用 * 3、方法必须加JavascriptInterface注解 */class JsInterface { @JavascriptInterface fun hello(msg: String) { Log.d("JsCallAndroidActivity", "android method hello is called:$msg") }}

(2)映射

//js调用安卓方法方式1: webSettings = web_view_th.settings webSettings.javaScriptEnabled = true//允许启用js功能 webSettings.javaScriptCanOpenWindowsAutomatically = true // 允许js弹窗 web_view_th.loadUrl("file:///android_asset/2.html") // 定义对象映射,把自定义的JsInterface映射给"test",供js对象使用。 web_view_th.addJavascriptInterface(JsInterface(), "test")// 一行代码十分简单

2、重写webViewClient的shouldOverrideUrlLoading方法

webSettings = web_view_th.settings webSettings.javaScriptEnabled = true//允许启用js功能 webSettings.javaScriptCanOpenWindowsAutomatically = true // 允许js弹窗 web_view_th.loadUrl("file:///android_asset/2.html") web_view_th.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { //view?.url //file:///android_asset/2.html , //注意这里的两个url不相同 get的url为 webViewLoad的,request的为动态定义的 val requestUrl = request?.url Log.i("JsCallAndroidActivity", "" + requestUrl) //js://webview?arg1=111&arg2=222 return if (requestUrl.toString() == "js://webview?arg1=111&arg2=222") { // 符合筛选,调用安卓方法。 printLog() true } else { super.shouldOverrideUrlLoading(view, request) } } } private fun printLog() { Log.i("JsCallAndroidActivity", "android method:print log !") }

3、通过 WebChromeClient 的dialog 回调判断筛选url调用

webSettings = web_view_th.settings webSettings.javaScriptEnabled = true//允许启用js功能 webSettings.javaScriptCanOpenWindowsAutomatically = true // 允许js弹窗 web_view_th.loadUrl("file:///android_asset/2.html") web_view_th.webChromeClient = object : WebChromeClient() { override fun onJsPrompt( view: WebView?, url: String?, message: String?, defaultValue: String?, result: JsPromptResult? ): Boolean { val uri = Uri.parse(message) if ("js" == uri.scheme) { if ("demo" == uri.authority) { result?.confirm("JS 调用了 Android 的方法") // 符合筛选,调用安卓方法。 printLog() } return true } return super.onJsPrompt(view, url, message, defaultValue, result) } }

4、小结

/** * js call android method practise * 1、webView的addJavascriptInterface(自定义对象,“映射给的自定义字符串对象”) * 这种方式最简单,自定义对象,内部定义一系列方法供js调用。js拿到映射对象即可调用安卓自定义的对象方法 * * 2、重写webViewClient的shouldOverrideUrlLoading方法,筛选请求的url是否为js规定的url,是规定的则调用安卓的相应方法。 * * 3、通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt(),原理与2一致。 * */

调用方式 优点 缺点 适用场景
WebView.addJavascriptInterface 对象映射 方便简洁 Android 4.2 一下存在漏洞 Android 4.2 以上相对简单的应用场景
WebViewClient.shouldOverrideUrlLoading 回调拦截 不存在漏洞 使用复杂,需要协议约束 不需要返回值情况下
WebChormeClient.onJsAlert / onJsConfirm / onJsPrompt 方法回调拦截 不存在漏洞 使用复杂,需要协议约束 能满足大多数场景

五、Android 内嵌网页时登录状态同步方案

1、使用 WebView 和 Cookie 同步

Android 的 WebView 是一种能在应用中显示网页的组件。WebView 会自动处理 Cookies,但为了确保登录状态的同步,可能需要手动同步应用和 WebView 的 Cookies。

(1) 获取应用中的 Cookie

如果你的应用在登录后保存了 Cookie,可以通过 CookieManager 来获取这些 Cookie。

(2) 将 Cookie 注入 WebView:

在加载网页前,将这些 Cookie 注入 WebView 的 CookieManager。

CookieManager cookieManager = CookieManager.getInstance();cookieManager.setAcceptCookie(true);// 获取应用的 Cookie,假设名为 "appCookie"String appCookie = "key=value; domain=yourdomain.com";cookieManager.setCookie("yourdomain.com", appCookie);// 加载网页webView.loadUrl("https://yourdomain.com");

(3) 确保同步:

在 WebView 加载新页面时,确保 Cookie 同步。

webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); CookieManager.getInstance().flush(); }});

2、使用 Shared Preferences 和 JavaScript Bridge

这种方法可以通过 JavaScript 与 Android 应用交互,保持登录状态同步。

(1)保存登录状态:

登录后,将登录状态(例如 token)保存到 Shared Preferences。

SharedPreferences sharedPref = getSharedPreferences("MyApp", Context.MODE_PRIVATE);SharedPreferences.Editor editor = sharedPref.edit();editor.putString("token", "your_token");editor.apply();

(2)设置 JavaScript 接口:

在 WebView 中设置 JavaScript 接口,用于获取登录状态。

webView.addJavascriptInterface(new Object() { @JavascriptInterface public String getToken() { SharedPreferences sharedPref = getSharedPreferences("MyApp", Context.MODE_PRIVATE); return sharedPref.getString("token", ""); }}, "Android");

(3)在网页中使用:

在网页的 JavaScript 中调用这个接口,获取登录状态。

function getToken() { return Android.getToken();}// 在需要时调用 getToken(),例如页面加载完成后document.addEventListener("DOMContentLoaded", function() { var token = getToken(); if (token) { // 使用 token 做进一步处理 }});

3、使用 OAuth 进行单点登录(SSO)

如果你的应用和网页都支持 OAuth,可以使用 OAuth 进行单点登录。

(1)在应用中获取 OAuth Token:

用户在应用中登录后,获取 OAuth Token 并保存。

(2)WebView 加载网页时附加 Token:

在加载网页时,将 Token 附加在 URL 或请求头中。

String url = "https://yourdomain.com?token=" + oauthToken;webView.loadUrl(url);// 或者设置请求头webView.setWebViewClient(new WebViewClient() { @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { Map<String, String> headers = new HashMap<>(); headers.put("Authorization", "Bearer " + oauthToken); request.setRequestHeaders(headers); return super.shouldInterceptRequest(view, request); }});

4、使用统一的身份认证系统

如果你的应用和网页都使用统一的身份认证系统(如 Firebase Authentication),可以通过相同的身份认证机制同步登录状态。

(1)应用中登录:

使用统一的身份认证系统登录,并获取认证信息。

(2)WebView 中共享认证信息:

通过与身份认证系统的集成,在 WebView 中实现自动登录。

FirebaseAuth mAuth = FirebaseAuth.getInstance();FirebaseUser currentUser = mAuth.getCurrentUser();if (currentUser != null) { String idToken = currentUser.getIdToken(false).getResult().getToken(); String url = "https://yourdomain.com?token=" + idToken; webView.loadUrl(url);}

六、WebView相关优化建议

1、给 WebView 加一个加载进度条

为了友好展示,重写 WebChromeClient 的 onProgressChanged 方法。未加载完成时展示loading进度条

2、提高 HTML 网页加载速度,等页面 finsh 在加载图片

public void load () { if(Build.VERSION.SDK_INT >= 19) { webView.getSettings().setLoadsImagesAutomatically(true); } else { webView.getSettings().setLoadsImagesAutomatically(false); }}

3、onReceivedError 时加载自定义界面

web的error 页面比较丑,我们可以在加载失败时,展示安卓自定义的错误展示页。

4、动画、银屏、视频 合适加载释放

动画、银屏、视频 加载会造成cpu、电量消耗可在activity、 fragment的onResume、onStop进行开关控制。

七、收获

1、明文传输控制

Android P 阻止加载任何 http 的请求,在清单文件application节点 添加android:usesCleartextTraffic=“true”

2、Android 5.0 之后 WebView 禁止加载 http 与 https 混合内容 。这样会造成某些资源加载失败。

if (Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){ webview.getSettings(). setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); }/*例如:https://www.aaa.com.cn/a/b/c/d/e/index.html?uid=0011&URL=http://www.bbb.com.cn/1.png 这个url1、实际页面为:https://www.aaa.com.cn/a/b/c/d/e/index.html 后面追加了一些参数2、h5页面上src标签加载http://www.bbb.com.cn/1.png此时你使用webview.loadUrl("https://www.aaa.com.cn/a/b/c/d/e/index.html?uid=0011&URL=http://www.bbb.com.cn/1.png") 就会出现h5 图片显示失败。解决方案开启MixedContentMode*/

3、硬件加速开启导致问题

比如不能打开 PDF,播放视频花屏等等。关闭硬件加速。

4、重写webViewClient的shouldOverrideUrlLoading

否则系统不知道你是想用webview打开url 还是使用系统浏览器。

5、WebView白屏问题

原因:页面进度不到100%时显示的就是白屏,进度到100时开始加载网页资源。

解决:网上方案较多这里提示一种:进度不到100时展示加载提示。

The end

官方文档

练习



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。