前言
在看這篇文章之前你要確保你有那麼一點點的js知識,沒錯只需要一點點,能看懂最簡單的代碼就可以。如果你之前沒接觸過js的話。。也沒關係,我會把其中對應的邏輯用語言表達出來。
爲什麼需要用到js呢,因爲前端體系中,像我們說的點擊按鈕這樣的邏輯都是放在js腳本中執行的,有點像我們Android中的model層。(由於本人對前端的知識也只是略知一二,這個比方可能不太恰當,見諒見諒)。所以說到hybrid通信,主要就是前端的js和我們Android端的通信。
傳統的JSInterface
首先先介紹一下最普通的一種通信方式,就是使用Android原生的JavascriptInterface來進行js和Java的通信。具體方式如下:
首先先看一段html代碼
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript">
function showToast(toast) {
javascript:control.showToast(toast);
}
function log(msg){
console.log(msg);
}
</script>
</head>
<body>
<input type="button" value="toast"
onClick="showToast('Hello world')" />
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
很簡單,一個button,點擊這個button就執行js腳本中的showToast方法。
而這個showToast方法做了什麼呢?
function showToast(toast) {
javascript:control.showToast(toast);
}
可以看到control.showToast,這個是什麼我們等下再說,下面看我們java的代碼。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="zjutkz.com.tranditionaljsdemo.MainActivity">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = (WebView)findViewById(R.id.webView);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsInterface(), "control");
webView.loadUrl("file:///android_asset/interact.html");
}
public class JsInterface {
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
log("show toast success");
}
public void log(final String msg){
webView.post(new Runnable() {
@Override
public void run() {
webView.loadUrl("javascript: log(" + "'" + msg + "'" + ")");
}
});
}
}
}
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
首先界面很簡單,一個WebView。在對應的activity中做的事也就幾件,首先打開js通道。
WebSettings webSettings = webView.getSettings()
webSettings.setJavaScriptEnabled(true)
然後通過WebView的addJavascriptInterface方法去注入一個我們自己寫的interface。
webView.addJavascriptInterface(new JsInterface(), "control");
public class JsInterface {
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
log("show toast success");
}
public void log(final String msg){
webView.post(new Runnable() {
@Override
public void run() {
webView.loadUrl("javascript: log(" + "'" + msg + "'" + ")");
}
});
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
可以看到這個interface我們給它取名叫control。
最後loadUrl。
webView.loadUrl("file:///android_asset/interact.html")
好了,讓我們再看看js腳本中的那個showToast()方法。
function showToast(toast) {
javascript:control.showToast(toast);
}
這裏的control就是我們的那個interface,調用了interface的showToast方法
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
log("show toast success");
}
可以看到先顯示一個toast,然後調用log()方法,log()方法裏調用了js腳本的log()方法。
function log(msg){
console.log(msg);
}
js的log()方法做的事就是在控制檯輸出msg。
這樣我們就完成了js和java的互調,是不是很簡單。但是大家想過這樣有什麼問題嗎?如果你使用的是AndroidStudio,在你的webSettings.setJavaScriptEnabled(true);這句函數中,AndroidStudio會給你一個warning。
這個提示的意思呢,就是如果你使用了這種方式去開啓js通道,你就要小心XSS攻擊了,具體的大家可以參考wooyun上的這篇文章。
雖然這個漏洞已經在Android 4.2上修復了,就是使用@JavascriptInterface這個註解。但是你得考慮兼容性啊,你不能保證,尤其在中國這樣碎片化嚴重的地方,每個用戶使用的都是4.2+的系統。所以基本上我們不會再利用Android系統爲我們提供的addJavascriptInterface方法或者@JavascriptInterface註解來實現js和java的通信了。那怎麼辦呢?方法都是人想出來的嘛,下面讓我們看解決方案。
JSBridge
JSBridge,顧名思義,就是和js溝通的橋樑。其實這個技術在Android中已經不算新了,相信有些同學也看到過不少實現方案,這裏說一種我的想法吧。其實說是我的想法,實際是公司裏的大牛實現的,我現在做的就是維護並且擴展,不過這裏還是拿出來和大家分享一下。
思路
首先先說思路,有經驗的同學可能都知道Android的WebView中有一個WebChromeClient類,這個類其實就是用來監聽一些WebView中的事件的,我們發現其中有三個這樣的方法。
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
這三個方法其實就對應於js中的alert(警告框),comfirm(確認框)和prompt(提示框)方法,那這三個方法有什麼用呢?前面我們說了JSBridge的作用是提供一種js和java通信的框架,其實我們可以利用這三個方法去完成這樣的事。比如我們可以在js腳本中調用alert方法,這樣對應的就會走到WebChromeClient類的onJsAlert()方法中,我們就可以拿到其中的信息去解析,並且做java層的事情。那是不是這三個方法隨便選一個就可以呢?其實不是的,因爲我們知道,在js中,alert和confirm的使用概率還是很高的,特別是alert,所以我們最好不要使用這兩個通道,以免出現不必要的問題。
好了,說到這裏我們前期的準備工作也就做好了,其實就是通過重寫WebView中WebChromeClient類的onJsPrompt()方法來進行js和java的通信。
有了實現方案,下面就是一些具體的細節了,大家有沒有想過,怎麼樣才能讓java層知道js腳本需要調用的哪一個方法呢?怎麼把js腳本的參數傳遞進來呢?同步異步的方式又該怎麼實現呢?下面提供一種我的思路。
首先大家都知道http是什麼,其實我們的JSBridge也可以效仿一下http,定義一個自己的協議。比如規定sheme,path等等。下面來看一下一些的具體內容:
hybrid://JSBridge:1538351/method?{“message”:”msg”}
是不是和http協議有一點像,其實我們可以通過js腳本把這段協議文本傳遞到onPropmt()方法中並且進行解析。比如,sheme是hyrid://開頭的就表示是一個hybrid方法,需要進行解析。後面的method表示方法名,message表示傳遞的參數等等。
有了這樣一套協議,我們就可以去進行我們的通信了。
代碼
先看一下我們html和js的代碼
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script src="file:///android_asset/jsBridge.js" type="text/javascript"></script>
</head>
<body>
<div class="blog-header">
<h3>JSBridge</h3>
</div>
<ul class="entry">
<br/>
<li>
toast展示<br/>
<button onclick="JsBridge.call('JSBridge','toast',{'message':'我是氣泡','isShowLong':0},function(res){});">toast</button>
</li>
<br/>
<li>
異步任務<br/>
<button onclick="JsBridge.call('JSBridge','plus',{'data':1},function(res){console.log(JSON.stringify(res))});">plus</button>
</li>
<br/>
<br/>
</ul>
</body>
</html>
(function (win, lib) {
var doc = win.document;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var JsBridge = win.JsBridge || (win.JsBridge = {});
var inc = 1;
var LOCAL_PROTOCOL = 'hybrid';
var CB_PROTOCOL = 'cb_hybrid';
var CALLBACK_PREFIX = 'callback_';
var Core = {
call: function (obj, method, params, callback, timeout) {
var sid;
if (typeof callback !== 'function') {
callback = null;
}
sid = Private.getSid();
Private.registerCall(sid, callback);
Private.callMethod(obj, method, params, sid);
},
onComplete: function (sid, data) {
Private.onComplete(sid, data);
}
};
var Private = {
params: {},
chunks: {},
calls: {},
getSid: function () {
return Math.floor(Math.random() * (1 << 50)) + '' + inc++;
},
buildParam: function (obj) {
if (obj && typeof obj === 'object') {
return JSON.stringify(obj);
} else {
return obj || '';
}
},
parseData: function (str) {
var rst;
if (str && typeof str === 'string') {
try {
rst = JSON.parse(str);
} catch (e) {
rst = {
status: {
code: 1,
msg: 'PARAM_PARSE_ERROR'
}
};
}
} else {
rst = str || {};
}
return rst;
},
registerCall: function (sid, callback) {
if (callback) {
this.calls[CALLBACK_PREFIX + sid] = callback;
}
},
unregisterCall: function (sid) {
var callbackId = CALLBACK_PREFIX + sid;
var call = {};
if (this.calls[callbackId]) {
call.callback = this.calls[callbackId];
delete this.calls[callbackId];
}
return call;
},
callMethod: function (obj, method, params, sid) {
params = Private.buildParam(params);
var uri = LOCAL_PROTOCOL + '://' + obj + ':' + sid + '/' + method + '?' + params;
var value = CB_PROTOCOL + ':';
window.prompt(uri, value);
},
onComplete: function (sid, data) {
var callObj = this.unregisterCall(sid);
var callback = callObj.callback;
data = this.parseData(data);
callback && callback(data);
}
};
for (var key in Core) {
if (!hasOwnProperty.call(JsBridge, key)) {
JsBridge[key] = Core[key];
}
}
})(window);
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
有前端經驗的同學應該能很輕鬆的看懂這樣的代碼,對於看不懂的同學我來解釋一下,首先看界面。
可以看到有兩個按鈕,對應着html的這段代碼
<br/>
<li>
toast展示<br/>
<button onclick="JsBridge.call('JSBridge','toast',{'message':'我是氣泡','isShowLong':0},function(res){});">toast</button>
</li>
<br/>
<li>
異步任務<br/>
<button onclick="JsBridge.call('JSBridge','plus',{'data':1},function(res){console.log(JSON.stringify(res))});">toast</button>
</li>
<br/>
點擊按鈕會執行js腳本的這段代碼
call: function (obj, method, params, callback, timeout)
sid = Private.getSid();
Private.registerCall(sid, callback);
Private.callMethod(obj, method, params, sid);
}
它其實就是一個函數,名字叫call,括號裏的是它的參數(obj, method, params, callback, timeout)。那這幾個參數是怎麼傳遞的呢?回過頭看我們的html代碼,點擊第一個按鈕,會執行這個語句
<button onclick="JsBridge.call('JSBridge','toast',{'message':'我是氣泡','isShowLong':0},function(res){});">toast</button>
其中括號(‘JSBridge’,’toast’,{‘message’:’我是氣泡’,’isShowLong’:0},function(res){})裏的第一個參數’JSBridge’對應着前面的obj,’toast’對應着method,以此類推。第二個按鈕也是一樣。
然後在call這個方法內,會執行Private類的registerCall和callMethod,我們來看callMehod()。
callMethod: function (obj, method, params, sid) {
params = Private.buildParam(params);
var uri = LOCAL_PROTOCOL + '://' + obj + ':' + sid + '/' + method + '?' + params;
var value = CB_PROTOCOL + ':';
window.prompt(uri, value);
}
註釋說的很清楚了,就是通過傳遞進來的參數生成uri,並且調用window.prompt()方法,這個方法大家應該很眼熟吧,沒錯,在調用這個方法之後,程序就會相應的走到java代碼的onJsPrompt()方法中。而生成的uri則是我們上面說過的那個我們自己定義的協議格式。
好了,我們總結一下這兩個前端的代碼。其實很簡單,以界面的第一個按鈕toast爲例,點擊這個按鈕,它會執行相應的js腳本代碼,然後就會像我們前面所講的那樣,走到onJsPrompt()方法中,下面讓我們看看對應的java代碼。
public class InjectedChromeClient extends WebChromeClient {
private final String TAG = "InjectedChromeClient";
private JsCallJava mJsCallJava;
public InjectedChromeClient() {
mJsCallJava = new JsCallJava();
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
result.confirm(mJsCallJava.call(view, message));
return true;
}
}
這是對應的WebChromeClient類,可以看到在onJsPrompt()方法中我們只做了一件事,就是丟給JsCallJava類去解析,再看JsCallJava類之前,我們可以先看看onJsPrompt()這個方法到底傳進來了什麼。
可以看到,我們傳給JsCallJava類的那個message,就像我們前面定義的協議一樣。sheme是hybrid://,表示這是一個hybrid方法,host是JSBridge,方法名字是toast,傳遞的參數是以json格式傳遞的,具體內容如圖。不知道大家有沒有發現,這裏我有一個東西沒有講,就是JSBridge:後面的那串數字,這串數字是幹什麼用的呢?大家應該知道,現在我們整個調用過程都是同步的,這意味着我們沒有辦法在裏面做一些異步的操作,爲了滿足異步的需求,我們就需要定義這樣的port,有了這串數字,我們在java層就可以做異步的操作,等操作完成以後回調給js腳本,js腳本就通過這串數字去得到對應的callback,有點像startActivity中的那個requestCode。大家沒聽懂也沒關係,後面我會在代碼中具體講解。
好了,下面我們可以來看JsCallJava這個類的具體代碼了。
public class JsCallJava {
private final static String TAG = "JsCallJava";
private static final String BRIDGE_NAME = "JSBridge";
private static final String SCHEME="hybrid";
private static final int RESULT_SUCCESS=200;
private static final int RESULT_FAIL=500;
private ArrayMap<String, ArrayMap<String, Method>> mInjectNameMethods = new ArrayMap<>();
private JSBridge mWDJSBridge = JSBridge.getInstance();
public JsCallJava() {
try {
ArrayMap<String, Class<? extends IInject>> externals = mWDJSBridge.getInjectPair();
if (externals.size() > 0) {
Iterator<String> iterator = externals.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
Class clazz = externals.get(key);
if (!mInjectNameMethods.containsKey(key)) {
mInjectNameMethods.put(key, getAllMethod(clazz));
}
}
}
} catch (Exception e) {
Log.e(TAG, "init js error:" + e.getMessage());
}
}
private ArrayMap<String, Method> getAllMethod(Class injectedCls) throws Exception {
ArrayMap<String, Method> mMethodsMap = new ArrayMap<>();
Method[] methods = injectedCls.getDeclaredMethods();
for (Method method : methods) {
String name;
if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (name = method.getName()) == null) {
continue;
}
Class[] parameters=method.getParameterTypes();
if(null!=parameters && parameters.length==3){
if(parameters[0]==WebView.class && parameters[1]==JSONObject.class && parameters[2]==JsCallback.class){
mMethodsMap.put(name, method);
}
}
}
return mMethodsMap;
}
public String call(WebView webView, String jsonStr) {
String methodName = "";
String name = BRIDGE_NAME;
String param = "{}";
String result = "";
String sid="";
if (!TextUtils.isEmpty(jsonStr) && jsonStr.startsWith(SCHEME)) {
Uri uri = Uri.parse(jsonStr);
name = uri.getHost();
param = uri.getQuery();
sid = getPort(jsonStr);
String path = uri.getPath();
if (!TextUtils.isEmpty(path)) {
methodName = path.replace("/", "");
}
}
if (!TextUtils.isEmpty(jsonStr)) {
try {
ArrayMap<String, Method> methodMap = mInjectNameMethods.get(name);
Object[] values = new Object[3];
values[0] = webView;
values[1] = new JSONObject(param);
values[2]=new JsCallback(webView,sid);
Method currMethod = null;
if (null != methodMap && !TextUtils.isEmpty(methodName)) {
currMethod = methodMap.get(methodName);
}
if (currMethod == null) {
result = getReturn(jsonStr, RESULT_FAIL, "not found method(" + methodName + ") with valid parameters");
}else{
result = getReturn(jsonStr, RESULT_SUCCESS, currMethod.invoke(null, values));
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
result = getReturn(jsonStr, RESULT_FAIL, "call data empty");
}
return result;
}
private String getPort(String url) {
if (!TextUtils.isEmpty(url)) {
String[] arrays = url.split(":");
if (null != arrays && arrays.length >= 3) {
String portWithQuery = arrays[2];
arrays = portWithQuery.split("/");
if (null != arrays && arrays.length > 1) {
return arrays[0];
}
}
}
return null;
}
private String getReturn(String reqJson, int stateCode, Object result) {
String insertRes;
if (result == null) {
insertRes = "null";
} else if (result instanceof String) {
insertRes = String.valueOf(result);
} else if (!(result instanceof Integer)
&& !(result instanceof Long)
&& !(result instanceof Boolean)
&& !(result instanceof Float)
&& !(result instanceof Double)
&& !(result instanceof JSONObject)) {
insertRes = result.toString();
} else {
insertRes = String.valueOf(result);
}
Log.d(TAG, " call json: " + reqJson + " result:" + insertRes);
return insertRes;
}
}
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
有點長,不過其實邏輯很好理解。首先我們調用的是call這個方法。它裏面做了什麼呢
public String call(WebView webView, String jsonStr) {
String methodName = "";
String name = BRIDGE_NAME;
String param = "{}";
String result = "";
String sid="";
if (!TextUtils.isEmpty(jsonStr) && jsonStr.startsWith(SCHEME)) {
Uri uri = Uri.parse(jsonStr);
name = uri.getHost();
param = uri.getQuery();
sid = getPort(jsonStr);
String path = uri.getPath();
if (!TextUtils.isEmpty(path)) {
methodName = path.replace("/", "");
}
}
if (!TextUtils.isEmpty(jsonStr)) {
try {
ArrayMap<String, Method> methodMap = mInjectNameMethods.get(name);
Object[] values = new Object[3];
values[0] = webView;
values[1] = new JSONObject(param);
values[2]=new JsCallback(webView,sid);
Method currMethod = null;
if (null != methodMap && !TextUtils.isEmpty(methodName)) {
currMethod = methodMap.get(methodName);
}
if (currMethod == null) {
result = getReturn(jsonStr, RESULT_FAIL, "not found method(" + methodName + ") with valid parameters");
}else{
result = getReturn(jsonStr, RESULT_SUCCESS, currMethod.invoke(null, values));
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
result = getReturn(jsonStr, RESULT_FAIL, "call data empty");
}
return result;
}
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
可以看到其實就是通過js腳本傳遞過來的參數得到了方法名字,sid(前面說的那串數字)等等內容。下面看這段代碼
ArrayMap<String, Method> methodMap = mInjectNameMethods.get(name);
通過name去得到一個map,這裏的name是我們剛剛解析得到了,對應實際情況就是JSBridge,那這個mInjectNameMethods又是什麼呢?
private ArrayMap<String, ArrayMap<String, Method>> mInjectNameMethods = new ArrayMap<>();
private JSBridge mJSBridge = JSBridge.getInstance();
public JsCallJava() {
try {
ArrayMap<String, Class<? extends IInject>> externals = mJSBridge.getInjectPair();
if (externals.size() > 0) {
Iterator<String> iterator = externals.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
Class clazz = externals.get(key);
if (!mInjectNameMethods.containsKey(key)) {
mInjectNameMethods.put(key, getAllMethod(clazz));
}
}
}
} catch (Exception e) {
Log.e(TAG, "init js error:" + e.getMessage());
}
}
private ArrayMap<String, Method> getAllMethod(Class injectedCls) throws Exception {
ArrayMap<String, Method> mMethodsMap = new ArrayMap<>();
Method[] methods = injectedCls.getDeclaredMethods();
for (Method method : methods) {
String name;
if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (name = method.getName()) == null) {
continue;
}
Class[] parameters=method.getParameterTypes();
if(null!=parameters && parameters.length==3){
if(parameters[0]==WebView.class && parameters[1]==JSONObject.class && parameters[2]==JsCallback.class){
mMethodsMap.put(name, method);
}
}
}
return mMethodsMap;
}
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 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
- 35
- 36
- 37
- 38
- 39
- 40
可以看到我們有一個JSBridge類,在JsCallJava的構造函數中,我們通過JSBridge這個類的getInjectPair()方法得到了一個String和class的映射關係,並且把class中符合標準的方法拿出來存放到mInjectNameMethods中,以便我們在call方法中調用。下面來看看JSBridge類。
public class JSBridge {
public static final String BRIDGE_NAME = "JSBridge";
private static JSBridge INSTANCE = new JSBridge();
private boolean isEnable=true;
private ArrayMap<String, Class<? extends IInject>> mClassMap = new ArrayMap<>();
private JSBridge() {
mClassMap.put(BRIDGE_NAME, JSLogical.class);
}
public static JSBridge getInstance() {
return INSTANCE;
}
public boolean addInjectPair(String name, Class<? extends IInject> clazz) {
if (!mClassMap.containsKey(name)) {
mClassMap.put(name, clazz);
return true;
}
return false;
}
public boolean removeInjectPair(String name,Class<? extends IInject> clazz) {
if (TextUtils.equals(name,BRIDGE_NAME)) {
return false;
}
Class clazzValue=mClassMap.get(name);
if(null!=clazzValue && (clazzValue == clazz)){
mClassMap.remove(name);
return true;
}
return false;
}
public ArrayMap<String, Class<? extends IInject>> getInjectPair() {
return mClassMap;
}
}
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
它的getInjectPair方法其實就是得到了mClassMap,這個map在JSBridge類初始化的時候就有一個默認的值了。
public static final String BRIDGE_NAME = "JSBridge";
private JSBridge() {
mClassMap.put(BRIDGE_NAME, JSLogical.class);
}
key是”JSBridge”,value是我們的JSLogincal類。
public class JSLogical implements IInject {
/**
* toast
*
* @param webView 瀏覽器
* @param param 提示信息
*/
public static void toast(WebView webView, JSONObject param, final JsCallback callback) {
String message = param.optString("message");
int isShowLong = param.optInt("isShowLong");
Toast.makeText(webView.getContext(), message, isShowLong == 0 ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();
if (null != callback) {
try {
JSONObject object = new JSONObject();
object.put("result", true);
invokeJSCallback(callback, object);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 加一
*
* @param webView
* @param param
* @param callback
*/
public static void plus(WebView webView, final JSONObject param, final JsCallback callback) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
int original = param.optInt("data");
original = original + 1;
if (null != callback) {
JSONObject object = new JSONObject();
object.put("after plussing", original);
invokeJSCallback(callback, object);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private static void invokeJSCallback(JsCallback callback, JSONObject objects) {
invokeJSCallback(callback, true, null, objects);
}
public static void invokeJSCallback(JsCallback callback, boolean isSuccess, String message, JSONObject objects) {
try {
callback.apply(isSuccess, message, objects);
} catch (JsCallback.JsCallbackException e) {
e.printStackTrace();
}
}
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
對這個類上面的兩個方法有沒有很眼熟?名字和js腳本中的那兩個方法一樣有木有。我們調用鏈最後就會走到相應的同名方法中!
上面就是js調js的整個過程了,其實吧,不應該放這麼多的代碼的,搞得像是源碼分析一樣,不過我覺得這樣還是有一定好處的,至少跟着代碼走一遍能加深印象嘛。
我們還是來捋一捋整個過程。
在js腳本中把對應的方法名,參數等寫成一個符合協議的uri,並且通過window.prompt方法發送給java層。
在java層的onJsPrompt方法中接受到對應的message之後,通過JsCallJava類進行具體的解析。
在JsCallJava類中,我們解析得到對應的方法名,參數等信息,並且在map中查找出對應的類的方法。
這裏多說一句,還記得我們定義的協議中的host是什麼嗎?
hybrid://JSBridge:875725/toast?{“message”:”我是氣泡”,”isShowLong”:0}
是JSBridge,而我們在JsCallJava類中是通過這個host去查找對應的類的,我們可以看到在JSBridge類中
public static final String BRIDGE_NAME = "JSBridge";
private JSBridge() {
mClassMap.put(BRIDGE_NAME, JSLogical.class);
}
這意味着,如果你可以更換你的host,叫aaa都沒關係,只要你在對應的map中的key也是aaa就可以了。
可能有的同學會說何必這麼麻煩,直接在JsCallJava類中定義方法不就好了,這樣還省的去寫那麼多的邏輯。可是大家有想過如果你把所有js腳本想要調用的方法都寫在JsCallJava類中,這個類會有多難擴展和維護嗎?而像我這樣,如果你的js腳本處理的是登錄相關邏輯,你可以寫一個LoginLogical.class,如果是業務相關,你可以寫一個BizLogical.class,這樣不僅清晰,而且解耦。
當然,如果你仔細的看過代碼,會發現其實在native層的那些同名函數其實是有規範的。
首先必須要是public static的,因爲這樣調用會更方便。
其次參數也有要求,有且僅有三個參數,WebView,JsonObject和一個Callback。WebView用來提供可能需要的context,另外java執行js方法也需要WebView對象。JsonObject是js腳本傳遞過來的參數。而Callback則是java用於回調js腳本的。
可能你會發現JSBridge裏處處都是規範,協議需要規範,參數需要規範。這些其實都是合理的,因爲規範所以安全。
在得到對應的方法之後,就去調用它,以我們的toast爲
/**
* toast
*
* @param webView 瀏覽器
* @param param 提示信息
*/
public static void toast(WebView webView, JSONObject param, final JsCallback callback) {
String message = param.optString("message");
int isShowLong = param.optInt("isShowLong");
Toast.makeText(webView.getContext(), message, isShowLong == 0 ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();
if (null != callback) {
try {
JSONObject object = new JSONObject();
object.put("result", true);
invokeJSCallback(callback, object);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
拿到對應的信息,直接makeToast就好了。
以上就是全部js調用java的過程,那我們java執行完邏輯以後,怎麼回調js呢?這裏我們以另外一個按鈕的例子來說。
<button onclick="JsBridge.call('JSBridge','plus',{'data':1},function(res){console.log(JSON.stringify(res))});">plus</button>
js腳本傳遞的一個json的參數,{“data”:1},從名字可以看出是先要java執行一個加邏輯。
/**
* 加一
*
* @param webView
* @param param
* @param callback
*/
public static void plus(WebView webView, final JSONObject param, final JsCallback callback) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
int original = param.optInt("data");
original = original + 1;
if (null != callback) {
JSONObject object = new JSONObject();
object.put("after plussing", original);
invokeJSCallback(callback, object);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
- 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
- 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
這裏我們模擬一下耗時操作,可以幫助大家更好的理解JSBridge中的異步操作。對應java層的方法執行完+1的操作之後,把結果封裝成一個jsonObject,並且調用invokeJSCallback方法。
public static void invokeJSCallback(JsCallback callback, boolean isSuccess, String message, JSONObject objects) {
try {
callback.apply(isSuccess, message, objects);
} catch (JsCallback.JsCallbackException e) {
e.printStackTrace();
}
}
invokeJSCallback方法中直接調用了callback的apply方法。
private static final String CALLBACK_JS_FORMAT = "javascript:JsBridge.onComplete('%s', %s);";
public void apply(boolean isSuccess, String message, JSONObject object) throws JsCallbackException {
if (mWebViewRef.get() == null) {
throw new JsCallbackException("the WebView related to the JsCallback has been recycled");
}
if (!mCouldGoOn) {
throw new JsCallbackException("the JsCallback isn't permanent,cannot be called more than once");
}
JSONObject result = new JSONObject();
try {
JSONObject code=new JSONObject();
code.put("code", isSuccess ? 0 : 1);
if(!isSuccess && !TextUtils.isEmpty(message)){
code.putOpt("msg",message);
}
if(isSuccess){
code.putOpt("msg", TextUtils.isEmpty(message)?"SUCCESS":message);
}
result.putOpt("status", code);
if(null!=object){
result.putOpt("data",object);
}
} catch (Exception e) {
e.printStackTrace();
}
final String jsFunc = String.format(CALLBACK_JS_FORMAT, mSid, String.valueOf(result));
if (mWebViewRef != null && mWebViewRef.get() != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
mWebViewRef.get().loadUrl(jsFunc);
}
});
}
}
- 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
- 35
- 36
- 37
- 38
- 39
- 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
- 35
- 36
- 37
- 38
- 39
在apply方法中,我們直接拼裝了一個jsonObject,裏面包括了我們想要返回給js腳本的結果,並且直接調用了js的onComplete方法。
onComplete: function (sid, data) {
var callObj = this.unregisterCall(sid);
var callback = callObj.callback;
data = this.parseData(data);
callback && callback(data);
}
可以看到js的onComplete通過sid(那一串數字)拿到對應的callback並執行,而我們plus的callback裏做了什麼呢?
function(res){console.log(JSON.stringify(res))}
直接在控制檯中輸出結果。
所以當我們點擊plug按鈕以後,過兩秒我們就可以在logcat中看到如下輸出
好了,至此所有和JSBridge相關的代碼就分析完了。其實原理非常的簡單,通過js的window.prompt方法將事先定義好的協議文本傳輸到java層,然後java層進行解析並調用相應的方法,最後通過callback將結果返回給js腳本。中間我們使用的那些類可以更好的解耦,如果你有心,甚至可以把所用邏輯相關代碼抽離出來,把剩餘的代碼寫成JSBridge.core作爲庫來使用。這樣你想加什麼功能直接寫,不用改任何的源碼。
UrlRouter
其實嚴格的說,UrlRouter不算是js和java的通信,它只是一個通過url來讓前端喚起native頁面的框架。不過千萬不要小看它的作用,如果協議定義的合理,它可以讓前端,Android和iOS三端有一個高度的統一,十分方便。
思路
其實吧,這個思路比JSBridge還要簡單,就是我們通過自己實現的框架去攔截前端同學寫的url,發現如果是符合我們UrlRouter的協議的話,就跳轉到相應的頁面。
至於怎麼攔截呢?當然是通過WebViewClient類的shouldOverrideUrlLoading方法咯。
代碼
首先我們還是先看一個html代碼
<html>
<title>Login</title>
<input type="button" value="login" onclick="javascript:location.href='http://login.h5.zjutkz.net/'">
</html>
很簡單,有一個按鈕,通過點擊這個按鈕,會加載一個url,這個url是http://login.h5.zjutkz.net/。
這裏多說一句,如果你也想用UrlRouter這樣的形式的話,協議的sheme最好是http這樣開頭的,不要自己去重新定義,因爲這樣可以保證前端同學邏輯的清晰。如果你想着自己定義一個sheme叫shemeA,公司做別的app的同學也定義一個sheme叫shemeB,加上本來就要的http,前端的同學可能腦子都昏了。。。
下面來看看WebViewClient類。
public class NavWebViewClient extends WebViewClient {
private Context context;
public NavWebViewClient(Context context){
this.context = context;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if( Nav.from(context).toUri(url)){
return true;
}
view.loadUrl(url);
return true;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
很簡單,在shouldOverrideUrlLoading方法中先攔截url交給Nav類處理,如果返回true則表示需要攔截,直接return true,否則交給WebView去loadUrl。
接下去看看Nav。
public class Nav {
private static final String TAG = "Nav";
public static Nav from(final Context context) {
return new Nav(context);
}
public boolean toUri(final String uri) {
if(TextUtils.isEmpty(uri)) return false;
return toUri(Uri.parse(uri));
}
public boolean toUri(final Uri uri) {
Log.d(TAG, uri.toString());
final Intent intent = to(uri);
for (;;) try {
intent.setPackage(mContext.getPackageName());
PackageManager pm = mContext.getPackageManager();
final ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
if(info == null) {
throw new ActivityNotFoundException("No Activity found to handle " + intent);
} else {
intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
}
mContext.startActivity(intent);
return true;
} catch (final ActivityNotFoundException e) {
return false;
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void startActivities(final Intent[] intents) {
mContext.startActivities(intents);
}
private Intent to(final Uri uri) {
mIntent.setData(uri);
return mIntent;
}
private Nav(final Context context) {
mContext = context;
mIntent = new Intent(Intent.ACTION_VIEW);
}
private final Context mContext;
private final Intent mIntent;
}
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 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
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
我們在NavWebViewClient類中是這樣調用的
Nav.from(context).toUri(url)
from方法創建了一個Nav類的實例,下面來看看toUri方法
public boolean toUri(final String uri) {
if(TextUtils.isEmpty(uri)) return false;
return toUri(Uri.parse(uri));
}
public boolean toUri(final Uri uri) {
Log.d(TAG, uri.toString());
final Intent intent = to(uri);
for (;;) try {
intent.setPackage(mContext.getPackageName());
PackageManager pm = mContext.getPackageManager();
final ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
if(info == null) {
throw new ActivityNotFoundException("No Activity found to handle " + intent);
} else {
intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
}
mContext.startActivity(intent);
return true;
} catch (final ActivityNotFoundException e) {
return false;
}
}
private Intent to(final Uri uri) {
mIntent.setData(uri);
return mIntent;
}
- 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
- 35
- 36
- 37
- 38
- 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
- 35
- 36
- 37
- 38
在toUri方法中調用了to方法,to方法做的就是將uri以setData的方式注入到intent中。
接着通過一系列PackageManager的方法去判斷有沒有符合uri的activity,如果有則直接startActivity。
是不是很簡單,下面我以文中最開頭的場景2爲例子。
我們native端需要一個LoginActivity,並且根據上面的代碼我們知道,這個LoginActivity必須要配置上對應的data才行。
<activity android:name=".activity.LoginActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="${NAV_CATEGORY}"/>
<data android:scheme="${NAV_SCHEMA}"/>
<data android:host="${NAV_HOST}"/>
</intent-filter>
</activity>
defaultConfig {
applicationId "zjutkz.com.navigationdemo"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
manifestPlaceholders = ["NAV_SCHEMA": "http", "NAV_HOST": "login.h5.zjutkz.net","NAV_CATEGORY": "zjutkz.net"]
}
這是我們的manifest文件,可以看到已經通過gradle配置了對應的data。
這裏我爲什麼要用grdle去配置呢?想象如果你有十幾個頁面,你難道要在manifest中都寫一遍嗎?用我這種方式,直接在build.gradle中寫一遍就可以了。這裏我是想給大家傳遞一個思想:
使用gradle我們可以做很多自動化的事,千萬不要自己給自己找麻煩了。
看到這兒大家肯定會覺得,就這麼簡單?是的,大體的框架就這麼簡單,但是如果你想真正的用好它,還需要做很多工作。
比如在跳轉到native頁面,做完響應的邏輯之後,你怎麼通知前端去更新呢?這裏你可以使用startActivityForResult,也可以使用廣播,甚至是eventBus。這需要你在你的框架內做好封裝。
再比如,上面的例子是最簡單的,但是如果前端的同學想在跳到對應的native頁面的時候加上一些參數呢?你的intent該怎麼處理?
還有,如果你想你的框架魯棒性夠強,是不是得提供一個hook工具呢?讓調用者可以hook掉你內部的那個intent,從而添加自己想要添加的數據。
這些都是要解決的問題,這裏我就不給大家上具體的代碼了。畢竟只有你自己去實現了以後纔會有更深的理解。
轉載:http://zjutkz.net/2016/04/17/好好和h5溝通!幾種常見的hybrid通信方式