本文發(fā)表于KuTear's Blog,轉(zhuǎn)載請注明
開源方案
實現(xiàn)
Jockey
基本原理
復(fù)寫WebViewClient
的shouldOverrideUrlLoading()
,通過對URL
的scheme和action來判斷是否由App執(zhí)行某個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(...)
函數(shù)中
@SuppressLint("SetJavaScriptEnabled")
@Override
public void configure(WebView webView) {
webView.getSettings().setJavaScriptEnabled(true);
//設(shè)置默認(rèn)的WebViewClient
webView.setWebViewClient(this.getWebViewClient());//JockeyWebViewClient
}
在設(shè)置自定義WebViewClient時不能直接在WebView設(shè)置,因為這樣會覆蓋底層的JockeyWebViewClient
,需要使用
mJockey.setWebViewClient(myWebViewClient);
在這里的看看實現(xiàn)
@Override
public void setWebViewClient(WebViewClient client) {
//裝飾模式
this._client.setDelegate(client); //這里的_client就是JockeyWebViewClient
}
核心代碼就在JockeyWebViewClient
的shouldOverrideUrlLoading(...)
函數(shù)
@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進(jìn)行處理
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進(jìn)行處理
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)); //參數(shù)解析,在自己項目中可以替換為自己的解析方式
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類型,如本節(jié)開始的'log'
if (this.handles(type)) {
JockeyHandler handler = _listeners.get(type);
//每一個事件都有一個對WebView js的回調(diào)
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);
}
});
}
});
}
}
現(xiàn)在可以反過來看看本節(jié)開始的mJsckey.on()
方法的實現(xiàn)
@Override
public void on(String type, JockeyHandler... handler) {
if (!this.handles(type)) {
_listeners.put(type, new CompositeJockeyHandler());
}
_listeners.get(type).add(handler); //又是裝飾模式
}
CompositeJockeyHandler
的add()
的實現(xiàn)
public void add(JockeyHandler ... handler) {
_handlers.addAll(Arrays.asList(handler));
}
就是把自定義的JockeyHandler
添加到變量_handlers
中去了.
現(xiàn)在可以知道上面_listeners.get(type)
得到的一定是CompositeJockeyHandler
,執(zhí)行perform
函數(shù),
@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);
}
在此,我們就可以看見,事件到達(dá)用戶自已的JockeyHandler
的perform()
,perform()
又調(diào)用onPerform()
函數(shù).OK,現(xiàn)在自己處理事件咯~
對于Java調(diào)用js部分,原理就更加的簡單,就一句話
webView.load("javascript:XXXXXXX");
JsBridge
JsBridge
是大頭鬼的開源項目,star和fork的量很高,但是我個人覺得他的實現(xiàn)并沒有Jockey
好,比如對自定義的WebViewClient
,它要添加WebViewClient
必須重寫BridgeWebView
,并且WebViewClient
必須是BridgeWebViewClient
的子類.這樣不僅對開發(fā)者來說使用復(fù)雜程度提升了,另外暴露了太多開發(fā)者本不需要關(guān)注的接口.
基本原理
同Jockey
一樣,復(fù)寫WebViewClient
的shouldOverrideUrlLoading()
方法.源碼部分也是基本和上面一致,這里不再多說.
RainbowBridge
基本原理
RainbowBridge
的原理和上面兩個不同,上面是通過URL
來觸發(fā)事件的執(zhí)行,而RainbowBridge
是通過Js
來控制事件的執(zhí)行.Android在WebChromeClient
有對一些原生的Js
事件進(jìn)行捕獲,最常見的就是alert
對話框,但是alert
對話框在原本Js
中出現(xiàn)的頻率較高,如果我們對這個事件攔截掉,可能有一些異常,所以可以攔截一個相對較少出現(xiàn)的事件,通過處理這個事件的信息從而達(dá)到執(zhí)行事件.
源碼解讀
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); //解析參數(shù)到mParms
invokeNativeMethod(webView); //根據(jù)參數(shù)掉本地方法,這里使用了反射. 實現(xiàn)比較靈活
}
同樣,對于原生發(fā)送消息到Js
依舊是使用WebView.load("JavaScript:XXXX")
.