WebView开发详解

目录

大纲_02.png
一.前言

现在很多app并不是纯原生开发,而是会嵌套网页,比如一些经常会变动的页面往往会采用嵌套h5网页的形式展现。Android中就有一个专门用来加载html网页的组件,这个组件就是Webview。

二.概述

webview是Android开发中常见的控件,内部实现是采用渲染引擎来展示内容,Android4.4以前采用Webkit渲染引擎,4.4版本及以后开始采用chromium渲染引擎来渲染。

三.使用介绍
3.1 基本使用

(1)xml布局

<WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

(2)activity或fragment中加载webview控件

WebView webView =(WebView) findViewById(R.id.webview);
//或者
WebView webView =new WebView(this);

(3)添加网络权限

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

(4)访问网页

webview.loadurl("https://feibendexiaoma.github.io");

Tip : 如果为了避免内存泄漏可以在xml中定义一个viewgroup,如linearLayout,然后在代码中动态创建webView

3.2 常用方法详解

3.2.1 加载url,这个url可以是网络的url,可以是手机里的html网页url,也可以是assets文件夹下的网页url

webview.loadurl("https://feibendexiaoma.github.io");
webview.loadurl("content://com.android.htmlfileprovider/sdcard/index.html");
webview.loadurl("file:///android_asset/index.html");
//加载url,并添加HTTP headers
webview.loadUrl(String url, Map<String, String> additionalHttpHeaders);

3.2.2 加载网页中的一部分代码

/**
* data:要加载的内容 e.g. 'text/html'. If null,defaults to 'text/html'.
  mimeType:data数据类型的mime类型
  encoding:data的编码格式  如果data是base64编码,则encoding必须为"base64"
*/
webview.loadData(String data, String mimeType, String encoding)

 /**
 * baseUrl 加载的base url的网页内容
   historyUrl 历史记录条目的url 如果为空默认跳转到“about :blank”,如果非null,则必须是有效的url
 */
webview.loadDataWithBaseURL(String baseUrl, String data,
            String mimeType, String encoding, String historyUrl)

3.2.3 前进后退

webview.canGoBack()//是否能后退
webview.goBack();//后退
webview.canGoForward();//是否能前进
webview.goForward();//前进
webview.goBackOrForward(int steps);//以当前的index为起始点前进或后退到历史记录中指定的steps,steps为正则为前进,steps为负则为后退

Tip: 一般常用的是后退的方法

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            clickBtnReback();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

   public void clickBtnReback() {
      if (mCustomWebView.canGoBack()) {//能回退时回退
            mCustomWebView.goBack();
        }else {
            ActivityStack.getInstance().closeActivity(mActivity);
        }
    }

3.2.4 清除缓存

//清除资源缓存,由于内核缓存是全局的 ,因此这里针对的是整个应用程序
public void clearCache(boolean includeDiskFiles)
//清除自动完成填充的表单数据,不会清除WebView存储到本地的数据
public void clearFormData() 
 //清除webview访问的历史记录,清除其前进后退列表里的缓存,除了当前访问记录
public void clearHistory ()

3.2.5 WebView的状态

//激活webview为活跃状态,能正常执行网页的响应 在前一个页面执行完onPause()后会执行这个方法
public void onResume () 
//当前页面失去焦点被切换为不可见状态,会执行onPause,会尝试暂停所有可以安全暂停的动作,如DOM的解析、plugin的执行,注意不会暂停javascript。要想暂停javascript可以用pauseTimers
public void onPause()
//当应用程序被切换到后台时,我们使用了webview,这个方法不仅针对当前webview,而是全局应用程序的Webview,会暂停所有webview的layout,解析和JavaScript
public void pauseTimers ()
//恢复所有Webview的所有布局,解析和JavaScript计时器    
public void resumeTimers()
 //调用destroy时,必须保证Webview已经从view tree中被删除了,可以在之前调用根布局的removeView方法将webview移除
public void destroy () 
3.3 相关设置介绍

3.3.1.WebSettings设置

WebSettings mSettings = this.getSettings();
mSettings.setJavaScriptEnabled(true);// 开启javascript 如果访问的页面中有与js交互,则必须开启
mSettings.setJavaScriptCanOpenWindowsAutomatically(true);////支持通过JS打开新窗口
mSettings.setDomStorageEnabled(true);// 开启DOM
mSettings.setDatabaseEnabled(true);//开启database
mSettings.setDefaultTextEncodingName("utf-8");// 设置字符编码
mSettings.setAllowFileAccess(true);// 设置支持文件流

//LOAD_DEFAULT默认缓存模式 根据cache-control决定是否从网络上取数据
//LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
////LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
mSettings.setCacheMode(WebSettings.LOAD_DEFAULT);// 设置缓存模式

mSettings.setSupportZoom(false);// 支持缩放
mSettings.setBuiltInZoomControls(false);// 设置内置的缩放控件

//以下两个结合使用
mSettings.setUseWideViewPort(true);// 调整图片适合webview大小
mSettings.setLoadWithOverviewMode(true);// 调整到屏幕大小

mSettings.setDefaultZoom(ZoomDensity.FAR);// 屏幕自适应网页,如果没有这个,在低分辨率的        手机上显示可能会异常
mSettings.setRenderPriority(RenderPriority.HIGH);
// 提高网页加载速度,暂时阻塞图片加载,然后网页加载好了,在进行加载图片
mSettings.setBlockNetworkImage(true);
        mSettings.setAppCacheEnabled(true);// 开启缓存机制

3.3.2 WebViewClient
用于处理请求事件,加载设置。
常用方法:

webview.setWebViewClient(new WebViewClient(){
    /**
    * 重写此方法表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边
    */
      @Override
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
     if (url.endsWith(".apk")) {//添加下载
          WebActivity.this.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
     } else {
         view.loadUrl(url);
     } 
     /**
         * 页面开始加载调用的方法
         *
         */
        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
        }
         
       /**
         * 页面加载完成回调的方法
         *
         */
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);

        }
        /**
         * 页面加载过程中,加载资源回调的方法,每一个资源(比如图片)的加载都会调用一次
         * 
         */
        @Override
        public void onLoadResource(WebView view, String url) {
            super.onLoadResource(view, url);
        }
         /**
         * 页面加载出错的时候调用 如404
           在这里可以自定义错误页面
         */
       @Override
        public void onReceivedError(WebView view, WebResourceRequest request,WebResourceError error) {
            super.onReceivedError(view, request, error);
        }
         /**
         *加载网页发生证书认证错误时
         */
         @Override    
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {       
          handler.cancel();      //取消加载
         // handler.proceed();    //如果要上传到google play ,回调中使用此方法是无法通过审核的
        // handler.handleMessage(null);    //可做其他处理
        }    
 }
});

3.3.3 WebChromeClient
用于处理加载条,对话框,网站标题等
常见方法:

webview.setWebChromeClient(new WebChromeClient(){
    /**
    * 处理进度条
    */
      @Override
     public void onProgressChanged(WebView view, int newProgress) {
           if (newProgress >= 100) {
                 myProgressBar.setVisibility(View.GONE);
            } else {
                 if (View.GONE == myProgressBar.getVisibility()) {
                       myProgressBar.setVisibility(View.VISIBLE);
                 }
                 myProgressBar.setProgress(newProgress);
            }
            super.onProgressChanged(view, newProgress);
     }
    /**
    * 获取网站的标题
    */
     @Override
    public void onReceivedTitle(WebView view, String title) {
       titleview.setText(title);
    }
    /**
    * 支持js弹alert窗处理
    */
     @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            new AlertDialog.Builder(MainActivity.this)
                    .setTitle("JsAlert")
                    .setMessage(message)
                    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    })
                    .setCancelable(false)
                    .show();
            return true;
        }
    /**
    *支持js 确认框处理
    */
      @Override
        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
            return super.onJsConfirm(view, url, message, result);
        }
});
3.4 相关问题

3.4.1 内存泄漏
Webview为什么会发生内存泄漏?
Webview会关联一个activity,webview内部执行的操作是在新的线程中,这个线程的生命周期和activity的生命周期是不一样的,导致webview一直持有activity的引用不能回收。和匿名内部类持有外部类的引用导致外部类无法回收原理一样。

首先应该做的就是不能在xml中定义webview节点,而是在需要的时候动态生成。即:可以在使用WebView的地方放置一个LinearLayout类似ViewGroup的节点,然后在要使用WebView的时候,动态生成. new WebView(getApplicationContext()) ;必须传入ApplicationContext如果传入Activity的Context的话,对内存的引用会一直被保持着。

LinearLayout.LayoutParams params =new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
WebView mWebView = new WebView(getApplicationContext());
mWebView.setLayoutParams(params);
mLayout.addView(mWebView);

在onDestroy()中将webview移除

  @Override
    protected void onDestroy() {
        if(mWebView!=null){
           ((ViewGroup) mWebView.getParent()).removeView(mWebView);
            mWebView.destroy();
            mWebView = null;
        }
        super.onDestroy();
    }

3.4.2 微信支付问题

我在开发时遇到这样的问题:当网页中有支付时,android自带的webview不能响应支付的相关协议,而ios的就可以,搜索网上的方法及开发者文档,解释是ios内置浏览器中只要输入相关协议都可以调起相关app,比如输入weixin://就可以调起微信,输入alipay://可以调起支付宝。
查看微信支付的开发者文档(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1)知道:
H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付。
主要用于触屏版的手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付。
微信支付的案例介绍给出了一个体验链接:http://wxpay.wxutil.com/mch/pay/h5.v2.php,在微信外浏览器打开,是可以调起微信支付的,看来微信是支持浏览器调起微信支付的。

解决方案
方案一
由于微信的H5支付协议可以在浏览器中调起微信,那么如果我们的app打开第三方网页的是手机浏览器的话,就不用我们做什么,就可以直接调起微信支付了。但是我们一般是在webview中调起微信支付,而直接在浏览器中打开的非常少,所以这种方法作为参考

方案二
由于以前的微信支付的协议为weixin://wap/pay?,后来改版之后url中就没有这个了,替换为https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?这个开头的url了,其实方案二也是参考的方案一,可以通过判断支付前一步的网页地址,在支付的前一步就跳到手机浏览器内,通过浏览器调起微信支付

public boolean shouldOverrideUrlLoading(WebView view, String url) {
               try {
                    if (url.startsWith("http://dh.xxx.cn/index/order/pay")) {//在支付的前一个网页就跳转出去用浏览器调支付
                        Intent intent = new Intent();
                        intent.setAction(Intent.ACTION_VIEW);
                        intent.setData(Uri.parse(url));
                        startActivity(intent);
                        return true;
                    }

因涉及到公司网址,就以xxx代表了,亲测这样是可以通过手机浏览器调起微信支付的。

3.5 与Js交互

3.5.1 Android调用js
(1)首先要想调用网页上js脚本,webview必须支持java script

mWebView.getSettings().setJavaScriptEnabled(true);//支持javascript
// 设置允许JS弹窗 测试使用
mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);

(2)js代码 test.html 放到src\main\assets下

<!DOCTYPE html>
<html>
    <script type="text/javascript">

      //android 要调用的方法
       function helloworld(){
         alert("helloworld")
       }
       
       function alertMsg(msg){
         alert(msg)
       }
       
       function toastMsg(msg){
          window.web2app.toastMsg(msg)
       }
       
       function add(num1,num2){
          window.web2app.add(num1+num2)
       }
    </script>
     java-js in Android
</html>

(3)java 调用js的方法,注意这里的方法名要和test.html里的名称对应

String method ="javascript:helloworld()";//无参数调用
      method ="javascript:alertMsg(\""+ "content" +"\")";//有参数调用 注意对于字符串作为参数值需要进行转义双引号
   mWebView.loadUrl(method);

(4)调用setWebChromeClient处理弹窗,这一步只为测试时用

mWebView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder ad = new AlertDialog.Builder(WebTestActivity.this);
                ad.setTitle("Java 调用了JS方法");
                ad.setMessage(message);
                ad.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                ad.setCancelable(false);
                ad.create().show();
                return true;
            }
        });

java整体代码

private void initView() {
        mWebView = (WebView)findViewById(R.id.webview);
        btnJava = (Button)findViewById(R.id.btn_java_to_js);
        WebSettings mWebSettings = mWebView.getSettings();
        mWebSettings.setJavaScriptEnabled(true);//开启支持js
        mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);//支持弹窗
        mWebView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                //处理弹窗后的样式
                AlertDialog.Builder ad = new AlertDialog.Builder(WebTestActivity.this);
                ad.setTitle("Java 调用了JS方法");
                ad.setMessage(message);
                ad.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                ad.setCancelable(false);
                ad.create().show();
                return true;
            }
        });
        btnJava.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //调用js方法
                        javaToJsMethod();
            }
        });
        mWebView.loadUrl("file:///android_asset/test.html");
    }

    private void javaToJsMethod() {
        String method ="javascript:helloworld()";
         method ="javascript:alertMsg(\""+ "content" +"\")";
        mWebView.loadUrl(method);
    }

效果:


java_01.png

java_02.png

Android4.4以后处理
另外在Android4.4之后引入了一个方法evaluateJavascript,因为该方法的执行不会使页面刷新,比loadurl更高效,但是只能用于4.4以后版本。

private void testEvaluateJavascript(WebView webView) {
  webView.evaluateJavascript("helloworld()", new ValueCallback<String>() {

  @Override
  public void onReceiveValue(String value) {
      //处理js 返回的结果
  }});
}

在使用时可以通过判断版本号,根据版本号是4.4以下的话使用loadurl,4.4以上版本可以使用evaluateJavascript

3.5.2 Js 调用Android的方法
(1)添加webview的addJavascriptInterface方法,并使用web2app作为注入接口名称,这个名称可以自定义,但是要和html中的名称一致
(2)定义一个与js对象映射关系的android 类 jsToJavaInteration,里面定义js要调用的方法

public class jsToJavaInteration{
        @JavascriptInterface
        public void toastMsg(String msg){
            Toast.makeText(getApplicationContext(),msg,Toast.LENGTH_SHORT).show();
        }

        @JavascriptInterface
        public void add(String result){
            Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
        }

    }

(3)加载js方法 ,这里的方法要和test.html中的方法名一致

private void javaToJsMethod() {
        String method ="javascript:helloworld()";
//         method ="javascript:alertMsg(\""+ "content" +"\")";
        method = "javascript:toastMsg(\"" + "content" + "\")";//有参调用
        method = "javascript:add(2,3)";//
        mWebView.loadUrl(method);
    }

(4)同上第四步
整体java类

private void initView() {
        mWebView = (WebView)findViewById(R.id.webview);
        btnJava = (Button)findViewById(R.id.btn_java_to_js);
        WebSettings mWebSettings = mWebView.getSettings();
        mWebSettings.setJavaScriptEnabled(true);
        mWebView.addJavascriptInterface(new jsToJavaInteration(),"web2app");
        mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
        mWebView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder ad = new AlertDialog.Builder(WebTestActivity.this);
                ad.setTitle("Java 调用了JS方法");
                ad.setMessage(message);
                ad.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                ad.setCancelable(false);
                ad.create().show();
                return true;
            }
        });
        btnJava.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                        javaToJsMethod();
            }
        });
        mWebView.loadUrl("file:///android_asset/test.html");
    }

    private void javaToJsMethod() {
         String method="";
 //        method ="javascript:helloworld()";
//         method ="javascript:alertMsg(\""+ "content" +"\")";
        method = "javascript:toastMsg(\"" + "content" + "\")";
        method = "javascript:add(2,3)";
        mWebView.loadUrl(method);
    }

    //js 调用java 类方法
    public class jsToJavaInteration{
        @JavascriptInterface
        public void toastMsg(String msg){
            Toast.makeText(getApplicationContext(),msg,Toast.LENGTH_SHORT).show();
        }

        @JavascriptInterface
        public void add(String result){
            Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
        }

    }

效果:


js_01.png

js_02.png
四.使用示例参照上面代码
五.总结

本节写了一些关于webview的知识,包括webview的概述,基本使用,常用方法介绍,相关设置,内存泄漏,webview遇到的问题,java与js的交互,并用一些代码做了说明,希望对你对我都有帮助,有不完善的后续补充哦。

头条号申请者 :飞奔的小付

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容