• Android基于JsBridge封装的高效带加载进度的WebView


    概述

    从去年4月项目就一直用起了JsBridge,前面也针对jsBridge使用姿势介绍过一篇入门篇,《Android JsBridge实战 打造专属你的Hybrid APP》,本篇接着继续深入,通过再次优化封装,大大优化了部分代码,简化上层调用流程,快速部署你的Hybridge APP。

    再进行具体编码前 ,我先进行了一般商业APP对WebView的需求

    • 可加载本地和云端H5
    • 拥有cookie持久能力
    • 添加公共参数
    • 回退前进功能
    • Js与本地navtive交互
    • 拥有加载默认错误页面能力
    • 加载网页可展现进度
    • 支持https

    好为了满足以上常用功能,大致对webview相关知识进行下普及。

    效果图:

    这里写图片描述

    WebView

    谷歌提供的系统组件,用来加载和展现html网页,其采用webkit内核驱动,来实现网页浏览功能。

    拥有load() URL和本地html文件

        // 云端
        webView.loadUrl("https://www.baidu.com"); 
        // 本地
        webView.loadUrl("file:///android_asset/demo.html"); 
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    WebViewClient

    WebViewClient主要辅助WebView执行处理各种响应请求事件的,比如:

    • onLoadResource
    • onPageStart
    • onPageFinish
    • onReceiveError
    • onReceivedHttpAuthRequest
    • shouldOverrideUrlLoading

    本次加载失败页面,和拦截加入header头必须用到它,由于Android无法拦截h5本身ajax的请求,所以对header同步不是很好,建议大家对于ajax请求采用cookie形式,以防止url参数服务端无法获取的问题。

    加入header 一般直接使用webView.load(url, header)

    view.loadUrl(url, header);
    

    为了方便上层开发者调用,可以将此code加入到WebViewClient 的shouldOverrideUrlLoading中执行 
    姿势那就是这样:

     public boolean shouldOverrideUrlLoading(WebView view, String url) {
       if(this.onPageHeaders(url) != null) {
         view.loadUrl(url, this.onPageHeaders(url));
     }
       return super.shouldOverrideUrlLoading(view, url);
    }
    

    错误页面也是复写WebViewClient的onReceivedError() 来加入自定义的抽象onPageError(),姿势如下:

    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        view.loadUrl(this.onPageError(failingUrl));
    }
    

    onPageHeaders()便是上层抽象出来的接口,方便我们直接加入header,而onPageError()是方便指定加载错误页面,那么在activity中就是这样了,

     mProgressBarWebView.setWebViewClient(new    CustomWebViewClient(mProgressBarWebView.getWebView()) {
    
            @Override
            public String onPageError(String url) {
                return "file:///android_asset/error.html";
            }
    
            @Override
            public Map<String, String> onPageHeaders(String url) {
                return CookieManger.getHeader(getContext());
            }
        });
    

    WebChromeClient

    主要辅助WebView处理JavaScript的对话框、网站Logo、网站title、load进度等处理。

    • onCloseWindow(关闭WebView)
    • onCreateWindow()
    • onJsAlert ()
    • onJsPrompt
    • onJsConfirm
    • onProgressChanged
    • onReceivedIcon
    • onReceivedTitle
    • onShowCustomView

    WebView只是用来处理一些html的页面内容,只用WebViewClient就行了,如果需要更丰富的处理效果,比如js、进度条等,就要用到WebChromeClient。因为这次功能要用加载进度,不得不说它。

    为了加入顶部的加载进度条,复写WebChromeClient中onProgressChanged,在这里更改我们加入的ProgressBar的进度,你也可以设置网页标题,甚至可以全屏!

     public class CustomWebChromeClient extends WebChromeClient {
    private NumberProgressBar mProgressBar;
    
    public CustomWebChromeClient(NumberProgressBar progressBar) {
        this.mProgressBar = progressBar;
    }
    
    public void onProgressChanged(WebView view, int newProgress) {
        if(newProgress >= 95) {
            this.mProgressBar.setVisibility(8);
        } else {
            if(this.mProgressBar.getVisibility() == 8) {
                this.mProgressBar.setVisibility(0);
            }
    
            this.mProgressBar.setProgress(newProgress);
        }
    
        super.onProgressChanged(view, newProgress);
    }
    
       //获取tittle
        @Override
        public void onReceivedTitle(WebView view, String title) {
            super.onReceivedTitle(view, title);
        }
    
        //全屏
        @Override
        public void onShowCustomView(View view, CustomViewCallback callback) {
            super.onShowCustomView(view, callback);
        }
    }
    

    好了准备好了同步Header和进度条之后,就的考虑cookie同步问题

    CookieSync

    CookieManager 
    CookieManager是用来管理Cookie的,主要来管理cookie相关,提供如下API

    • setAcceptCookie()
    • setCookie()
    • getCookie(String url);
    • removeSessionCookies();
    • hasCookies()
    • removeAllCookie()

    CookieSyncManager

    CookieSyncManagerl继承WebSyncManager,来管理同步cookie相关,主要有以下API

    • resetSync()
    • stopSync()
    • sync()
    • syncFromRamToFlash()
    • checkInstanceIsAllowed()

    你想问这些api什么意思,请保留点你的童真,不要问这么简单的问题好吗?

    接着我们就可以这样操作来实现cookie同步了,

        CookieManager cookieManager = CookieManager.getInstance();
       // 接受服务器cookie
        cookieManager.setAcceptCookie(true);
        //移除之前的cookie
        cookieManager.removeSessionCookie();
        // 注入cookies
        List<String> cookies = getCookies(customCookies);
        for (String cookie : cookies) {
            cookieManager.setCookie(uri.getHost(), cookie);
        }
        // 同步cookie
        CookieSyncManager.getInstance().sync();
    

    这里需要注意棒棒糖以上的会出现无法同步问题那么请这样做

             if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            cookieManager.flush();
        } else {
            CookieSyncManager.getInstance().sync();
        }
    

    测试,完美!

    你可能想问?我想自定义像header一样加入一些自定义cookie,行,没问题,继续看!

     public static List<String> createCustomCookies() {
           List<String> cookies = new ArrayList<>();
            cookies.add(“author ” + "= " + "tamic");
             cookies.add(“data” + "= " + "2016.8.15");
             cookies.add(“key” + "= " + 4fdfsfd34dfdfswer");
             cookies.add(“chanel” + "= " + "简书");
        return cookies;
    }
    

    很可能会遇到处理缓存问题,设置缓存webView缓存模式!这里在普及下相关姿势!

    缓存模式

    webview缓存模式有5种,具体方式: 
    - LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据 
    - LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据。 
    - LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式 
    - LOAD_NO_CACHE: 不使用缓存,只从网络获取数据. 
    - LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。

    www.baidu.com的cache-control为no-cache,在模式LOAD_DEFAULT下,无论如何都会从网络上取数据,如果没有网络,就会出现错误页面;在LOAD_CACHE_ELSE_NETWORK模式下,无论是否有网,只要本地有缓存,都会加载缓存。本地没有缓存时才从网络上获取, 
    这个和Http缓存一致,我不在过多介绍,如果你想自定义缓存策略和时间,可以尝试下,

    清除缓存

    CacheManager来处理webview缓存相关:

     clearCache(boolean)
    
      CacheManager.clear
    

    在4.4以上的此api已经无法使用,也就是说缓存清空涉及安全,需要你自己去实现,就类似picasso, okhttp缓存,一样要开发者自我去实现。
    当然也可以这样:

             WebView.clearCache(true);
    

    清空历史记录

       mWebview.clearHistory();
    

    需要在onPageFinished()的方法之后调用

    webview支持https

    webView.setWebViewClient(new SSLTolerentWebViewClient());
    webView.loadUrl(myhttps url);
    

    复写WebViewClient的nReceivedSslError函数,执行handler.cancel();给用户感知的话,来个对话框授权下就行了额

    private class SSLTolerentWebViewClient extendsWebViewClient {
       public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
    
      AlertDialog.Builder builder = new   AlertDialog.Builder(Tab1Activity.this);
      AlertDialog alertDialog = builder.create();
      String message = "SSL Certificate error.";
      switch (error.getPrimaryError()) {
        case SslError.SSL_UNTRUSTED:
         message = "The certificate authority is not trusted.";
          break;
        case SslError.SSL_EXPIRED:
        message = "The certificate has expired.";
         break;
        case SslError.SSL_IDMISMATCH:
        message = "The certificate Hostname mismatch.";
         break;
        case SslError.SSL_NOTYETVALID:
        message = "The certificate is not yet valid.";
        break;
    }
    
    message += " Do you want to continue anyway?";
    alertDialog.setTitle("SSL Certificate Error");
     alertDialog.setMessage(message);
    alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        // Ignore SSL certificate errors
        handler.proceed();
    }
    });
    
    alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
       @Override
        public void onClick(DialogInterface dialog, int which) {
    
         handler.cancel();
        }
        });
         alertDialog.show();
      }
    }
    

    ProgressBarWebView

    学习了上面基础知识,我这里就开始进行自定义的进度条ProgressBarWebView的封装了,这里我直接对BridgeWebView进行扩展。下面是主要部分。

     public class ProgressBarWebView extends LinearLayout {
       static final String TAG = ProgressBarWebView.class.getSimpleName();
       private NumberProgressBar mProgressBar;
        private BridgeWebView mWebView;
    
       public ProgressBarWebView(Context context) {
           super(context);
           this.init(context, (AttributeSet)null);
       }
    
       public ProgressBarWebView(Context context, AttributeSet attrs) {
          super(context, attrs);
          this.init(context, attrs);
      }
    
    @TargetApi(11)
    public ProgressBarWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.init(context, attrs);
    }
    
    @TargetApi(21)
    public ProgressBarWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        this.init(context, attrs);
    }
    

    为了使webview能有后退功能!我屏蔽了长按事件,并且对返回键建进行了拦截。

     mWebView.setOnLongClickListener(new OnLongClickListener() {
            public boolean onLongClick(View v) {
                return true;
            }
        });
        this.mWebView.setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if(event.getAction() == 0 && keyCode == 4 &&      ProgressBarWebView.this.mWebView.canGoBack()) {
                    ProgressBarWebView.this.mWebView.goBack();
                    return true;
                } else {
                    return false;
                }
            }
        });
    

    如果防止webview代码产生内存泄漏,请及时在activity销毁时,清空webview

     @Override
      public void onDestroy() {
        super.onDestroyView();
        if (mProgressBarWebView.getWebView() != null) {
            mProgressBarWebView.getWebView().destroy();
        }
    }
    

    看了构造方法你已明白,里面包含一个BridgeWebView和一个NumberProgressBar 成员属性, 
    接着就是对JsBridge进行封装了

    Java调用js代码

    public void registerHandler(final String handlerName, final JsHandler handler) {
         this.mWebView.registerHandler(handlerName, new BridgeHandler() {
             public void handler(String data, CallBackFunction function) {
                 if(handler != null) {
                    handler.OnHandler(handlerName, data, function);
                 }
            }
        });
    }
    

    js调用Native

    public void callHandler(final String handlerName, String javaData, final JavaCallHandler handler) {
        this.mWebView.callHandler(handlerName, javaData, new CallBackFunction() {
            public void onCallBack(String data) {
                if(handler != null) {
                    handler.OnHandler(handlerName, data);
                }
    
            }
        });
    }
    

    看可jsBridge的可能问这个JsHandler谁神马。本来在jsBridge源码中没这个东东的, 是为了方便上层调用我自己封装的接口,

     public interface JsHandler {
    void OnHandler(String var1, String var2, CallBackFunction var3);
    

    好了 关键的东西已经介绍完,如果对jsBridge可以看看去年我写的一篇对他的介绍:Android JsBridge实战 打造专属你的Hybrid APP

    接着使用我们封装好的ProgressBarWebView

    案列使用

    配置

    Gradle:

    root:

      repositories {
    maven { url "https://jitpack.io" }
    jcenter()
      }
    

    Module:

       dependencies {
       .....
       compile 'com.tamic:browse:1.0.0'
    
       }
    

    初始化

        ProgressBarWebView  mProgressBarWebView = (ProgressBarWebView)    findViewById(R.id.login_progress_webview);
    

    设置自定义WebViewClient

        mProgressBarWebView.setWebViewClient(new   CustomWebViewClient(mProgressBarWebView.getWebView()) {
    
            @Override
            public String onPageError(String url) {
                //指定网络加载失败时的错误页面
                return "file:///android_asset/error.html";
            }
    
            @Override
            public Map<String, String> onPageHeaders(String url) {
    
                // 可以加入header
    
                return null;
            }
        });
    

    加载指定Url

        mProgressBarWebView.loadUrl("file:///android_asset/demo.html");
    

    当然,也可以支持网络url;

    注册Js回调函数

        ArrayList<String> mHandlerNames = new ArrayList<>();
        mHandlers.add("login");
        mHandlers.add("callNative");
        mHandlers.add("callJs");
        mHandlers.add("open");
    

    回调js的方法

            mProgressBarWebView.registerHandlers(mHandlers, new JsHandler() {
            @Override
            public void OnHandler(String handlerName, String responseData, CallBackFunction function) {
    
                if (handlerName.equals("login")) {
    
                    Toast.makeText(MainActivity.this, responseData, Toast.LENGTH_SHORT).show();
    
                } else if (handlerName.equals("callNative")) {
    
                    Toast.makeText(MainActivity.this, responseData, Toast.LENGTH_SHORT).show();
    
                    function.onCallBack("我在上海");
    
                } else if (handlerName.equals("callJs")) {
    
                    Toast.makeText(MainActivity.this, responseData, Toast.LENGTH_SHORT).show();
    
                    // 想调用你的方法:
                    function.onCallBack("好的 这是图片地址 :xxxxxxx");
    
                } if (handlerName.equals("open")) {
    
                    mfunction = function;
    
                    pickFile();
    
                }
    
            }
        });
    

    Native调用js

        mProgressBarWebView.callHandler("callNative", "hello H5, 我是java", new JavaCallHandler() {
            @Override
            public void OnHandler(String handlerName, String jsResponseData) {
                Toast.makeText(MainActivity.this, "h5返回的数据:" + jsResponseData, Toast.LENGTH_SHORT).show();
            }
        });
    

    Native发送消息给js

        mProgressBarWebView.send("哈喽", new CallBackFunction() {
            @Override
            public void onCallBack(String data) {
                Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
            }
        });
    }
    

    Xml文件和js代码这里不做介绍,具体看项目案列中源码:

    GtiHub:https://github.com/NeglectedByBoss/JsWebView

    如果喜欢,请star!

    结束语

    这里感谢曾技术经理出身的同事的对相关部分代码的封装,感谢振南同学!

  • 相关阅读:
    记录两种获取配置文件的方法
    jsp-自定义标签
    转载 -jsp静态包含和动态包含的区别
    Linux基础知识笔记
    关于HTTP协议
    关于orcale创建type的一些小经验(遇到的坑)
    servlet处理乱码之post和get
    发布restful类型的接口
    ros2 dashing 安装失败指南
    exit回调
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/7274203.html
Copyright © 2020-2023  润新知