本文发表于KuTear's Blog,转载请注明
开源方案
实现
Jockey
基本原理
复写WebViewClient
的shouldOverrideUrlLoading()
,通过对URL
的scheme和action来判断是否由App执行某个Action.
使用
mJockey = JockeyImpl.getDefault();
mJockey.configure(mWebView);
mJockey.setWebViewClient(mWebViewClient);
mJockey.on("log", new JockeyHandler() { //添加Action
@Override
public void doPerform(Map<Object, Object> payload) {
String value = "color=" + payload.get("color");
Log.d("jockey", value);
}
});
源码解读
在JockeyImpl
的configure(...)
函数中
@SuppressLint("SetJavaScriptEnabled")
@Override
public void configure(WebView webView) {
webView.getSettings().setJavaScriptEnabled(true);
//设置默认的WebViewClient
webView.setWebViewClient(this.getWebViewClient());//JockeyWebViewClient
}
在设置自定义WebViewClient时不能直接在WebView设置,因为这样会覆盖底层的JockeyWebViewClient
,需要使用
mJockey.setWebViewClient(myWebViewClient);
在这里的看看实现
@Override
public void setWebViewClient(WebViewClient client) {
//装饰模式
this._client.setDelegate(client); //这里的_client就是JockeyWebViewClient
}
核心代码就在JockeyWebViewClient
的shouldOverrideUrlLoading(...)
函数
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (delegate() != null
&& delegate().shouldOverrideUrlLoading(view, url))
return true; //用户的Client处理
try {
URI uri = new URI(url);
if (isJockeyScheme(uri)) { //判读是否为 jockey://开始
processUri(view, uri); //对URL进行处理
return true;
}
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (HostValidationException e) {
e.printStackTrace();
Log.e("Jockey", "The source of the event could not be validated!");
}
return false; //宿主不处理,由WebView自行处理
}
接着就是对感兴趣的URL进行处理
public void processUri(WebView view, URI uri)
throws HostValidationException {
String[] parts = uri.getPath().replaceAll("^\\/", "").split("/");
String host = uri.getHost();
JockeyWebViewPayload payload = checkPayload(_gson.fromJson(
uri.getQuery(), JockeyWebViewPayload.class)); //参数解析,在自己项目中可以替换为自己的解析方式
if (parts.length > 0) {
if (host.equals("event")) {
getImplementation().triggerEventFromWebView(view, payload); //getImplementation() 返回 JockeyImpl
} else if (host.equals("callback")) {
getImplementation().triggerCallbackForMessage(
Integer.parseInt(parts[0]));
}
}
}
protected void triggerEventFromWebView(final WebView webView,JockeyWebViewPayload envelope) {
final int messageId = envelope.id;
String type = envelope.type;//Action类型,如本节开始的'log'
if (this.handles(type)) {
JockeyHandler handler = _listeners.get(type);
//每一个事件都有一个对WebView js的回调
handler.perform(envelope.payload, new JockeyHandler.OnCompletedListener() {
@Override
public void onCompleted() {
// This has to be done with a handler because a webview load
// must be triggered
// in the UI thread
_handler.post(new Runnable() {
@Override
public void run() {
triggerCallbackOnWebView(webView, messageId);
}
});
}
});
}
}
现在可以反过来看看本节开始的mJsckey.on()
方法的实现
@Override
public void on(String type, JockeyHandler... handler) {
if (!this.handles(type)) {
_listeners.put(type, new CompositeJockeyHandler());
}
_listeners.get(type).add(handler); //又是装饰模式
}
CompositeJockeyHandler
的add()
的实现
public void add(JockeyHandler ... handler) {
_handlers.addAll(Arrays.asList(handler));
}
就是把自定义的JockeyHandler
添加到变量_handlers
中去了.
现在可以知道上面_listeners.get(type)
得到的一定是CompositeJockeyHandler
,执行perform
函数,
@Override
public void perform(String payload, OnCompletedListener listener) {
this._listener = listener;
this._accumulator = new AccumulatingListener();
doPerform(payload);
}
@Override
protected void doPerform(String payload) {
for (JockeyHandler handler : _handlers)
handler.perform(payload, this._accumulator);
}
在此,我们就可以看见,事件到达用户自已的JockeyHandler
的perform()
,perform()
又调用onPerform()
函数.OK,现在自己处理事件咯~
对于Java调用js部分,原理就更加的简单,就一句话
webView.load("javascript:XXXXXXX");
JsBridge
JsBridge
是大头鬼的开源项目,star和fork的量很高,但是我个人觉得他的实现并没有Jockey
好,比如对自定义的WebViewClient
,它要添加WebViewClient
必须重写BridgeWebView
,并且WebViewClient
必须是BridgeWebViewClient
的子类.这样不仅对开发者来说使用复杂程度提升了,另外暴露了太多开发者本不需要关注的接口.
基本原理
同Jockey
一样,复写WebViewClient
的shouldOverrideUrlLoading()
方法.源码部分也是基本和上面一致,这里不再多说.
RainbowBridge
基本原理
RainbowBridge
的原理和上面两个不同,上面是通过URL
来触发事件的执行,而RainbowBridge
是通过Js
来控制事件的执行.Android在WebChromeClient
有对一些原生的Js
事件进行捕获,最常见的就是alert
对话框,但是alert
对话框在原本Js
中出现的频率较高,如果我们对这个事件拦截掉,可能有一些异常,所以可以拦截一个相对较少出现的事件,通过处理这个事件的信息从而达到执行事件.
源码解读
public class JsBridgeWebChromeClient extends WebChromeClient {
@Override
public final boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
result.confirm();
JsCallJava.newInstance().call(view,message);//在这里处理事件
return true;
}
...
}
下面简单看一下对事件的处理.
public void call(WebView webView, String message) {
if (webView == null || TextUtils.isEmpty(message))
return;
parseMessage(message); //解析参数到mParms
invokeNativeMethod(webView); //根据参数掉本地方法,这里使用了反射. 实现比较灵活
}
同样,对于原生发送消息到Js
依旧是使用WebView.load("JavaScript:XXXX")
.