概述
从去年4月项目就一直用起了JsBridge,前面也针对jsBridge使用姿势介绍过一篇入门篇,《Android JsBridge实战 打造专属你的Hybrid APP》,本篇接着继续深入,通过再次优化封装,大大优化了部分代码,简化上层调用流程,快速部署你的Hybridge APP。
再进行具体编码前 ,我先进行了一般商业APP对WebView的需求
- 可加载本地和云端H5
- 拥有cookie持久能力
- 添加公共参数
- 回退前进功能
- Js与本地navtive交互
- 拥有加载默认错误页面能力
- 加载网页可展现进度
好为了满足以上常用功能,大致对webview相关知识进行下普及。
#WebView
谷歌提供的系统组件,用来加载和展现html网页,其采用webkit内核驱动,来实现网页浏览功能。
拥有load() URL和本地html文件
1 2 3 4
| 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)
1
| view.loadUrl(url, header);
|
为了方便上层开发者调用,可以将此code加入到WebViewClient
的shouldOverrideUrlLoading
中执行
姿势那就是这样:
1 2 3 4 5 6
| 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()
,姿势如下:
1 2 3 4
| public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { view.loadUrl(this.onPageError(failingUrl)); }
|
onPageHeaders()
便是上层抽象出来的接口,方便我们直接加入header,而onPageError()
是方便指定加载错误页面,那么在activity中就是这样了,
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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的进度,你也可以设置网页标题,甚至可以全屏!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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); } @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同步了,
1 2 3 4 5 6 7 8 9 10 11 12
| CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptCookie(true); cookieManager.removeSessionCookie(); List<String> cookies = getCookies(customCookies); for (String cookie : cookies) { cookieManager.setCookie(uri.getHost(), cookie); } CookieSyncManager.getInstance().sync();
|
这里需要注意棒棒糖以上的会出现无法同步问题那么请这样做
1 2 3 4 5 6
| if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { cookieManager.flush(); } else { CookieSyncManager.getInstance().sync(); }
|
经测试,完美!
你可能想问?我想自定义像header一样加入一些自定义cookie,行,没问题,继续看!
1 2 3 4 5 6 7 8
| 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
缓存相关:
1 2 3 4
| clearCache(boolean) CacheManager.clear
|
在4.4以上的此api已经无法使用,也就是说缓存清空涉及安全,需要你自己去实现,就类似picasso, okhttp缓存,一样要开发者自我去实现。
当然也可以这样:
1 2
| WebView.clearCache(true)
|
清空历史记录
1 2
| mWebview.clearHistory()
|
需要在onPageFinished()
的方法之后调用
ProgressBarWebView
学习了上面基础知识,我这里就开始进行自定义的进度条ProgressBarWebView的封装了,这里我直接对BridgeWebView进行扩展。下面是主要部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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能有后退功能!我屏蔽了长按事件,并且对返回键建进行了拦截。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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
1 2 3 4 5 6 7 8
| @Override public void onDestroy() { super.onDestroyView(); if (mProgressBarWebView.getWebView() != null) { mProgressBarWebView.getWebView().destroy(); } }
|
看了构造方法你已明白,里面包含一个BridgeWebView和一个NumberProgressBar 成员属性,
接着就是对JsBridge进行封装了
Java调用js代码:
1 2 3 4 5 6 7 8 9 10 11 12
| 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
1 2 3 4 5 6 7 8 9 10
| 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源码中没这个东东的, 是为了方便上层调用我自己封装的接口,
1 2 3
| public interface JsHandler { void OnHandler(String var1, String var2, CallBackFunction var3);
|
好了 关键的东西已经介绍完,如果对jsBridge可以看看去年我写的一篇对他的介绍:Android JsBridge实战 打造专属你的Hybrid APP,
接着使用我们封装好的ProgressBarWebView
案列使用
#Dependencies
Gradle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| root: repositories { maven { url "https://jitpack.io" } jcenter() } Module: dependencies { ..... compile 'com.tamic:browse:1.0.0' }
|
初始化
1
| ProgressBarWebView mProgressBarWebView = (ProgressBarWebView) findViewById(R.id.login_progress_webview);
|
设置自定义WebViewClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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 null; } });
|
加载指定Url
1 2
| mProgressBarWebView.loadUrl("file:///android_asset/demo.html")
|
当然,也可以支持网络url;
注册Js回调函数
1 2 3 4 5 6
| ArrayList<String> mHandlerNames = new ArrayList<>(); mHandlers.add("login"); mHandlers.add("callNative"); mHandlers.add("callJs"); mHandlers.add("open");
|
回调js的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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
1 2 3 4 5 6
| 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
1 2 3 4 5 6 7 8
| 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!
效果图:
#结束语
这里感谢曾技术经理出身的同事的对相关部分代码的封装,感谢振南同学!
第一时间获资讯请关注微信公众号!