按安卓開發目前現狀來說,開發者大部分時間還是花在UI的屏幕適配上,使用原生控件開發成本已不是那么理想,鑒於很多項目和iOS基於一致的ui界面,至使安卓UI開發成本花費更大的代價,因此目前結合HTML5和原生控件是解決UI適配的一種很好的選擇,處於APP性能也會用Java和native層進行結合。不管是哪種結合,其實原理都差不多,只要按照它的協議來,是很容易的,今天我們僅對於Html和Java層結合,學習下WebViewJavascriptBridge。
已經熟悉了jsbridge的朋友直接移步:
《Android基於JsBridge封裝的高效帶加載進度的WebView》:http://blog.csdn.net/sk719887916/article/details/52402470
一 什么是webViewjavascripBridge?
WebViewJavascriptBridge是移動UIView和Html交互通信的橋梁,用作者的話來說就是實現java(ios為oc)和js的互相調用的橋梁。替代了WebView的自帶的JavascriptInterface的接口,使得我們的開發更加靈活和安全。
二 為什么要用webViewjavascripBridge?
mWebView.addJavascriptInterface(new JsToJava(), "myjsfunction");
4.4之后調用需要在調用方法加入加入@JavascriptInterface注解,如果代碼無此申明,那么也就無法使得js生效,也就是說這樣就可以避免惡意網頁利用js對安卓客戶端的竊取和攻擊。
但是即使這樣,我們很多時候需要在js記載本地代碼的時候,要做一些判斷和限制,或者有可能也會做些過濾和對用戶友好提示,因此JavascriptInterface也就無法滿足我們的需求了,特此有大神就寫出了WebViewJavascriptBridge框架。
三 怎么使用webViewjavascripBridge
1 將jsBridge.jar引入到我們的工程
repositories { // ... maven { url "https://jitpack.io" } } dependencies { compile 'com.github.lzyzsd:jsbridge:1.0.4' }
2 WebView包需使用以上包的webView
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <!-- button 演示Java調用web --> <Button android:id="@+id/button" android:layout_width="match_parent" android:text="@string/button_name" android:layout_height="48dp" /> <!-- webview 演示web調用Java --> <com.github.lzyzsd.jsbridge.BridgeWebView android:id="@+id/webView" android:layout_width="match_parent" android:layout_height="match_parent" > </com.github.lzyzsd.jsbridge.BridgeWebView> </LinearLayout>
3 Java代碼
public class MainActivity extends Activity implements OnClickListener { private final String TAG = "MainActivity"; BridgeWebView webView; Button button; int RESULT_CODE = 0; ValueCallback<Uri> mUploadMessage; static class Location { String address; } static class User { String name; Location location; String testStr; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = (BridgeWebView) findViewById(R.id.webView); button = (Button) findViewById(R.id.button); button.setOnClickListener(this); webView.setDefaultHandler(new DefaultHandler()); webView.setWebChromeClient(new WebChromeClient() { @SuppressWarnings("unused") public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType, String capture) { this.openFileChooser(uploadMsg); } @SuppressWarnings("unused") public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType) { this.openFileChooser(uploadMsg); } public void openFileChooser(ValueCallback<Uri> uploadMsg) { mUploadMessage = uploadMsg; pickFile(); } }); //加載本地網頁 //webView.loadUrl("file:///android_asset/demo.html"); //加載服務器網頁 webView.loadUrl("https://www.baidu.com"); //必須和js同名函數,注冊具體執行函數,類似java實現類。 webView.registerHandler("submitFromWeb", new BridgeHandler() { @Override public void handler(String data, CallBackFunction function) { String str ="這是html返回給java的數據:" + data; // 例如你可以對原始數據進行處理 makeText(MainActivity.this, str, LENGTH_SHORT).show(); Log.i(TAG, "handler = submitFromWeb, data from web = " + data); function.onCallBack( str + ",Java經過處理后截取了一部分:"+ str.substring(0,5)); } }); //模擬用戶獲取本地位置 User user = new User(); Location location = new Location(); location.address = "上海"; user.location = location; user.name = "Bruce"; webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() { @Override public void onCallBack(String data) { makeText(MainActivity.this, "網頁在獲取你的位置", LENGTH_SHORT).show(); } }); webView.send("hello"); } 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) { if (null == mUploadMessage){ return; } Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } } @Override public void onClick(View v) { if (button.equals(v)) { webView.callHandler("functionInJs", "data from Java", new CallBackFunction() { @Override public void onCallBack(String data) { // TODO Auto-generated method stub Log.i(TAG, "reponse data from js " + data); } }); } } }
2 自定webVIewClient
class MyWebViewClient extends BridgeWebViewClient{ public MyWebViewClient(BridgeWebView webView) { super(webView); } }
將自定義webViewClient設置到webview上
mWebView.setWebViewClient(new MyWebViewClient(mWebView));
上面句話很關鍵:
通過實例化webView,用法和安卓原生的view沒多大區別,設置WebChromClient, 設置加載的html(同樣支持網絡和本地文件),接着我們需要給web注冊和html端約定好的js方法名。代碼列舉的submitFromweb和js的執行的方法名一致,玩過NDK的JNI調用的朋友也知道必須和c代碼之間有個約定,其實js橋和jni有點類似,
通過注冊handler來實現回調,Java代碼中通過js返回的數據,進行處理后在調用function.onCallback返回給js.這里不做過多解釋
//必須和js函數同名,注冊具體執行回調函數,類似java實現類。 webView.registerHandler("submitFromWeb", new BridgeHandler() { @Override public void handler(String data, CallBackFunction function) { String str ="這是html返回給java的數據:" + data; // 例如你可以對原始數據進行處理 makeText(MainActivity.this, str, LENGTH_SHORT).show(); Log.i(TAG, "handler = submitFromWeb, data from web = " + data); function.onCallBack( str + ",Java經過處理后截取了一部分:"+ str.substring(0,5)); } });
如果是webview剛開始就執行一段Java代碼 ,可以通過webView.CallHandler()來實現 。當然我們注冊的方法也要和js里面的方法名一致。
webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() { @Override public void onCallBack(String data) { makeText(MainActivity.this, "網頁在獲取你的位置", LENGTH_SHORT).show(); } });
3 Html和js實現
html代碼如下,效果圖
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type">
<title>
js調用java
</title>
</head>
<body>
<p>
<xmp id="show">
</xmp>
</p>
<p>
<xmp id="init">
</xmp>
</p>
<p>
<input type="text" id="text1" value="用戶名(username)" />
</p>
<p>
<input type="text" id="text2" value="password" />
</p>
<p>
<input type="button" id="enter" value="發消息給Native" onclick="testClick();"
/>
</p>
<p>
<input type="button" id="enter1" value="調用Native方法" onclick="testClick1();"
/>
</p>
<p>
<input type="button" id="enter2" value="顯示html" onclick="testDiv();" />
</p>
<p>
<input type="file" value="打開文件" />
</p>
</body>
</html>
js代碼
<script> function testDiv() { document.getElementById("show").innerHTML = document.getElementsByTagName("html")[0].innerHTML; } function testClick() { var str1 = document.getElementById("text1").value; var str2 = document.getElementById("text2").value; //發送消息給java代碼 var data = "name=" + str1 + ",pass=" + str2; window.WebViewJavascriptBridge.send( data , function(responseData) { document.getElementById("show").innerHTML = "repsonseData from java, data = " +responseData } ); } function testClick1() { var str1 = document.getElementById("text1").value; var str2 = document.getElementById("text2").value; //調用本地java方法 window.WebViewJavascriptBridge.callHandler( 'submitFromWeb' , {'param': str1} , function(responseData) { document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData } ); } function bridgeLog(logContent) { document.getElementById("show").innerHTML = logContent; }//注冊事件監聽 function connectWebViewJavascriptBridge(callback) { if (window.WebViewJavascriptBridge) { callback(WebViewJavascriptBridge) } else { document.addEventListener( 'WebViewJavascriptBridgeReady' , function() { callback(WebViewJavascriptBridge) }, false ); } } //注冊回調函數,第一次連接時調用 初始化函數 connectWebViewJavascriptBridge(function(bridge) { bridge.init(function(message, responseCallback) { console.log('JS got a message', message); var data = { 'Javascript Responds': 'Wee!' }; console.log('JS responding with', data); responseCallback(data); }); bridge.registerHandler("functionInJs", function(data, responseCallback) { document.getElementById("show").innerHTML = ("data from Java: = " + data); var responseData = "Javascript Says Right back aka!"; responseCallback(responseData); }); }) </script>
這段代碼不難理解,我們對上面的id為enter的Button注冊了一個點擊事件,點擊后執行以下testClick()方法,依次類推,其他Button注冊事件相同,
當點擊“發消息給Native”的按鈕時,Js通過webWiew的jsBridage.send()發送一條數據給java層(密碼和用戶名),同時在此用function()來執行應java層回調函數的。此demo中是把java返回的數據插入到"show"的div里面去。
testClick1():此方法中調用callHandler來調用Java代碼的submitFromweb同名函數,可以結合上面的Activty的代碼理解下,此函數調用我們已在java注冊實現好的
//必須和js同名函數,注冊具體執行函數,類似java實現類。 webView.registerHandler("submitFromWeb", new BridgeHandler() { @Override public void handler(String data, CallBackFunction function) { String str ="這是html返回給java的數據:" + data; // 例如你可以對原始數據進行處理 makeText(MainActivity.this, str, LENGTH_SHORT).show(); Log.i(TAG, "handler = submitFromWeb, data from web = " + data); function.onCallBack( str + ",Java經過處理后截取了一部分:"+ str.substring(0,5)); } });
java注冊Js函數如上面列子 ,那么在js中注冊方法來注冊呢,
在js中我們同樣可以直接注冊一個回調函數,通過 bridge.registerHandler()來注冊,接着調用 responseCallback(responseData)來返回數據給java代碼的同名函數。f
也可以直接調用init()來指定網頁首次加載上面注冊java代碼。
connectWebViewJavascriptBridge(function(bridge) { bridge.init(function(message, responseCallback) { console.log('JS got a message', message); var data = { 'Javascript Responds': 'Wee!' }; console.log('JS responding with', data); responseCallback(data); }); bridge.registerHandler("functionInJs", function(data, responseCallback) { document.getElementById("show").innerHTML = ("data from Java: = " + data); var responseData = "Javascript Says Right back aka!"; responseCallback(responseData); }); })
四 總結
通過以上的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的內部實現。
jsbridage的封裝請參考: <Android基於JsBridge封裝的高效帶加載進度的WebView>
項目實例:https://github.com/Tamicer/JsBridge_Android
