原文出自: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:

  1. 导入jar包
  2. 直接copy jar的源码到工程

    JsBridge. jar可以到gitHub上直接下载。

    Android Studio:

  3. 配置gradle

    1. repositories {
    2. // ...
    3. maven { url "https://jitpack.io" }
    4. }
    5. dependencies {
    6. compile 'com.github.lzyzsd:jsbridge:1.0.4'
    7. }

2 WebView需使用jsBridge的webView

  1. <com.github.lzyzsd.jsbridge.BridgeWebView
  2. android:id="@+id/webView"
  3. android:layout_width="match_parent"
  4. 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)

    1. //必须和js同名函数,注册具体执行函数,类似java实现类
    2. webView.registerHandler("submitFromWeb", new BridgeHandler() {
    3. @Override
    4. public void handler(String data, CallBackFunction function) {
    5. function.onCallBack( data + java”);
    6. }
    7. });

3.2 JS

Js代码需要用window.WebViewJavascriptBridge.callHandler同步回调java层注册的同名函数,这和java和c库的jni调用如出一辙,方法名必须和Java层保持一致

  • 第一参数:方法名
  • 第二个:js调用native的请求参数
  • 第三个:js在被回调后具体执行方法,responseData为java层回传jsonStr.

    1. window.WebViewJavascriptBridge.callHandler(
    2. 'submitFromWeb'
    3. , {'param': requsetData }
    4. , function(responseData) {
    5. // do somrthing
    6. }
    7. );

案列实践

* Activity*

  • 初始化WebView,设置必要的参数。

    1. mWebView = (BridgeWebView) findViewById(R.id.webView);
    2. // 设置具体WebViewClient
    3. mWebView.setWebViewClient(new MyWebViewClient(mWebView));
    4. // set HadlerCallBack
    5. mWebView.setDefaultHandler(new myHadlerCallBack());
    6. // setWebChromeClient
    7. mWebView.setWebChromeClient(new WebChromeClient());
  • 需要自定义HandlerCallBack和WebViewClent;

    1. /**
    2. * 自定义的WebViewClient
    3. */
    4. class MyWebViewClient extends BridgeWebViewClient {
    5. public MyWebViewClient(BridgeWebView webView) {
    6. super(webView);
    7. }
    8. }
    9. /**
    10. * 自定义回调
    11. */
    12. class myHadlerCallBack extends DefaultHandler {
    13. @Override
    14. public void handler(String data, CallBackFunction function) {
    15. if(function != null){
    16. Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
    17. }
    18. }
    19. }
  • 加载网页 支持本地和远程

    1. mWebView.loadUrl("file:///android_asset/demo.html");

1 java调用js函数

订阅一个叫submitFromWeb 的方法名 并会写回调函数

  1. mWebView.registerHandler("submitFromWeb", new BridgeHandler() {
  2. @Override
  3. public void handler(String data, CallBackFunction function) {
  4. String str = "这是html返回给java的数据:" + data;
  5. // 例如你可以对原始数据进行处理
  6. str = str + ",Java经过处理后截取了一部分:" + str.substring(0, 5);
  7. Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
  8. Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
  9. //回调返回给Js
  10. function.onCallBack(str);
  11. }
  12. });

js同样需要java注册订阅的同名函数

  1. //call native method
  2. window.WebViewJavascriptBridge.callHandler(
  3. 'submitFromWeb'
  4. , {'param': data }
  5. , function(responseData) {
  6. document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
  7. }
  8. );

2 JS调用Java代码

反之js调用java代码

  • Js 订阅一个叫functionInJs的方法

    1. // 注册一个"functionInJs",
    2. bridge.registerHandler("functionInJs", function(data, responseCallback) {
    3. document.getElementById("show").innerHTML = ("data from Java: = " + data);
    4. var responseData = "Javascript Says 我要你的地址!";
    5. // response java层
    6. responseCallback(responseData);
    7. });
  • Java代码响应functionInJs 函数

    1. // 回调 "functionInJs"
    2. mWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
    3. @Override
    4. public void onCallBack(String data) {
    5. // to do somthing
    6. }
    7. });

3 注意事项

无论怎样形式的交互,Js 必须要初始化jsBridge

  1. bridge.init(function(message, responseCallback) {
  2. console.log('JS got a message', message);
  3. var data = {
  4. 'Javascript Responds': '测试中文!'
  5. };
  6. console.log('JS responding with', data);
  7. responseCallback(data);
  8. });
  • 效果图:

代码展示

1. Activty

public class MainActivity extends AppCompatActivity {

  1. private BridgeWebView mWebView;
  2. ValueCallback<Uri> mUploadMessage;
  3. int RESULT_CODE = 0;
  4. private static final String TAG = "MainActivity";
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_main);
  9. Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
  10. setSupportActionBar(toolbar);
  11. FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
  12. fab.setOnClickListener(new View.OnClickListener() {
  13. @Override
  14. public void onClick(View view) {
  15. Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
  16. .setAction("Action", null).show();
  17. }
  18. });
  19. mWebView = (BridgeWebView) findViewById(R.id.webView);
  20. initWebView();
  21. }
  22. private void initWebView() {
  23. // 设置具体WebViewClient
  24. mWebView.setWebViewClient(new MyWebViewClient(mWebView));
  25. // set HadlerCallBack
  26. mWebView.setDefaultHandler(new myHadlerCallBack());
  27. // setWebChromeClient
  28. mWebView.setWebChromeClient(new WebChromeClient() {
  29. @SuppressWarnings("unused")
  30. public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType, String capture) {
  31. this.openFileChooser(uploadMsg);
  32. }
  33. @SuppressWarnings("unused")
  34. public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType) {
  35. this.openFileChooser(uploadMsg);
  36. }
  37. public void openFileChooser(ValueCallback<Uri> uploadMsg) {
  38. mUploadMessage = uploadMsg;
  39. pickFile();
  40. }
  41. });
  42. mWebView.loadUrl("file:///android_asset/demo.html");
  43. //必须和js函数名字一致,注册好具体执行回调函数,类似java实现类。
  44. mWebView.registerHandler("submitFromWeb", new BridgeHandler() {
  45. @Override
  46. public void handler(String data, CallBackFunction function) {
  47. String str = "这是html返回给java的数据:" + data;
  48. // 例如你可以对原始数据进行处理
  49. str = str + ",Java经过处理后截取了一部分:" + str.substring(0, 5);
  50. Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
  51. Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
  52. //回调返回给Js
  53. function.onCallBack(str + ",Java经过处理后截取了一部分:" + str.substring(0, 5));
  54. }
  55. });
  56. mWebView.registerHandler("functionOpen", new BridgeHandler() {
  57. @Override
  58. public void handler(String data, CallBackFunction function) {
  59. Toast.makeText(MainActivity.this, "网页在打开你的下载文件预览", Toast.LENGTH_SHORT).show();
  60. pickFile();
  61. }
  62. });
  63. //模拟用户信息 获取本地位置,用户名返回给html
  64. User user = new User();
  65. user.setLocation("上海");
  66. user.setName("Bruce");
  67. // 回调 "functionInJs"
  68. mWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
  69. @Override
  70. public void onCallBack(String data) {
  71. Toast.makeText(MainActivity.this, "网页在获取你的位置,"+ data, Toast.LENGTH_SHORT).show();
  72. }
  73. });
  74. }
  75. /**
  76. * 自定义的WebViewClient
  77. */
  78. class MyWebViewClient extends BridgeWebViewClient {
  79. public MyWebViewClient(BridgeWebView webView) {
  80. super(webView);
  81. }
  82. }
  83. /**
  84. * 自定义回调
  85. */
  86. class myHadlerCallBack extends DefaultHandler {
  87. @Override
  88. public void handler(String data, CallBackFunction function) {
  89. if(function != null){
  90. Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
  91. }
  92. }
  93. }
  94. public void pickFile() {
  95. Intent chooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
  96. chooserIntent.setType("image/*");
  97. startActivityForResult(chooserIntent, RESULT_CODE);
  98. }
  99. @Override
  100. public void onActivityResult(int requestCode, int resultCode, Intent intent) {
  101. if (requestCode == RESULT_CODE) {
  102. if (null == mUploadMessage) {
  103. return;
  104. }
  105. Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
  106. mUploadMessage.onReceiveValue(result);
  107. mUploadMessage = null;
  108. }
  109. }

}

通过实例化webView,用法和安卓原生的view没多大区别,设置WebChromClient, 设置加载的html(同样支持网络和本地文件),接着我们需要给web注册和html端约定好的js方法名。代码列举的submitFromweb和js的执行的方法名一致,玩过NDK的JNI调用的朋友也知道必须和c代码之间有个约定,其实js桥和jni有点类似

2 js代码

  1. function onOpen() {
  2.  var str1 = document.getElementById("text1").value;
  3. var str2 = document.getElementById("text2").value;
  4. var data = "name=" + str1 + ",pass=" + str2;
  5. //call native method
  6. window.WebViewJavascriptBridge.callHandler(
  7. 'functionOpen'
  8. , {'param': data }
  9. , function(responseData) {
  10. //document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData;
  11. }
  12. );
  13. }
  14. function testDiv() {
  15. document.getElementById("show").innerHTML = document.getElementsByTagName("html")[0].innerHTML;
  16. }
  17. function testClick() {
  18. var str1 = document.getElementById("text1").value;
  19. var str2 = document.getElementById("text2").value;
  20. //发送消息给java本地代码
  21. var data = {id: 1, content: "这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"};
  22. window.WebViewJavascriptBridge.send(
  23. data
  24. , function(responseData) {
  25. document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
  26. }
  27. );
  28. }
  29. function testClick1() {
  30. var str1 = document.getElementById("text1").value;
  31. var str2 = document.getElementById("text2").value;
  32. var data = "name=" + str1 + ",pass=" + str2;
  33. //call native method
  34. window.WebViewJavascriptBridge.callHandler(
  35. 'submitFromWeb'
  36. , {'param': data }
  37. , function(responseData) {
  38. document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
  39. }
  40. );
  41. }
  42. function bridgeLog(logContent) {
  43. document.getElementById("show").innerHTML = logContent;
  44. }
  45. function connectWebViewJavascriptBridge(callback) {
  46. if (window.WebViewJavascriptBridge) {
  47. callback(WebViewJavascriptBridge)
  48. } else {
  49. document.addEventListener(
  50. 'WebViewJavascriptBridgeReady'
  51. , function() {
  52. callback(WebViewJavascriptBridge)
  53. },
  54. false
  55. );
  56. }
  57. }
  58. // 第一连接时初始化bridage
  59. connectWebViewJavascriptBridge(function(bridge) {
  60. bridge.init(function(message, responseCallback) {
  61. console.log('JS got a message', message);
  62. var data = {
  63. 'Javascript Responds': '测试中文!'
  64. };
  65. console.log('JS responding with', data);
  66. responseCallback(data);
  67. });
  68. // 注册一个"functionInJs",
  69. bridge.registerHandler("functionInJs", function(data, responseCallback) {
  70. document.getElementById("show").innerHTML = ("data from Java: = " + data);
  71. var responseData = "Javascript Says 我要你的地址!";
  72. // response层
  73. responseCallback(responseData);
  74. });
  75. })
  76. bridge.init(function(message, responseCallback) {
  77. console.log('JS got a message', message);
  78. var data = {
  79. 'Javascript Responds': 'Wee!'
  80. };
  81. console.log('JS responding with', data);
  82. responseCallback(data);
  83. });

这段代码不难理解,我们对上面的id为enter的Button注册了一个点击事件,点击后执行以下testClick()方法,依次类推,其他Button注册事件相同,当点击“发消息给Native”的按钮时,Js通过webWiew的jsBridage.send()发送一条数据给java层(密码和用户名),同时执行function()来执行应java层回调函数的。此demo中是把java返回的数据插入到ID为”show”的div里面去。

testClick1():此方法中调用callHandler来调用Java代码的submitFromweb同名函数,可以结合上面的Activty的代码理解下,此函数调用我们已在java已注册订阅

  1. //必须和js同名函数,注册具体执行函数,类似java实现类。
  2. webView.registerHandler("submitFromWeb", new BridgeHandler() {
  3. @Override
  4. public void handler(String data, CallBackFunction function) {
  5. String str ="这是html返回给java的数据:" + data;
  6. // 例如你可以对原始数据进行处理
  7. makeText(MainActivity.this, str, LENGTH_SHORT).show();
  8. Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
  9. function.onCallBack( str + ",Java经过处理后截取了一部分:"+ str.substring(0,5));
  10. }
  11. });

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 详解(二)的更多相关文章

  1. Android ActionBar详解(二):ActionBar实现Tabs标签以及下拉导航

    一.添加标签 Tabs   在ActionBar中实现标签页可以实现android.app.ActionBar.TabListener ,重写onTabSelected.onTabUnselected ...

  2. Android Fragment详解(二):Fragment创建及其生命周期

    Fragments的生命周期 每一个fragments 都有自己的一套生命周期回调方法和处理自己的用户输入事件. 对应生命周期可参考下图: 创建片元(Creating a Fragment) To c ...

  3. Android ActionBar详解(二)--->使用ActionBar显示选项菜单

    MainActivity如下: package cc.testsimpleactionbar1; import android.os.Bundle; import android.app.Activi ...

  4. (转)android Fragments详解二:创建Fragment

    创建Fragment 要创建fragment,必须从Fragment或Fragment的派生类派生出一个类.Fragment的代码写起来有些像activity.它具有跟activity一样的回调方法, ...

  5. Android Loader详解二:使用加载器

    一个使用装载器的应用会典型的包含如下组件: 一个Activity或Fragment. 一个LoaderManager的实例. 一个加载被ContentProvider所支持的数据的CursorLoad ...

  6. Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

    [Android布局学习系列]   1.Android 布局学习之——Layout(布局)详解一   2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)   3.And ...

  7. Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

    View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇  ...

  8. Android 签名详解

    Android 签名详解 AndroidOPhoneAnt设计模式Eclipse  在Android 系统中,所有安装 到 系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程 ...

  9. Android编译系统详解(一)

    ++++++++++++++++++++++++++++++++++++++++++ 本文系本站原创,欢迎转载! 转载请注明出处: http://blog.csdn.net/mr_raptor/art ...

随机推荐

  1. [USACO09FEB]改造路Revamping Trails

    题目描述 Farmer John dutifully checks on the cows every day. He traverses some of the M (1 <= M <= ...

  2. NOIP2014-11-3模拟赛

    字符串 题目描述 现在给一个字符串,你要做的就是当这个字符串中存在两个挨着的字符是相同的时就将这两个字符消除.需要注意的是,当把这两个字符消除后,可能又产生一对新的挨着的字符是相同的.比如,初始的字符 ...

  3. poj1741Tree 点分治

    上午学习了点分治,写了1个半小时终于写出一个代码--poj1741,可以说是个模板题. 分治:对于每个儿子找出重心,分别处理 注意:1.每次处理一个重心后,ans减去对它儿子的处理 原因:因为统计方法 ...

  4. Ubuntu 16.04 Vim安装及配置

    安装VIM 默认已经安装了VIM-tiny acewu@acewu-computer:~$ locate vi | grep 'vi$' |xargs ls -al lrwxrwxrwx 1 root ...

  5. 笔记12 注入AspectJ切面

    虽然Spring AOP能够满足许多应用的切面需求,但是与AspectJ相比, Spring AOP 是一个功能比较弱的AOP解决方案.AspectJ提供了Spring AOP所不能支持的许多类型的切 ...

  6. ubuntu 16.04 安装 tensorflow-gpu 包括 CUDA ,CUDNN,CONDA

    ubuntu 16.04 安装 tensorflow-gpu 包括 CUDA ,CUDNN,CONDA 显卡驱动装好了,如图: 英文原文链接: https://github.com/williamFa ...

  7. glusterfs 4.0.1 rpc 分析笔记2 (socket.so 模块)

    socket.c在4000行位置定义了一组结构函数,我们可以从这里开始找到入口,如果是客户端则需要调用connect, 如果是服务端则需要调用listen, struct rpc_transport_ ...

  8. 腾讯北京SNG一面

    写在前面 面试官超nice,以一种聊天的形式跟你交流.上午10点10左右开始,11点40结束.总的来说,基础还可以,但是有些东西的底层学的还是不够深. 问题回忆 自我介绍 怎么平衡科研与项目开发之间的 ...

  9. AQS简简单单过一遍

    前言 回顾前面: 多线程三分钟就可以入个门了! Thread源码剖析 多线程基础必要知识点!看了学习多线程事半功倍 Java锁机制了解一下 只有光头才能变强! 本来我是打算在这章节中写Lock的子类实 ...

  10. Apache软件基金会项目百度百科链接

    Apache软件基金会 顶级项目 ▪ ActiveMQ ▪ Ant ▪ Apache HTTP Server ▪ APR ▪ Beehive ▪ Camel ▪ Cassandra ▪ Cayenne ...