Android JavascriptBridge 详解(二)
原文出自:http://blog.csdn.net/sk719887916/article/details/47189607
Android开发目前现状来说,开发者大部分时间花在UI的屏幕适配上,使用原生控件开发成本已不是那么的理想,鉴于很多项目保持和iOS一致的UI界面风格,至使移动UI开发成本花费更大的代价,因此目前结合H5和原生控件混合开发是解决UI适配的一种很好的选择, 因此基于网页形式的插件更新业务功能出现了,处于APP性能的考虑,Android也会使用java和native层(C,C++)进行结合。无论是哪种结合,其实原理都差不多,只要按照它的协议来是很容易的,今天我们仅对于H5和Java层结合的混合开发,了解WebViewJavascriptBridge的使用。
如果已经掌握jsbridge的朋友可以直接进阶对他封装篇:
Android基于JsBridge封装的高效带加载进度的WebView:http://blog.csdn.net/sk719887916/article/details/52402470
什么是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只需如下代码:
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 WebView需使用jsBridge的webView
<com.github.lzyzsd.jsbridge.BridgeWebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
JS和Native交互
JS是基于订阅和回调来实现Js和native交互的,我们需要在java中订阅,然后Js中回调,反之也可以。
3.1 Java
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”); } });
3.2 JS
Js代码需要用window.WebViewJavascriptBridge.callHandler同步回调java层注册的同名函数,这和java和c库的jni调用如出一辙,方法名必须和Java层保持一致
- 第一参数:方法名
- 第二个:js调用native的请求参数
第三个:js在被回调后具体执行方法,responseData为java层回传jsonStr.
window.WebViewJavascriptBridge.callHandler( 'submitFromWeb' , {'param': requsetData } , function(responseData) { // do somrthing } );
案列实践
* Activity*
初始化WebView,设置必要的参数。
mWebView = (BridgeWebView) findViewById(R.id.webView); // 设置具体WebViewClient mWebView.setWebViewClient(new MyWebViewClient(mWebView)); // set HadlerCallBack mWebView.setDefaultHandler(new myHadlerCallBack()); // setWebChromeClient mWebView.setWebChromeClient(new WebChromeClient());
需要自定义HandlerCallBack和WebViewClent;
/** * 自定义的WebViewClient */ class MyWebViewClient extends BridgeWebViewClient { public MyWebViewClient(BridgeWebView webView) { super(webView); } } /** * 自定义回调 */ class myHadlerCallBack extends DefaultHandler { @Override public void handler(String data, CallBackFunction function) { if(function != null){ Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show(); } } }
加载网页 支持本地和远程
mWebView.loadUrl("file:///android_asset/demo.html");
1 java调用js函数
订阅一个叫submitFromWeb 的方法名 并会写回调函数
mWebView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
String str = "这是html返回给java的数据:" + data;
// 例如你可以对原始数据进行处理
str = str + ",Java经过处理后截取了一部分:" + str.substring(0, 5);
Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
//回调返回给Js
function.onCallBack(str);
}
});
js同样需要java注册订阅的同名函数
//call native method
window.WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': data }
, function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
2 JS调用Java代码
反之js调用java代码
Js 订阅一个叫functionInJs的方法
// 注册一个"functionInJs", bridge.registerHandler("functionInJs", function(data, responseCallback) { document.getElementById("show").innerHTML = ("data from Java: = " + data); var responseData = "Javascript Says 我要你的地址!"; // response java层 responseCallback(responseData); });
Java代码响应functionInJs 函数
// 回调 "functionInJs" mWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() { @Override public void onCallBack(String data) { // to do somthing } });
3 注意事项
无论怎样形式的交互,Js 必须要初始化jsBridge
bridge.init(function(message, responseCallback) {
console.log('JS got a message', message);
var data = {
'Javascript Responds': '测试中文!'
};
console.log('JS responding with', data);
responseCallback(data);
});
- 效果图:
代码展示
1. Activty
public class MainActivity extends AppCompatActivity {
private BridgeWebView mWebView;
ValueCallback<Uri> mUploadMessage;
int RESULT_CODE = 0;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
mWebView = (BridgeWebView) findViewById(R.id.webView);
initWebView();
}
private void initWebView() {
// 设置具体WebViewClient
mWebView.setWebViewClient(new MyWebViewClient(mWebView));
// set HadlerCallBack
mWebView.setDefaultHandler(new myHadlerCallBack());
// setWebChromeClient
mWebView.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();
}
});
mWebView.loadUrl("file:///android_asset/demo.html");
//必须和js函数名字一致,注册好具体执行回调函数,类似java实现类。
mWebView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
String str = "这是html返回给java的数据:" + data;
// 例如你可以对原始数据进行处理
str = str + ",Java经过处理后截取了一部分:" + str.substring(0, 5);
Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
//回调返回给Js
function.onCallBack(str + ",Java经过处理后截取了一部分:" + str.substring(0, 5));
}
});
mWebView.registerHandler("functionOpen", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
Toast.makeText(MainActivity.this, "网页在打开你的下载文件预览", Toast.LENGTH_SHORT).show();
pickFile();
}
});
//模拟用户信息 获取本地位置,用户名返回给html
User user = new User();
user.setLocation("上海");
user.setName("Bruce");
// 回调 "functionInJs"
mWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
Toast.makeText(MainActivity.this, "网页在获取你的位置,"+ data, Toast.LENGTH_SHORT).show();
}
});
}
/**
* 自定义的WebViewClient
*/
class MyWebViewClient extends BridgeWebViewClient {
public MyWebViewClient(BridgeWebView webView) {
super(webView);
}
}
/**
* 自定义回调
*/
class myHadlerCallBack extends DefaultHandler {
@Override
public void handler(String data, CallBackFunction function) {
if(function != null){
Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
}
}
}
public void pickFile() {
Intent chooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
chooserIntent.setType("image/*");
startActivityForResult(chooserIntent, RESULT_CODE);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == RESULT_CODE) {
if (null == mUploadMessage) {
return;
}
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
}
}
}
通过实例化webView,用法和安卓原生的view没多大区别,设置WebChromClient, 设置加载的html(同样支持网络和本地文件),接着我们需要给web注册和html端约定好的js方法名。代码列举的submitFromweb和js的执行的方法名一致,玩过NDK的JNI调用的朋友也知道必须和c代码之间有个约定,其实js桥和jni有点类似
2 js代码
function onOpen() {
var str1 = document.getElementById("text1").value;
var str2 = document.getElementById("text2").value;
var data = "name=" + str1 + ",pass=" + str2;
//call native method
window.WebViewJavascriptBridge.callHandler(
'functionOpen'
, {'param': data }
, function(responseData) {
//document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData;
}
);
}
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 = {id: 1, content: "这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"};
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;
var data = "name=" + str1 + ",pass=" + str2;
//call native method
window.WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': data }
, 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
);
}
}
// 第一连接时初始化bridage
connectWebViewJavascriptBridge(function(bridge) {
bridge.init(function(message, responseCallback) {
console.log('JS got a message', message);
var data = {
'Javascript Responds': '测试中文!'
};
console.log('JS responding with', data);
responseCallback(data);
});
// 注册一个"functionInJs",
bridge.registerHandler("functionInJs", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("data from Java: = " + data);
var responseData = "Javascript Says 我要你的地址!";
// response层
responseCallback(responseData);
});
})
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);
});
这段代码不难理解,我们对上面的id为enter的Button注册了一个点击事件,点击后执行以下testClick()方法,依次类推,其他Button注册事件相同,当点击“发消息给Native”的按钮时,Js通过webWiew的jsBridage.send()发送一条数据给java层(密码和用户名),同时执行function()来执行应java层回调函数的。此demo中是把java返回的数据插入到ID为”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));
}
});
3. H5
这里so ez ! 你不必care
h5实现文件上传
四 总结
通过以上的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的业务功能的地方,由于本文是从CSDN迁移过来的,原文实在一年前写的,所以带来的坟贴感觉请见谅。
原文csdn请戳:这里
以上只JSBridge的使用姿势,,以后再给大家解剖下JsBridge的内部实现。
项目地址
项目实例:https://github.com/NeglectedByBoss/JsBridge_Android
英文介绍:https://github.com/lzyzsd/JsBridge
Android JavascriptBridge 详解(二)的更多相关文章
- Android ActionBar详解(二):ActionBar实现Tabs标签以及下拉导航
一.添加标签 Tabs 在ActionBar中实现标签页可以实现android.app.ActionBar.TabListener ,重写onTabSelected.onTabUnselected ...
- Android Fragment详解(二):Fragment创建及其生命周期
Fragments的生命周期 每一个fragments 都有自己的一套生命周期回调方法和处理自己的用户输入事件. 对应生命周期可参考下图: 创建片元(Creating a Fragment) To c ...
- Android ActionBar详解(二)--->使用ActionBar显示选项菜单
MainActivity如下: package cc.testsimpleactionbar1; import android.os.Bundle; import android.app.Activi ...
- (转)android Fragments详解二:创建Fragment
创建Fragment 要创建fragment,必须从Fragment或Fragment的派生类派生出一个类.Fragment的代码写起来有些像activity.它具有跟activity一样的回调方法, ...
- Android Loader详解二:使用加载器
一个使用装载器的应用会典型的包含如下组件: 一个Activity或Fragment. 一个LoaderManager的实例. 一个加载被ContentProvider所支持的数据的CursorLoad ...
- Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)
[Android布局学习系列] 1.Android 布局学习之——Layout(布局)详解一 2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数) 3.And ...
- Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)
View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇 ...
- Android 签名详解
Android 签名详解 AndroidOPhoneAnt设计模式Eclipse 在Android 系统中,所有安装 到 系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程 ...
- Android编译系统详解(一)
++++++++++++++++++++++++++++++++++++++++++ 本文系本站原创,欢迎转载! 转载请注明出处: http://blog.csdn.net/mr_raptor/art ...
随机推荐
- ●Codevs 4158 残缺的字符串
题链: http://codevs.cn/problem/4158/ 题解: FFT. 定义两个相同长度的字符串s1,s2的距离为 $$dis(s1,s2)=\sum_{i=0}^{len-1}(s1 ...
- hdu 5893 (树链剖分+合并)
List wants to travel Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/O ...
- VK Cup 2017 - Квалификация 1
CF上的VK Cup 2017资格赛1,好像很水,因为只有俄文所以语言是最大的障碍--不过之后正式赛貌似就有英文了.(比赛貌似只有开俄文模式才看的到--) 时长1天,不随时间扣分.FallDream ...
- hdu 5428
题意:一个数是这n个数的乘,找出它一个不是素数的最小因子 求出所有数的所有质因子中最小的两个,相乘就是答案.如果所有数字的质因子个数不到两个,那么就是无解. #include<iostream& ...
- Thinkphp中的U函数(Thinkphp3.2.3版本)
U函数的作用是根据当前的URL设置生成对应的URL地址,使用U函数可以确保项目在移植过程中不受环境的影响. U方法的定义规则如下(方括号内参数根据实际应用决定): U('地址表达式',['参数'],[ ...
- gcc编译器的工作流程
参考资料:http://www.cnblogs.com/dfcao/p/csapp_intr1_1-2.html 在linux系统上,从源文件到目标文件的转化是由编译器完成的.以hello.c程序的编 ...
- Windows10下配置python的环境变量
从官网下载Windows下的python版本,一路按照默认进行安装. 安装之后配置环境变量的步骤如下: 1,点"我的电脑",右键选"属性". 2,选择" ...
- C# TextBox 焦点
TextBox焦点问题 1.失焦 KeyBoard.ClearFocus(); 存在一个问题,失去焦点之后,中文通过输入法依旧是可以输入的. 如果是中文文本框,按Enter失焦,同时禁止输入中文,可以 ...
- 判断当前设备是移动端或者PC端
<script> function browserRedirect() { var sUserAgent = navigator.userAgent.toLowerCase(); var ...
- UCSC下载ENCODE数据
ENCODE数据库用于存放基因组原件,所有的测序数据(原始数据以及每一步处理后的数据以及最终的结果)都是开放下载的.假如说去官网下载的话会比较麻烦,这里可以通过UCSC的数据库下载(真的是神器啊)!下 ...