作者
大家好,我叫Jack冯;
本人20年硕士毕业于广东工业大学,于2020年6月加入37手游安卓团队;
目前主要负责海外游戏发行安卓相关开发。
作者:37手游移动客户端团队
链接:https://juejin.cn/post/7245084484756144186
背景
最近在接触活动相关需求,其中涉及到一个安卓的WebView;
刚毕业的我,对安卓知识积累比较少,所以在这里对Webview进行相关学习,希望自己可以在安卓方面逐步积累。
Webview介绍
1、关于MockView
( 1 ) 在targetSdkVersion 28/29的工程里面查看WebView继承关系
java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.widget.FrameLayout
↳ android.layoutlib.bridge.MockView
↳ android.webkit.WebView
( 2 ) 使用26/27等低版本SDK,查看源码中的WebView 继承关系
java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.widget.AbsoluteLayout
↳ android.webkit.WebView
( 3 )对比
两种方式对比,AbsoluteLayout和FrameLayout都是重写ViewGroup的方法,如与布局参数配置相关的 generateDefaultLayoutParams()、checkLayoutParams()等。两种方式明显不同的是多了一层MockView 。这里来看看MockView是什么:
public class MockView extends FrameLayout{
...
//创建方式
public MockView(Context context) {...}
public MockView(Context context,AttributeSet attrs) {...}
public MockView(Context context,AttributeSet attrs,int defStyleRes) {...}
//重写添加view方法
@Override
public void addView(View child){...}
@Override
public void addView(View child,int index){...}
@Override
public void addView(View child,int width,int height){...}
@Override
public void addView(View child,ViewGroup.LayoutParams params){...}
@Override
public void addView(View child,int index,ViewGroup.LayoutParams params){...}
public void setText(CharSequence text){...}
public void setGravity(int gravity){...}
}
MockView,译为"虚假的view"。
谷歌发布的Sdk其实只是为了提供App开发运行接口,实际运行时候替换为当前系统的Sdk。
具体说就是当谷歌在新的系统(Framework)版本上准备对WebView实现机制进行改动,同时又希望把新的sdk提前发出来,不影响用到WebView的App开发,于是谷歌提供给Android开发的sdk中让WebView继承自MockView,这个WebView只是暴露了接口,没有具体实现;这样当谷歌关于WebView新的实现做好,利用WebView,app也就做好了
2、基本使用
(1)创建
①一般方式
WebView webView = findViewById(R.id.webview);
②建议方式:
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
mWebView = new WebView(getApplicationContext());
mWebView.setLayoutParams(params);
好处:构建不用依赖本地xml文件,自定义页面参数;手动销毁避免内存泄露;
③更多方式 : 继承Webview和主要API等进行拓展
public class BaseWebView extends WebView {...}
public class BaseWebClient extends WebClient {...}
public class BaseWebChromeClient extends WebChromeClient {...}
(2)加载
① 加载某个网页
webView.loadUrl("http://www.google.com/");
②新建assets目录,将html文件放到目录下,通过路径加载本地页面
webView.loadUrl("file:///android_asset/loadFailed.html");
③使用evaluateJavascript(String script, ValueCallback resultCallback)方法加载,(Android4.4+)
mWebView.evaluateJavascript("file:///android_asset/javascript.html",new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.e("测试", "onReceiveValue:"+value );
}
});
3、WebViewClient
当URL即将加载到当前窗口,如果没有提供WebViewClient,默认情况下WebView将使用Activity管理器为URL选择适当的处理器。
如果提供了WebViewClient,按自定义配置要求来继续加载URL。
(1)常用方法
//加载过程对url的处理(webview加载、系统浏览器加载、其他操作等)
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
super.shouldOverrideUrlLoading(view, url);
}
//加载失败页面
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl){
view.loadUrl("file:///android_asset/js_error.html");
}
//证书错误处理
@Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
}
//开始加载页面(可自定义页面加载计时等)
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
Log.e(TAG, "onPageStarted:" + url);
}
//结束加载页面
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Log.e(TAG, "onPageFinished: " + url);
}
(2)关于shouldOverrideUrlLoading
如果在点击链接加载过程需要更多的控制,就可以在WebViewClient()中重写shouldOverrideUrlLoading()方法。
涉及shouldOverrideUrlLoading()的情形,大概分为三种:
(1)没有设定setWebViewClient(),点击链接使用默认浏览器打开;
(2)设定setWebViewClient(new WebViewClient()),默认shouldOverrideUrlLoading()返回false,点击链接在Webview加载;
(3)设定、重写shouldOverrideUrlLoading()
返回true:可由应用代码处理该 url,WebView 中止处理(若重写方法没加上view.loadUrl(url),不加载);
返回false:由 WebView 处理加载该 url。(即使没加上view.loadUrl(url),也会在当前Webview加载)
【一般应用】
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
super.shouldOverrideUrlLoading(view, url);
if (url != null) {
if (!(url.startsWith("http") || url.startsWith("https"))) {
return true;
}
//重定向到别的页面
//view.loadUrl("file:///android_asset/javascript.html");
//区别不同链接加载
view.loadUrl(url);
}
return true;
}
(3)常见误区
【误区1】 : 需要重写 shouldOverrideUrlLoading 方法才能阻止浏览器打开页面。
解释:WebViewClient 源码中 shouldOverrideUrlLoading 方法已经返回 false,不设定setWebViewClient(),默认使用系统浏览器加载。如果重写该方法并返回true, 就可以实现在app页面中加载新链接而不去打开浏览器。
【误区2】 : 每一个url加载过程都会经过 shouldOverrideUrlLoading 方法。
Q1:加载一定会触发shouldOverrideUrlLoading?
Q2:触发时机一定在onPageStarted调用之前?
解释:关于shouldOverrideUrlLoading的触发
1)如果在点击页面链接时通过标签跳转,触发方法如下:
shouldOverrideUrlLoading() —> onPageStarted()—> onPageFinished()
2)如果使用loadUrl加载时,触发方法如下:
onPageStarted()—>onPageFinished()
3)如果使用loadUrl加载重定向地址时,触发方法如下:
shouldOverrideUrlLoadings—>onPageStarted —> onPageFinished
ps:多次重定向的过程,
onPage1Started
—>shouldOverrideUrlLoadings
—>onPage2Started —> xxx...
—> onPageNFinished
结论:shouldOverrideUrlLoading()方法不是每次加载都会调用,WebView的前进、后退等不会调用shouldOverrideUrlLoading方法;非loadUrl方式加载 或者 是重定向的,才会调用shouldOverrideUrlLoading方法。
【误区3 】: 重写 shouldOverrideUrlLoading 方法返回true比false的区别,多调用一次onPageStarted()和onPageFinished()。
解释:返回True:应用代码处理url;返回False,则由 WebView 处理加载 url。
ps:低版本系统(华为6.0),测试 False比True会多调用一次onPageStarted()和onPageFinished(),这点还在求证中。
4、WebChromeClient
对比WebviewClient , 添加了处理JavaScript对话框,图标,标题和进度等。
处理对象 : 影响浏览器的事件
(1)常用方法:
//alert弹出框
public boolean onJsAlert(WebView view, String url, String message,JsResult result){
return true;//true表示拦截
}
//confirm弹出框
public boolean onJsConfirm(WebView view, String url, String message,JsResult result){
return false;//false则允许弹出
}
public boolean onJsPrompt(WebView view, String url, String message,String defaultValue, JsPromptResult result)
//打印 console 信息。return true只显示log,不显示js控制台的输入;false则都显示出来
public boolean onConsoleMessage(ConsoleMessage consoleMessage){
Log.e("测试", "consoleMessage:"+consoleMessage.message());
}
//通知程序当前页面加载进度,结合ProgressBar显示
public void onProgressChanged(WebView view, int newProgress){
if (newProgress < 100) {
String progress = newProgress + "%";
Log.e("测试", "加载进度:"+progress);
webProgress.setProgress(newProgress);
}
}
(2)拦截示例:
JsResult.comfirm() --> 确定按钮的调用方法
JsResult.cancle() --> 取消按钮
示例:拦截H5的弹框,并显示自定义弹框,点击按钮后重定向页面到别的url
@Override
public boolean onJsConfirm(final WebView view, String url, String message, final JsResult result) {
Log.e("测试", "onJsConfirm:"+url+",message:"+message+",jsResult:"+result.toString());
new AlertDialog.Builder(chromeContext)
.setTitle("拦截JsConfirm显示!")
.setMessage(message)
.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog,int which) {
//重定向页面
view.loadUrl("file:///android_asset/javascript.html");
result.confirm();
}
}).setCancelable(false).create().show();
return true;
}
5、WebSettings
用于页面状态设置\插件支持等配置.
(1)常用方法
WebSettings webSettings = webView.getSettings();
/**
* 设置缓存模式、支持Js调用、缩放按钮、访问文件等
*/
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
webSettings.setJavaScriptEnabled(true);
webSettings.setSupportZoom(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setDisplayZoomControls(true);
//允许WebView使用File协议,访问本地私有目录的文件
webSettings.setAllowFileAccess(true);
//允许通过file url加载的JS页面读取本地文件
webSettings.setAllowFileAccessFromFileURLs(true);
//允许通过file url加载的JS页面可以访问其他来源内容,包括其他的文件和http,https等来源
webSettings.setAllowUniversalAccessFromFileURLs(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setLoadsImagesAutomatically(true);
webSettings.setDefaultTextEncodingName("utf-8")
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
结束语
过程中有问题或者需要交流的同学,可以扫描二维码加好友,然后进群进行问题和技术的交流等;
关注我获取更多知识或者投稿