Android基于JsBridge封装的高效带加载进度的WebView
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的更多相关文章
- 带加载进度的Web图片懒加载组件Lazyload
在Web项目中,大量的图片应用会导致页面加载时间过长,浪费不必要的带宽成本,还会影响用户浏览体验. Lazyload 是一个文件大小仅4kb的图片懒加载组件(不依赖其它第三方库),组件会根据用户当前浏 ...
- 基于better-scroll封装一个上拉加载下拉刷新组件
1.起因 上拉加载和下拉刷新在移动端项目中是很常见的需求,遂自己便基于better-scroll封装了一个下拉刷新上拉加载组件. 2.过程 better-scroll是目前比较好用的开源滚动库,提供很 ...
- Android Volley和Gson实现网络数据加载
Android Volley和Gson实现网络数据加载 先看接口 1 升级接口 http://s.meibeike.com/mcloud/ota/cloudService POST请求 参数列表如下 ...
- Android中ViewPager+Fragment取消(禁止)预加载延迟加载(懒加载)问题解决方案
转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53205878本文出自[DylanAndroid的博客] Android中Vie ...
- 【Android】再来一篇Fragment懒加载(只加载一次哦)
效果 老规矩,先来看看效果图 没错,我又入坑了,又重新做了个 Gank 客户端,因为之前那个代码写得太烂了,这次有好好的考虑了下架构之类的事,代码应该会更容易读懂了点了,吧.哈哈,再次欢迎来 star ...
- 携程Android App的插件化和动态加载框架
携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验.本文将详细介绍Android平台插件式开发和动态加载技术的原理和实 ...
- 我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)
正如在<我的Android进阶之旅------>Android疯狂连连看游戏的实现之状态数据模型(三)>一文中看到的,在AbstractBoard的代码中,当程序需要创建N个Piec ...
- Android ProgressDialog 加载进度
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools= ...
- AngularJS进阶(三十九)基于项目实战解析ng启动加载过程
基于项目实战解析ng启动加载过程 前言 在AngularJS项目开发过程中,自己将遇到的问题进行了整理.回过头来总结一下angular的启动过程. 下面以实际项目为例进行简要讲解. 1.载入ng库 2 ...
随机推荐
- getgpc($k, $t='GP'),怎么返回的是 NULL?
<?php /** * 实用小代码 * 获得GET POST COOKIS */ $html=<<<WORD <form method="post"& ...
- MyBatis基础学习笔记--自总结
一.MyBatis和jdbc的区别 jdbc的过程包括: 1.加载数据库驱动. 2.建立数据库连接. 3.编写sql语句. 4.获取Statement:(Statement.PrepareStatem ...
- [LeetCode] Perfect Number 完美数字
We define the Perfect Number is a positive integer that is equal to the sum of all its positive divi ...
- 机器学习技法:03 Kernel Support Vector Machine
Roadmap Kernel Trick Polynomial Kernel Gaussian Kernel Comparison of Kernels Summary
- [USACO08JAN]haybale猜测Haybale Guessing
题目描述 The cows, who always have an inferiority complex about their intelligence, have a new guessing ...
- 例 7-10 uva12212(迭代加深搜索)
题意:对于一段数字,每次可以剪切一段连续的自然数,粘贴到任意部分,使其变成升序 思路: 考虑的是进行搜索,深搜并不能保证是最短,广搜感觉每层的情况太多. 迭代加深:枚举搜索深度,然后进行深搜. 这种方 ...
- POJ 3045 Cow Acrobats
Description Farmer John's N (1 <= N <= 50,000) cows (numbered 1..N) are planning to run away a ...
- bzoj4596[Shoi2016]黑暗前的幻想乡 Matrix定理+容斥原理
4596: [Shoi2016]黑暗前的幻想乡 Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 464 Solved: 264[Submit][Sta ...
- JS按照指定的周期来调用函数方法
setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式. setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭.由 s ...
- day5 liaoxuefeng---virtualenv、图形界面、网络编程、电子邮件
一.virtualenv 二.图形界面 三.网络编程 四.电子邮件