按安卓开发目前现状来讲,开发者大部分时间仍是花在UI的屏幕适配上,使用原生控件开发成本已不是那么理想,鉴于不少项目和iOS基于一致的ui界面,至使安卓UI开发成本花费更大的代价,所以目前结合HTML5和原生控件是解决UI适配的一种很好的选择,处于APP性能也会用Java和native层进行结合。不论是哪一种结合,其实原理都差很少,只要按照它的协议来,是很容易的,今天咱们仅对于Html和Java层结合,学习下WebViewJavascriptBridge。javascript
WebViewJavascriptBridge是移动UIView和Html交互通讯的桥梁,用做者的话来讲就是实现java(ios为oc)和js的互相调用的桥梁。替代了WebView的自带的JavascriptInterface的接口,使得咱们的开发更加灵活和安全。html
mWebView.addJavascriptInterface(new JsToJava(), "myjsfunction");
4.4以后调用须要在调用方法加入加入@JavascriptInterface注解,若是代码无此申明,那么也就没法使得js生效,也就是说这样就能够避免恶意网页利用js对安卓客户端的窃取和攻击。html5
可是即便这样,咱们不少时候须要在js记载本地代码的时候,要作一些判断和限制,或者有可能也会作些过滤和对用户友好提示,所以JavascriptInterface也就没法知足咱们的需求了,特此有大神就写出了WebViewJavascriptBridge框架。java
repositories { // ... maven { url "https://jitpack.io" } } dependencies { compile 'com.github.lzyzsd:jsbridge:1.0.4' }
<?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>
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 自定webVIewClientandroid
class MyWebViewClient extends BridgeWebViewClient{ public MyWebViewClient(BridgeWebView webView) { super(webView); } }
将自定义webViewClient设置到webview上ios
mWebView.setWebViewClient(new MyWebViewClient(mWebView));
上面句话很关键:git
经过实例化webView,用法和安卓原生的view没多大区别,设置WebChromClient, 设置加载的html(一样支持网络和本地文件),接着咱们须要给web注册和html端约定好的js方法名。代码列举的submitFromweb和js的执行的方法名一致,玩过NDK的JNI调用的朋友也知道必须和c代码之间有个约定,其实js桥和jni有点相似,github
经过注册handler来实现回调,Java代码中经过js返回的数据,进行处理后在调用function.onCallback返回给js.这里不作过多解释web
//必须和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(); } });
<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>
<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