美文网首页
Android-WebView

Android-WebView

作者: 哎呦呦胖子斌 | 来源:发表于2018-11-26 11:28 被阅读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

    相关文章

      网友评论

          本文标题:Android-WebView

          本文链接:https://www.haomeiwen.com/subject/eugvqqtx.html