Android——JsBridge實戰(一)
Android開發目前現狀來說,開發者大部分時間花在UI的螢幕適配上,使用原生控制元件開發成本已不是那麼的理想,鑑於很多專案保持和iOS一致的UI介面風格,至使移動UI開發成本花費更大的代價,因此目前結合H5和原生控制元件混合開發是解決UI適配的一種很好的選擇, 因此基於網頁形式的外掛更新業務功能出現了,處於APP效能的考慮,Android也會使用java和native層(C,C++)進行結合。無論是哪種結合,其實原理都差不多,只要按照它的協議來是很容易的,今天我們僅對於H5和Java層結合的混合開發,瞭解WebViewJavascriptBridge的使用。
什麼是JsBridge
WebViewJavascriptBridge是移動UIView和Html互動通訊的橋樑,用作者的話來說就是實現java(ios為oc)和js的互相呼叫的橋樑。替代了WebView的自帶的JavascriptInterface的介面,使得開發者更方便的讓js和native靈活互動,使我們的開發更加靈活和安全。
JSBridge的優點
Android API 4.4以前,谷歌的webview存在安全漏洞,網站可以通過js注入就可以隨便拿到客戶端的重要資訊,甚至輕而易舉的呼叫原生代碼進行流氓行為,谷歌後來發現有此漏洞後,在API 4.4以後增加了防禦措施,如果用js呼叫原生代碼,開發者必須在程式碼申明JavascriptInterface, 列如在4.0之前我們要使得webView載入js只需如下程式碼:
mWebView.addJavascriptInterface(new JsToJava(), "myjsfunction");
4.4之後使用時需要在呼叫Java方法加入@JavascriptInterface註解,如果程式碼無此申明,那麼也就無法使得js生效,也就是說這樣就可以避免惡意網頁利用js對客戶端的進行竊取和攻擊。 但是即使這樣,我們很多時候需要在js呼叫本地java程式碼的時候,要做一些判斷和限制,或者有的場景也會做些過濾或者對使用者友好提示,甚至更復雜的Hybrid模式下,需要js和native之間進行互動通訊,拍照上傳,因此原生的JavascriptInterface 就比較維護了,特此有了基於JavascriptInterface 封裝的WebViewJavascriptBridge框架。
使用JsBridge正確姿勢
1 新增依賴
Eclipse:
匯入jar包
直接copy jar的原始碼到工程
JsBridge. jar可以到gitHub上直接下載。Android Studio:
配置gradle
repositories {
//...
maven { url "https://jitpack.io" }
}
dependencies {
compile 'com.github.lzyzsd:jsbridge:1.0.4'
}
2 xml中使用jsBridge的webView
<com.github.lzyzsd.jsbridge.BridgeWebView
android:id="@+id/activity_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
3 JS和Native互動:
JSBridge是基於訂閱和回撥來實現Js和native互動的,我們需要在java中訂閱,然後Js中回撥,反之也可以。
3.1 Java中使用JsBridge
- registerHandler()用來註冊一個java函式,來實現js回撥的handler,
引數說明:
第一個 : 訂閱的方法名
第二個 : 回撥Handler , 引數返回js請求的resqustData,function.onCallBack()回撥到js,呼叫function(responseData)
//必須和js同名函式,註冊具體執行函式,類似java實現類
webView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
function.onCallBack( data + “java”);
}
});
- CallHandler( )用來呼叫js中的註冊的方法
引數說明:
第一個 : js中註冊的方法名
第二個 : java發給js的引數,json格式的string
第三個 : 用來處理js回覆給java的資訊的回撥方法
mWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
showToast(data);
}
});
- send()方法用來向js傳送訊息
mWebView.send("Hello!");
3.2 JS中使用JsBridge
- window.WebViewJavascriptBridge.callHandler:呼叫java層註冊的同名函式,方法名必須和Java層保持一致
引數說明:
第一個 : 方法名
第二個 : js呼叫native的請求引數
第三個:js在被回撥後具體執行方法,responseData為java層回傳jsonStr.
window.WebViewJavascriptBridge.callHandler('submitFromWeb',
{'param': requsetData }, function(responseData) {
// do something
});
- bridge.registerHandler:註冊一個js方法,java層可呼叫此方法
引數說明:
第一個:方法名
第二個:回撥方法,用於接收java層回覆給js的訊息;responseCallback()用於回調回java層
bridge.registerHandler("functionInJs",function(data,responseCallback){
alert("data from Java = " + data);
document.getElementById("msgFromJava").innerHTML = "data from Java = " + data;
var responseData = "Javascript Says Right back aka!";
responseCallback(responseData);
});
- window.WebViewJavascriptBridge.send:js向java層傳送訊息
引數說明:
第一個:js發給java的訊息
第二個:回撥方法,接收java回覆給js的訊息
window.WebViewJavascriptBridge.send(data,function(responseData){
alert(responseData);
});
程式碼演示
1.Activity
public class MainActivity extends AppCompatActivity {
@BindView(R.id.mWebView)
BridgeWebView mWebView;
private String webUrl = "file:///android_asset/demo.html";
//private String webUrl = "https://www.baidu.com";
private static CallBackFunction mfunction;
int RESULT_CODE = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ButterKnife.bind(this);
initWebView();
}
private void initWebView() {
mWebView.setWebViewClient(new MyWebViewClient(mWebView));
mWebView.setDefaultHandler(new MyHandlerCallback());
mWebView.setWebChromeClient(new WebChromeClient());
//載入伺服器網頁或者本地網頁
mWebView.loadUrl(webUrl);
//必須和js同名函式,註冊具體執行函式,類似java實現類。
mWebView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
//對原始資料進行處理
String str = "這是html返回給java的資料:" + data;
str += ",對原始資料進行了擷取操作:" + str.substring(0, 5);
showToast(str);
//回撥返回給JS
function.onCallBack(str);
}
});
//開啟本地檔案
mWebView.registerHandler("openFile", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
pickFile();
mfunction = function;
}
});
//模擬使用者獲取本地位置
User user = new User();
user.name = "alice";
Location location = new Location();
location.address = "上海";
user.location = location;
//webView呼叫js的方法
mWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
showToast(data);
}
});
mWebView.send("Hello!");
}
private void showToast(String string) {
Toast.makeText(MainActivity.this, string, Toast.LENGTH_SHORT).show();
}
public void pickFile() {
Intent chooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
chooserIntent.setType("image/*");
startActivityForResult(chooserIntent, RESULT_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == RESULT_CODE) {
Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();
mfunction.onCallBack(result.toString());
}
}
class MyWebViewClient extends BridgeWebViewClient {
public MyWebViewClient(BridgeWebView webView) {
super(webView);
}
}
class MyHandlerCallback extends DefaultHandler {
@Override
public void handler(String data, CallBackFunction function) {
super.handler(data, function);
if (null != function) {
showToast(data);
function.onCallBack("收到JS訊息回覆。");
}
}
}
static class Location{
String address;
}
static class User{
String name;
Location location;
String testStr;
}
}
2.JS
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type">
<title>js呼叫java</title>
<script>
//註冊事件監聽
function connectWebViewJavascriptBridge(callback){
if(window.WebViewJavascriptBridge){
callback(WebViewJavascriptBridge);
}else{
document.addEventListener(
"WebViewJavascriptBridgeReady",
function(){
callback(WebViewJavascriptBridge);
},
false
);
}
}
//註冊回撥函式,第一次連線時呼叫 初始化函式
connectWebViewJavascriptBridge(function(bridge){
bridge.init(function(message, responseCallback){
alert("JS get a message : " + message);
var data = {
"JavaScript Responds" : "Wee!"
};
//alert("Js response data : " + data);
responseCallback("Js response data wee ");
});
bridge.registerHandler("functionInJs",function(data,responseCallback){
alert("data from Java = " + data);
document.getElementById("msgFromJava").innerHTML = "data from Java = " + data;
var responseData = "Javascript Says Right back aka!";
responseCallback(responseData);
});
})
function sendMsgToNative(){
var name = document.getElementById("username").value;
var pwd = document.getElementById("password").value;
//呼叫本地java方法
window.WebViewJavascriptBridge.callHandler("submitFromWeb",
{"name":name},
function(responseData){
alert("data from Java = " + responseData);
document.getElementById("msgFromJava").innerHTML = "data from Java = " + responseData;
});
var data = "name=" + name + ",pwd=" + pwd;
//傳送訊息給java程式碼
window.WebViewJavascriptBridge.send(data,function(responseData){
alert(responseData);
});
}
function openFile(){ window.WebViewJavascriptBridge.callHandler("openFile","",function(responseData){
//alert("openFile response : " + responseData);
document.getElementById("pic").src = responseData;
});
}
</script>
</head>
<body>
<p>使用者名稱:<input type="text" id="username"/></p>
<p>密碼:<input type="text" id="password"/></p>
<p>Java資訊測試:<input type="text" id="msgFromJava"/></p>
<p><input type="button" value="發訊息給Native" onclick="sendMsgToNative();"/></p>
<p><input type="button" value="呼叫Native方法"/></p>
<p><input type="button" value="顯示html"/></p>
<p><input type="file" value="選擇檔案" onclick="openFile();"/></p>
<img id="pic" src="" width="100%" height="auto"/>
</body>
</html>
效果圖:
注意事項:
無論怎樣形式的互動,Js 必須要初始化jsBridge
四 總結
通過以上的API介紹,程式碼示例,不難發現此框架的優雅和簡便,對js和java雙方來說,如果Html中的js需要呼叫java程式碼,而java程式碼沒做任何實現,那麼js中方法也是無效的,反之java程式碼註冊的函式,沒在js裡去回撥實現,那麼Java層也是無法獲取js中資料的,由此可見,此通訊是雙方支援的,必須由雙方來約定,這樣就避免了Android之前存在的js注入漏洞,也很大的提高了安全性,也可以保證我們的網頁資料不被第三方的APP獲取,具體來講,列如我們的專案某一個web的h5介面,被系統瀏覽器或者其他第三方App的惡意載入,那麼它的java程式碼想呼叫你的js函式,實現需要你的H5的Js先註冊,不然跟本無法呼叫你的h5資訊。這樣保證了這個html資料的安全性,,第三方的瀏覽器可以載入預覽你的網頁,但是第三方java無法和你的的h5中的js互動通訊的。同樣載入我們自己的APP載入第三方的網頁時候,我們可以對第三方網頁進行一些行為的過濾,方便保護我們手機的安全,列如第三方可以獲取本機地址時我們可以提示使用者授權。雖然H5並不屬於外掛的一種,但是藉助h5我可以方便的去更新一些運營活動,和某些需要經常需要更換UI的業務功能的地方,以上只JSBridge的使用姿勢,,以後再給大家解剖下JsBridge的內部實現。