Tamic

http://blog.csdn.net/sk719887916/article/details/52402470

概述

从去年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"); 

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!

结束语

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

Android基于JsBridge封装的高效带加载进度的WebView的更多相关文章

  1. 带加载进度的Web图片懒加载组件Lazyload

    在Web项目中,大量的图片应用会导致页面加载时间过长,浪费不必要的带宽成本,还会影响用户浏览体验. Lazyload 是一个文件大小仅4kb的图片懒加载组件(不依赖其它第三方库),组件会根据用户当前浏 ...

  2. 基于better-scroll封装一个上拉加载下拉刷新组件

    1.起因 上拉加载和下拉刷新在移动端项目中是很常见的需求,遂自己便基于better-scroll封装了一个下拉刷新上拉加载组件. 2.过程 better-scroll是目前比较好用的开源滚动库,提供很 ...

  3. Android Volley和Gson实现网络数据加载

    Android Volley和Gson实现网络数据加载 先看接口 1 升级接口 http://s.meibeike.com/mcloud/ota/cloudService POST请求 参数列表如下 ...

  4. Android中ViewPager+Fragment取消(禁止)预加载延迟加载(懒加载)问题解决方案

    转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53205878本文出自[DylanAndroid的博客] Android中Vie ...

  5. 【Android】再来一篇Fragment懒加载(只加载一次哦)

    效果 老规矩,先来看看效果图 没错,我又入坑了,又重新做了个 Gank 客户端,因为之前那个代码写得太烂了,这次有好好的考虑了下架构之类的事,代码应该会更容易读懂了点了,吧.哈哈,再次欢迎来 star ...

  6. 携程Android App的插件化和动态加载框架

    携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验.本文将详细介绍Android平台插件式开发和动态加载技术的原理和实 ...

  7. 我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)

    正如在<我的Android进阶之旅------>Android疯狂连连看游戏的实现之状态数据模型(三)>一文中看到的,在AbstractBoard的代码中,当程序需要创建N个Piec ...

  8. Android ProgressDialog 加载进度

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools= ...

  9. AngularJS进阶(三十九)基于项目实战解析ng启动加载过程

    基于项目实战解析ng启动加载过程 前言 在AngularJS项目开发过程中,自己将遇到的问题进行了整理.回过头来总结一下angular的启动过程. 下面以实际项目为例进行简要讲解. 1.载入ng库 2 ...

随机推荐

  1. 开发一款APP所需要的时间

    "要多少钱""要多少时间"这应该是一个企业在打算开发一款APP时问到最多的问题了.的确,现在的人不管做什么事情都讲究计划,更何况在这个时间就是金钱的时代,企业如 ...

  2. Django REST framework+Vue 打造生鲜超市(十二)

    十三.首页.商品数量.缓存和限速功能开发  13.1.轮播图接口实现 首先把pycharm环境改成本地的,vue中local_host也改成本地 (1)goods/serializer class B ...

  3. js 函数 作用域 全局作用域 局部作用域 闭包

    一个变量没有声明但调用 直接报错,声明没有赋值会显示未定义. 作用域 作用域(scope):一条数据可以在哪个范围中使用. 通常来说,一段程序代码中所用到的数据并不总是有效/可用的,而限定这个数据的可 ...

  4. MySql 使用规范推荐

    前言 废话不多说-- 一.基础规范 1.使用InnoDB存储引擎 支持事务.行级锁.并发性能更好.CPU及内存缓存页优化使得资源利用率更高 2.推荐使用utf8mb4字符集 无需转码,无乱码风险, 支 ...

  5. java小白设计模式之观察者模式

    观察者模式: 对象之间多对一依赖的一种设计方案,被依赖对象为Subject(一),依赖对象为Observer(多),Subject通知Observer变化直接代码: package com.wz.tw ...

  6. Java必须了解的“递归”与“IO流”!!!

    >>>First: 递归! 1. 定义: 在函数自身内部,调用函数本身的方式,称为递归. 2. 注意: 递归包括递进去.归出来两步. 首先,依次执行[函数调自身语句]上半部分的代码, ...

  7. [SPOJ 4155]OTOCI

    Description 题库链接 给你 \(n\) 个节点,让你兹磁以下操作,维护一棵树: 动态加边: 修改点权: 询问路径上点权和. \(1\leq n\leq 30000\) Solution 好 ...

  8. Prison 监狱

    [题目描述]Caima 王国中有一个奇怪的监狱,这个监狱一共有 P 个牢房,这些牢房一字排开,第 i 个仅挨着第 i+1 个(最后一个除外).现在正好牢房是满的.上级下发了一个释放名单,要求每天释放名 ...

  9. 洛谷P2221 [HAOI2012]高速公路

    线段树 #include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> ...

  10. bzoj 5285: [Hnoi2018]寻宝游戏

    Description Solution 把输入的 \(n\) 个二进制数看作一个大小为 \(n*m\) 的矩阵 把每一列压成一个二进制数,其中最高位是最下面的元素 然后就有了 \(m\) 个二进制数 ...