Android与JS交互篇--JSBridge的使用

在android日常开发中,大家或多或少都会碰到原生嵌套web页面,大家可以使用传统的方式实现Native与JS的交互,这里就不多介绍了,现在我们简单介绍下网上目前比较流行的已经封装好的框架JsBridge。

可参考官网github地址:点击打开链接

先看下接下来将要实现的效果图:


Android与JS交互篇--JSBridge的使用_第1张图片

一、JsBridge的基本概念

Android4.4以前,谷歌的webview存在安全漏洞,网站可以通过js注入就可以随便拿到客户端的重要信息,甚至轻而易举的调用本地代码进行流氓行为,谷歌在4.4以后增加了防御措施,如果用js调用本地代码,开发者必须在代码声明JavascriptInterface, 4.4之前我们要使得webView加载js只需如下代码:

mWebView.addJavascriptInterface(new JsToJava(), “myfunction”);
4.4之后使用时, 需要在调用Java方法加入@JavascriptInterface注解,如果代码无此声明,那么js就不生效,这样就可以避免恶意网页利用js对客户端的进行窃取和攻击。 但使用比较繁琐,要做一些判断和限制,在比较复杂的Hybrid模式下,需要js和native之间进行交互通讯,原生的JavascriptInterface 难以维护,基于JavascriptInterface 封装的WebViewJavascriptBridge框架,很好的解决了这一问题。

WebViewJavascriptBridge是移动UIView和Html交互通信的桥梁,用于替代WebView自带的JavascriptInterface接口,使开发者可以简单安全的实现js和native交互。

二、以上图为案例介绍JSBridge的使用方法

首先在Module的build.gradle里引入JSBridge所需要的包,也可以下载源码引入项目自定义库文件

repositories {
    maven { url "https://jitpack.io" }
}

dependencies {
    ...
    compile 'com.github.lzyzsd:jsbridge:1.0.4'
}

在xml文件中使用控件




    

    

MainActivity主界面中使用

下面先简单介绍下重点几步操作:

1、加载网页地址
mBridgeWebview.loadUrl("file:///android_asset/web.html");
2、注册handler,接收来自js的数据data,并可通过onCallBack回传数据
mBridgeWebview.registerHandler("submitFromWeb", new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                if (!TextUtils.isEmpty(data)) {
                    mEditText.setText("通过调用Native方法接收数据:\n" + data);
                }
                function.onCallBack("Native已经接收到数据:" + data + ",请确认!");
            }
        });

对应的js代码如下

function useClick() {
            var name = document.getElementById("uname").value;
            var pwd = document.getElementById("psw").value;
            var data = "name = " + name + ", password = " + pwd;

            window.WebViewJavascriptBridge.callHandler(
                'submitFromWeb', {'info':data},//data发送给native
                function(responseData) {//接收onCallBack回传回来的数据
                    document.getElementById("show").innerHTML = responseData;
                }
            );
     }

上面这种方法是通过native和js定义相同的标识进行通信,还有种方法可直接在js中通过send方法发送,并在native中创建DefaultHandler接收

mBridgeWebview.setDefaultHandler(new DefaultHandler(){
            @Override
            public void handler(String data, CallBackFunction function) {
                function.onCallBack("Native已收到消息!");
            }
        });

对应js代码如下:

//h5直接通过send向Native发送消息,在DefaultHandler的handler方法里接收,并可通过onCallBack方法回传
    function sendClick() {
        var name = document.getElementById("uname").value;
        var pwd = document.getElementById("psw").value;
        var data = "name = " + name + ", password = " + pwd;

        window.WebViewJavascriptBridge.send(
            data,
            function(responseData) {
                document.getElementById("show").innerHTML = responseData
            }
        );
    }
3、定义callHandler,多用于在初始化界面时native向js发送数据渲染界面,同时也可获取来自js的数据
mBridgeWebview.callHandler("functionInJs", new Gson().toJson(new UserInfo("liuw", "123456")), new CallBackFunction() {
            @Override
            public void onCallBack(String data) {
                mEditText.setText("向h5发送初始化数据成功,接收h5返回值为:\n" + data);
            }
        });

对应的js代码如下

function connectWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) {
                callback(WebViewJavascriptBridge)
            } else {
                document.addEventListener(
                    'WebViewJavascriptBridgeReady'
                    , function() {
                        callback(WebViewJavascriptBridge)
                    },
                    false
                );
            }
        }

// 第一连接时初始化bridage
connectWebViewJavascriptBridge(function(bridge) {
            //注册handler等待java代码调用
            //初始化时获取数据是调用此处代码
            //参数:标识,要传递到JAVA的数据,回调方法。
            //JAVA代码响应的方法:mBridgeWebview.callHandler("functionInJs", new Gson().toJson(实体类对象), new CallBackFunction(){onCallBack(String data)}
            bridge.registerHandler("functionInJs", function(data, responseCallback) {
                document.getElementById("show").innerHTML = ("data from Java: = " + data);
                var responseData = "I am javascript, Data reception success!";
                responseCallback(responseData);
            });
})
4、Native中send方法的使用
这里需要注意下,该方法对应js中的bridge.init处理,此处需加CallBackFunction方法,如果只使用mBridgeWebview.send("");如:mBridgeWebview.send("hello");,会导致js中只收到通知,接收不到值
mBridgeWebview.send("来自java的发送消息!!!", new CallBackFunction() {
            @Override
            public void onCallBack(String data) {
                Toast.makeText(MainActivity.this, "bridge.init初始化数据成功" + data, Toast.LENGTH_SHORT).show();
            }
        });

对应的js代码如下

function connectWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) {
                callback(WebViewJavascriptBridge)
            } else {
                document.addEventListener(
                    'WebViewJavascriptBridgeReady'
                    , function() {
                        callback(WebViewJavascriptBridge)
                    },
                    false
                );
            }
        }

    // 第一连接时初始化bridage
    connectWebViewJavascriptBridge(function(bridge) {
            //也注册默认的Handler,用来接收java调用的send(string,CallBackFunction)方法
            bridge.init(function(message, responseCallback) {
                console.log('JS got a message', message);
                var data = {
                    'Javascript Responds': '测试中文!'
                };
                console.log('JS responding with', data);
                responseCallback(data);
            });
    })
5、再介绍一种常规的Native直接向js传递数据的方法
//直接调用nativeFunction方法向H5发送数据
mBridgeWebview.loadUrl("javascript:nativeFunction('" + data + "')");

对应的js代码如下

    function nativeFunction(data) {
        document.getElementById("show").innerHTML = data;
    }

只需通过方法名直接调用即可,是不是很简单啊~

三、下面上完整代码

package com.liuw.jsbridge;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.github.lzyzsd.jsbridge.BridgeHandler;
import com.github.lzyzsd.jsbridge.BridgeWebView;
import com.github.lzyzsd.jsbridge.CallBackFunction;
import com.github.lzyzsd.jsbridge.DefaultHandler;
import com.google.gson.Gson;
import com.liuw.jsbridge.bean.UserInfo;

import butterknife.ButterKnife;
import butterknife.InjectView;

public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.edit_view)
    EditText mEditText;
    @InjectView(R.id.btn_send)
    Button btnSend;
    @InjectView(R.id.bridge_webview)
    BridgeWebView mBridgeWebview;
    @InjectView(R.id.btn_reset)
    Button btnReset;

    private String TAG = "MainActivity";
    private MyHandlerCallBack.OnSendDataListener mOnSendDataListener;
    private ValueCallback mUploadMessage;
    ;
    private ValueCallback mUploadCallbackAboveL;
    private final static int FILECHOOSER_RESULTCODE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);

        initListener();
        initWebView();
    }

    private void initWebView() {
        //辅助WebView设置处理关于页面跳转,页面请求等操作
        mBridgeWebview.setWebViewClient(new MyWebViewClient(mBridgeWebview, MainActivity.this));
        //Handler做为通信桥梁的作用,接收处理来自H5数据及回传Native数据的处理,当h5调用send()发送消息的时候,调用MyHandlerCallBack
        mBridgeWebview.setDefaultHandler(new MyHandlerCallBack(mOnSendDataListener));
        //WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等比等,不过它还能处理文件上传操作
        mBridgeWebview.setWebChromeClient(new MyChromeWebClient());
        // 如果不加这一行,当点击界面链接,跳转到外部时,会出现net::ERR_CACHE_MISS错误
        // 需要在androidManifest.xml文件中声明联网权限
        // 
        if (Build.VERSION.SDK_INT >= 19) {
            mBridgeWebview.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
        }

        //加载网页地址
        mBridgeWebview.loadUrl("file:///android_asset/web.html");

        //有方法名的都需要注册Handler后使用
        mBridgeWebview.registerHandler("submitFromWeb", new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                Log.i("liuw", "html返回数据为:" + data);
                if (!TextUtils.isEmpty(data)) {
                    mEditText.setText("通过调用Native方法接收数据:\n" + data);
                }
                function.onCallBack("Native已经接收到数据:" + data + ",请确认!");
            }
        });

        mBridgeWebview.registerHandler("functionOpen", new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                Toast.makeText(MainActivity.this, "网页在打开你的文件预览", Toast.LENGTH_SHORT).show();
            }
        });

        //应用启动后初始化数据调用,js处理方法connectWebViewJavascriptBridge(function(bridge)
        mBridgeWebview.callHandler("functionInJs", new Gson().toJson(new UserInfo("liuw", "123456")), new CallBackFunction() {
            @Override
            public void onCallBack(String data) {
                mEditText.setText("向h5发送初始化数据成功,接收h5返回值为:\n" + data);
            }
        });

        //对应js中的bridge.init处理,此处需加CallBackFunction,如果只使用mBridgeWebview.send("");会导致js中只收到通知,接收不到值
        mBridgeWebview.send("来自java的发送消息!!!", new CallBackFunction() {
            @Override
            public void onCallBack(String data) {
                Toast.makeText(MainActivity.this, "bridge.init初始化数据成功" + data, Toast.LENGTH_SHORT).show();
            }
        });

    }

    private void initListener() {
        btnSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String data = mEditText.getText().toString();
                //直接调用nativeFunction方法向H5发送数据
                mBridgeWebview.loadUrl("javascript:nativeFunction('" + data + "')");
            }
        });

        btnReset.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mBridgeWebview.loadUrl("file:///android_asset/web.html");
            }
        });

        mOnSendDataListener = new MyHandlerCallBack.OnSendDataListener() {
            @Override
            public void sendData(String data) {
                mEditText.setText("通过webview发消息接收到数据:\n" + data);
            }
        };
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage && null == mUploadCallbackAboveL) {
                return;
            }
            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
            if (mUploadCallbackAboveL != null) {
                onActivityResultAboveL(requestCode, resultCode, data);
            } else if (mUploadMessage != null) {
                mUploadMessage.onReceiveValue(result);
                mUploadMessage = null;
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) {
        if (requestCode != FILECHOOSER_RESULTCODE
                || mUploadCallbackAboveL == null) {
            return;
        }

        Uri[] results = null;
        if (resultCode == Activity.RESULT_OK) {
            if (data == null) {

            } else {
                String dataString = data.getDataString();
                ClipData clipData = data.getClipData();

                if (clipData != null) {
                    results = new Uri[clipData.getItemCount()];
                    for (int i = 0; i < clipData.getItemCount(); i++) {
                        ClipData.Item item = clipData.getItemAt(i);
                        results[i] = item.getUri();
                    }
                }

                if (dataString != null) {
                    results = new Uri[]{Uri.parse(dataString)};
                }
            }
        }
        mUploadCallbackAboveL.onReceiveValue(results);
        mUploadCallbackAboveL = null;
    }

    //自定义 WebChromeClient 辅助WebView处理图片上传操作【 文件上传标签,点击会自动调用】
    public class MyChromeWebClient extends WebChromeClient {
        // For Android 3.0-
        public void openFileChooser(ValueCallback uploadMsg) {
            Log.d(TAG, "openFileChoose(ValueCallback uploadMsg)");
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            MainActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

        // For Android 3.0+
        public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
            Log.d(TAG, "openFileChoose( ValueCallback uploadMsg, String acceptType )");
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            MainActivity.this.startActivityForResult(
                    Intent.createChooser(i, "File Browser"),
                    FILECHOOSER_RESULTCODE);
        }

        //For Android 4.1
        public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) {
            Log.d(TAG, "openFileChoose(ValueCallback uploadMsg, String acceptType, String capture)");
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            MainActivity.this.startActivityForResult(Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE);
        }

        // For Android 5.0+会调用此方法
        @Override
        public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) {
            Log.d(TAG, "onShowFileChooser(ValueCallback uploadMsg, String acceptType, String capture)");
            mUploadCallbackAboveL = filePathCallback;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            MainActivity.this.startActivityForResult(
                    Intent.createChooser(i, "File Browser"),
                    FILECHOOSER_RESULTCODE);
            return true;
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // 处理返回键,在webview界面,按下返回键,不退出程序
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (mBridgeWebview != null && mBridgeWebview.canGoBack()) {
                mBridgeWebview.goBack();
                return true;
            }else {
                System.exit(0);
            }
        }
        return super.onKeyDown(keyCode, event);
    }
}

自定义MyHandlerCallBack类

package com.liuw.jsbridge;

import android.text.TextUtils;
import android.util.Log;

import com.github.lzyzsd.jsbridge.BridgeHandler;
import com.github.lzyzsd.jsbridge.CallBackFunction;

/**
 * Created by liuw on 2018/3/6.
 * 自定义Handler回调
 */

class MyHandlerCallBack implements BridgeHandler {
    private OnSendDataListener mSendDataListener;

    public MyHandlerCallBack(OnSendDataListener mSendDataListener){
        this.mSendDataListener = mSendDataListener;
    }

    @Override
    public void handler(String data, CallBackFunction function) {
        Log.e("liuw","接收数据为:" + data);
        if (!TextUtils.isEmpty(data) && mSendDataListener != null) {
            mSendDataListener.sendData(data);
        }
        function.onCallBack("Native已收到消息!");
    }

    public interface OnSendDataListener {
        void sendData(String data);
    }
}

自定义MyWebViewClient类

package com.liuw.jsbridge;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.webkit.WebView;

import com.github.lzyzsd.jsbridge.BridgeWebView;
import com.github.lzyzsd.jsbridge.BridgeWebViewClient;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

/**
 * Created by liuw on 2018/3/6.
 * 自定义WebViewClient
 */

class MyWebViewClient extends BridgeWebViewClient {
    private Context mContext;
    public MyWebViewClient(BridgeWebView mBridgeWebview, Context context) {
        super(mBridgeWebview);
        mContext = context;
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        Log.i("liuw", "url地址为:" + url);
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //默认操作url地址yy://__QUEUE_MESSAGE__/
        if (url.trim().startsWith("yy:")) {
            return super.shouldOverrideUrlLoading(view, url);
        }
        //特殊情况tel,调用系统的拨号软件拨号【拨打电话110】
        if(url.trim().startsWith("tel")){
            Intent i = new Intent(Intent.ACTION_VIEW);
            i.setData(Uri.parse(url));
            mContext.startActivity(i);
        }else {
            //特殊情况【调用系统浏览器打开】调用系统浏览器
            if(url.contains("csdn")){
                Intent i = new Intent(Intent.ACTION_VIEW);
                i.setData(Uri.parse(url));
                mContext.startActivity(i);
            } else {//其它非特殊情况全部放行
                view.loadUrl(url);
            }
        }
        return true;
    }
}

创建web.html并放入assets资源文件夹下,同时将WebViewJavascriptBridge.js也拷入



    
    
        js调用java
    



Native将要回传回来的数据展示

拨打电话110

webview浏览器

调用系统浏览器

最后不要忘了加网络权限哦

四、总结

1、如果使用registerHandler,那么native和js中定义的标识必须一致

2、大家如果有出现上传文件图片失败,可能是适配原因,安卓系统5.0以上需要在WebChromeClient中实现onShowFileChooser()方法,详情请看代码示例

以上总结不完全,代码中如有错误欢迎指正。


完整代码已上传至GitHub:jsbridge地址

你可能感兴趣的:(java)