Android-WebView

2018-11-26  本文已影响0人  哎呦呦胖子斌

        Android应用层开发有两种方式:客户端开发和HTML5移动端开发,所谓的HTML5开发就是用HTML5+CSS+JS来构建一个网页版的应用,这中间的媒介就是webview,而web和网页端可以通过js来进行交互。相比普通客户端开发,HTML5移动端有一个优势,可以用百分比来布局,而且如果HTML5端由什么大的改变,不用像客户端那样去重新下载一个APP,只要修改下网页即可,当然HTML5也有一个缺点,就是性能问题,数据积累,耗电问题,还有闪屏等。
        webview:android内置webkit内核的高性能浏览器,而Webview则是在这个基础上进行封装后的一个控件,可以简单理解为一个嵌套到界面上的浏览器控件。

webview的常用方法

webview.onResume()

激活WebView为活跃状态,能正常执行网页的相应

webview.onPause()

当页面被失去焦点切换到后台为不可见状态时,执行此方法,通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、javascript的执行

webview.pauseTimes()

当应用程序被切换到后台时这个方法不仅仅针对当前的webview而是全局的全应用程序的webview,它会暂停所有webview的layout,parsing,javascripttimer,降低cpu功耗。

webview.resumeTimers()

恢复pauseTimers状态

rootLayout.removeView(webview);

webview.destroy();

销毁webview,在关闭了Activity时,如果webview的音乐或视频还在播放,就必须销毁webview,但是注意:webview调用destroy时,webview仍绑定在Activity上,这是由于自定义webview构建时传入了该Activity的context对象,因此需要先从父容器中移除webview,然后再销毁webview。

webview.canGoBack()

是否可以后退

webview.goBack()

后退网页

webview.canGoForward()

是否可以前进

webview.goForward()

前进网页

webview.goBackOrForward(int steps)

以当前的index为起始点前进或者后退到历史记录中指定的steps,如果steps为负数则为后退,正数则为前进

常见用法:Back键控制网页后退
        在不做任何处理的前提下,浏览网页时点击系统的back键,整个浏览器会调用finish()而结束自身,如果想要实现网页的回退而不是退出浏览器,那么要重写onKeyDown()方法。


image.png

webview.clearCache(true)
清除网页留下的缓存,由于内核缓存时全局的因此这个方法不仅仅针对webview而是针对整个应用程序。

webview.clearHistory()
清除当前webview访问的历史记录,只会webview访问历史记录里的所有记录,除了当前访问的记录

webview.clearFormData()
这个方法仅仅清除自动完成填充的表单数据,并不会清除webview存储到本地的数据。

WebSettings类

作用:对webview进行配置和管理

配置步骤:

1. 添加访问网络的权限

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

2. 生成一个webview组件

webView = (WebView) findViewById(R.id.webview);

3. 利用webSettings的子类进行配置

webSettings = webView.getSettings();

//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);

//设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小

//缩放操作
webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件

//其他细节操作
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
webSettings.setAllowFileAccess(true); //设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式

WebViewClient类

作用:处理各种通知和请求事件

常用方法:

1. shouldOverrideUrlLoading()

打开网页时不调用系统浏览器,而是在本webview中显示,在网页上的所有加载都经过这个方法。


image.png

2. onPageStarted()
开始载入页面时调用,可以设定一个loading的页面,告诉用户程序正在等待网络响应。


image.png
3. onPageFinished()
在页面加载结束时调用,可以关闭loading条,切换程序动作。
image.png

4. onReceivedError()
加载页面的服务器出现错误时调用,App里面使用webview控件的时候遇到了诸如404这类的错误的时候,若也显示浏览器里面的那种错误提示页面就会显得很丑陋,那么这个时候我们的app就需要加载一个本地的错误提示页面,即webview如何加载一个本地的页面。


image.png

WebChromeClient类

作用:辅助webview处理javascript的对话框、网站图标、网站标题等。

常用方法:

1. onProgressChanged()

获得网页的加载进度并显示

image.png

2. onReceivedTitle()
获取web页中的标题


image.png

避免webview内存泄露

        不在xml中定义webview,而是在需要的时候在Activity中创建,并且Context使用getApplicationContext(),在Activity销毁webview的时候,先让webview加载null内容,然后移除webview,再销毁webview,最后置空。

Android通过webview与JS进行交互

两种方式:

方式一:通过webview的loadUrl()
方式二:通过webview的evaluateJavascript()
        需要将调用的本地JS代码以.html的格式放到src/main/assets文件夹下面,在实际情况中更多的是调用远程js代码,将加载的js代码路径改成url即可。例如我们的本地js代码如下,JS中的calljs()方法弹出一个弹框,并返回一个值111,我们看下在android中怎么调用:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>jsweb</title>
    <script>
        function calljs() {
            alert("android调用了js的alert");
            return 111;
        }
    </script>
</head>

<body>
    <div style="background:#f00">我是一个块</div>
</body>
</html>

方式一:通过webview的loadUrl()

设置一个Button,当点击按钮时,即调用js代码,在此之前要对webview进行一些配置:

//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
//支持通过JS打开新窗口
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

载入JS代码:

webView.loadUrl("file:///android_asset/jsweb.html");

重写button的onClick方法

bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                webView.post(new Runnable() {
                    @Override
                    public void run() {
                        //同步
                        webView.loadUrl("javascript:calljs()");
                    }
                });
            }
        });

        Runnable是一个接口,不是一个线程,一般线程会实现Runnable,所以如果我们使用匿名内部类是运行在UI线程的,如果我们使用实现这个Runnable接口的线程类,则是运行在对应线程的。
        个人的理解:webview.post()的作用很明确,就是从其他线程访问主线程,比如在onCreat()方法中获取某个view的宽高,而直接view.getWidth获取到的值为0,为啥呢,因为view显示到界面上需要经历onMeasure、onLayout、onDraw三个过程,而view的宽高是在onLayout阶段才确定的,在onCreate中并不能保证view已经执行到了onLayout方法,也就是说Activity的生命周期与view的绘制流程并不是一一绑定的,为啥调用post方法就能起作用呢,首先MessageQueue是按顺序处理消息的,而在setContentView()后队列中会包含一条询问是否完成布局的消息,view.post()方法把action添加到队列尾部,保证了在onLayout结束后才执行。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里,在Handler里,它将传递过来的action对象包装成一个Message,然后将其投入UI线程的消息循环中,在Handler再次处理该Message时,有一条分支就是为它所设,直接调用Runnable的run方法,而此时,已经路由到UI线程里,此时可以毫无顾忌的更新UI,在这种情况下,由于不是在新的线程中使用,所以不要做复杂的计算逻辑。

方式二:通过webview的evaluateJavascript()方法

优点:更高效、使用更简洁。该方法的执行不会使页面刷新。

两个问题:(已解决)

1. loadUrl()是同步的?evaluateJavaScript()是异步的?

(好像是的)

2. webview.post(new Runnable())到底开没开子线程?

(没有开子线程,而是为了防止在子线程中使用webview.loadUrl(),而使用post方法调回主线程)

  bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                webView.post(new Runnable() {
                    @Override
                    public void run() {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                            webView.evaluateJavascript("calljs()", new ValueCallback<String>() {
                                @Override
                                public void onReceiveValue(String s) {
                                    Log.e("eee",s);
                                }
                            });
                        }
                    }
                });
            }
        });

两种方法对比:


image.png

JS通过webview与Android进行交互

三种方式:

方式一:通过webview的addJavascriptInterface()方法进行对象映射

方式二:通过webview的shouldOverrideUrlLoading()方法回调拦截Url

方式三:通过webChromeClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框

方式一:通过webview的addJavascriptInterface()

首先,要定义一个与JS对象有映射关系的Android类,继承Object类,并加入@JavaScriptInterface注解

public class AndroidJs extends Object {
    @JavascriptInterface
    public void hello(String msg){
        Log.e("eee",msg);
    }
}

nbsp;nbsp;nbsp;nbsp;nbsp;nbsp;nbsp;nbsp;同样将需要调用的JS代码以.html格式存放到src/main/assets文件夹里,这里我们定义一个button,在点击事件中,androidcall对象是js中的对象,而在activity代码中通过addJavascriptInterface()将java对象映射到js对象上。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>jsweb</title>
    <script>
        function calljs() {
            alert("android调用了js的alert");
            return 111;
        }
        function callandroid(){
            androidcall.hello("js调用了android的方法");
        }
    </script>
</head>

<body>
    <div style="background:#f00">我是一个块</div>
    <button style="height:200px;width:200px" onclick="callandroid()">js调用android</button>
</body>
</html>

在activity代码中,通过webview设置Android与JS之间的映射。

webView.addJavascriptInterface(new AndroidJs(),"androidcall");

在AndroidJs对象中写hello()方法,执行相应的操作。
优点:使用简单,仅将Android对象和JS对象映射即可
缺点:存在严重的漏洞问题。

方式二:通过WebViewClient()中的shouleOverrideUrlLoading()方法回调拦截url

原理:

        Android通过webviewClient的回调方法shouldOverrideUrlLoading()拦截url;解析该url的协议;如果检测到是预先约定好的协议,就调用相应的方法。

首先,在js中预定所需要的url协议

<!DOCTYPE html>

<html>

<head>

  <meta  charset="utf-8">

  <title>jsweb</title>

  <script>

  function  calljs() {

  alert("android调用了js的alert");

  return  111;

 }

  function  callandroid1(){

  androidcall.hello("js调用了android的方法");

 }

  function  callandroid2(){

  document.location="js://webview?arg1=123&arg2=456";

 }

  </script>

</head>

<body>

  <div  style="background:#f00">我是一个块</div>

  <button  style="height:200px;width:200px"  onclick="callandroid1()">js调用android</button>

  <button  style="height:200px;width:200px"  onclick="callandroid2()">js调用android</button>

</body>

</html>

        重写WebViewClient()的shouleOverrideUrlLoading()方法,根据协议的参数,判断是否是所需要的url,一般根据scheme(协议格式)和authority(协议名)这两个参数判断,约定好的url格式是:”js://webview?arg1=123&arg2=456”,下面的代码分别对协议格式和协议名进行解析。

webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String Url) {
        Uri uri = Uri.parse(Url);
        if(uri.getScheme().equals("js")){
            if(uri.getAuthority().equals("webview")){
                String aa = uri.getQueryParameter("arg1");
                Log.e("eee",aa);
            }
            return true;
        }
        return super.shouldOverrideUrlLoading(view, Url);
    }

优点:不存在方式一的漏洞
缺点:JS获取Android方法的返回值比较复杂(怎么返回呢,看看好不好),参数bb为android返回给js的参数。

webView.evaluateJavascript("result("+bb+")", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.e("eee",s);
    }
});

JS代码如下:

function result(data) {
            document.getElementById("div2").innerHTML=data;
        }

方式三:通过WebClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt()消息

原理:

        Android通过WebChromeClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框,得到他们的消息内容,然后解析即可。常用的拦截是JS的输入框(prompt),因为只有prompt()可以返回任意类型的值,操作最全面方便,更加灵活,而alert()对话框没有返回值,confirm()对话框只能返回两种状态(确定/取消)两个值。

         同样,在JS代码中定义规则

function  callandroid3(){

  var  msg = prompt("js://prompt?msg1=222&msg2=555");

  document.getElementById("div2").innerHTML=msg;

 }

在Activity中当使用webview.loadUrl()加载了JS代码后,就会触发回调onJsPrompt()方法,重写此方法如下:

webView.setWebChromeClient(new WebChromeClient(){
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        Uri uri = Uri.parse(message);
        if(uri.getScheme().equals("js")){
            if(uri.getAuthority().equals("prompt")){
                Log.e("eee",uri.getQueryParameter("msg1"));
                Log.e("eee",uri.getQueryParameter("msg2"));
                result.confirm("哈哈哈哈哈哈");
            }
            return true;
        }
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }

        并在代码中通过result.confirm()给JS返回值,在上述JS代码中,msg的值就是得到的返回值(我就是觉得溜溜溜!)

        实现的结果是这样的:黄色div原来的文字内容是“巴拉巴拉巴拉”,点击第三个按钮之后,文字内容变为“溜溜溜溜溜溜”。


image.png

'


image.png
给出三种方式的运用场景
image.png
上一篇下一篇

猜你喜欢

热点阅读