移动端web页面调用原生jsbridge的封装

为了统一android端和ios端调用原生jsbridge方法统一,且web端不需要使用callback方式接收回调,特封装了以下js工具类:

// 全局回调管理器
window.CallbackManager = {
      callbacks: new Map(),

registerCallback: function (callbackId, callback) {
this.callbacks.set(callbackId, callback);
},

unregisterCallback: function (callbackId) {
this.callbacks.delete(callbackId);
},

invokeCallback: function (callbackId, result) {
    const callback = this.callbacks.get(callbackId);
    console.log("查看回调数据:"+callbackId+"***"+result);
    if (callback) {
        callback(result);
        console.log("执行了回调");
        this.unregisterCallback(callbackId);
    }
},

invokeCallbackForIOS: function (result) {
let decodedResponse = decodeURIComponent(result);
const jsonObject = JSON.parse(decodedResponse);
let callbackId = jsonObject.callbackId

const callback = this.callbacks.get(callbackId);
console.log("查看回调数据:"+callbackId+"***"+result);
if (callback) {
    callback(jsonObject.result);
    console.log("执行了回调");
    this.unregisterCallback(callbackId);
                 }
  }

};

window.mobileNativeMethod= {
callHandler: function (methodName, params, callback) {
    let userAgent = navigator.userAgent;
    if (/iPad|iPhone|iPod/.test(userAgent)) {
 // iOS平台调用原生方法
        let callbackId = Math.random().toString(36).substring(7);
        let jsonParams = JSON.parse(params)
        jsonParams.methodName = methodName
        jsonParams.callbackId = callbackId
        window.webkit.messageHandlers.mobileNativeMethod.postMessage(JSON.stringify(jsonParams));
        CallbackManager.registerCallback(callbackId, callback);

    } else if (userAgent.indexOf('Android') > -1) {
        // Android平台调用原生方法
        if (window.androidJavascript) {
            console.log("查看调用:"+methodName+"***"+params)

            let callbackId = Math.random().toString(36).substring(7);
            window.androidJavascript.callHandler(methodName, params, callbackId);
            CallbackManager.registerCallback(callbackId, callback);
        }
    } else {
        console.warn('Unsupported platform');
    }
  }
};

在web页面移动端统一使用 **window.mobileNativeMethod.callBack(methodName,params, callback)**方法调用原生实现的方法。

methodName是字符串方法名,params是Map参数,如果需要接收回调需要实现callback,如果不需要回调可以省略回调。

此处以android原生端实现的jsbridge为例,示例实现如下:

public class MyWebViewJavaScript {

private final String TAG = "MyWebViewJavaScript";

private TextView mTvtitle;

private SetWebTitle mSetWebTitle;

List mHandles;

public Map<String, String> callBackIdStack;

private StWebView mWebView;

private Handler mHandler;

//响应数据

private String response = "";

public interface SetWebTitle {
    void toSetWebTitle(String title);

    void toHideTitleBar(boolean isHide);
}

public WebViewJavaScript(StWebView webview, SetWebTitle setWebTitle) {
    EventBusUtil.register(this);
    this.mWebView = webview;
    callBackIdStack = new HashMap<>();
    mHandler = new Handler(Looper.getMainLooper());
    this.mSetWebTitle = setWebTitle;
    mHandles = new ArrayList<>();
    //缓存相关的js通讯渠道
    mHandles.add(new CacheChannelImpl());
    //公共的js通讯渠道
    mHandles.add(new CommonChannelImpl(mWebView.getContext()));
}

@JavascriptInterface
public void callHandler(String handleName, String jsonArgs, String callbackId) {
    if (mWebView == null) {
        return;
    }

    //设置web页面标题
    if (handleName.equals("setAppBarTitle")) {
        JSONObject jsonObject = JSON.parseObject(jsonArgs);
        String title = jsonObject.getString("title");
        if (null != mSetWebTitle) {
            mSetWebTitle.toSetWebTitle(title);
        } else {
            Log.d(TAG, "Interface SetWebTitle is null");
        }
        return;
    }

    //是否隐藏标题栏
    if (handleName.equals("hideTitleBar")) {
        JSONObject jsonObject = JSON.parseObject(jsonArgs);
        boolean isHide = jsonObject.getBoolean("isHide");
        if (null != mSetWebTitle) {
            mSetWebTitle.toHideTitleBar(isHide);
        } else {
            Log.d(TAG, "Interface SetWebTitle is null");
        }
        return;
    }

    boolean hasMethod = false;
    for (IJsCallHandle handle : mHandles) {
        if (handle.handle(handleName)) {
            if (handle instanceof CacheChannelImpl) {
                handle.run(mWebView, jsonArgs, callbackId, callBackIdStack);
            } else {
                handle.run(mWebView, jsonArgs);
                callBackIdStack.put(handleName, callbackId);
            }
            hasMethod = true;
            break;
        }
    }

    if (!hasMethod) {
        //未实现方法 handleName
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Gson gson = new GsonBuilder().disableHtmlEscaping() // 禁用 HTML 转义
                        .create();
                HashMap<String, Object> mmap = new HashMap<>();
                mmap.put("result", "");
                mmap.put("code", "error");
                mmap.put("message", String.format("未实现方法%s", handleName));
                mmap.put("methodName", handleName);
                response = gson.toJson(mmap);
                String jsFunctionCall = "javascript:CallbackManager.invokeCallback('" + callbackId + "', '" + response + "')";
                mWebView.loadUrl(jsFunctionCall);
                callBackIdStack.remove(handleName);
            }
        });
    }
  }
}

CommonChannelImpl的实现示例:

public class CommonChannelImpl implements IJsCallHandle {
private Map<String, Boolean> methods = new HashMap<String, Boolean>();
private String mHandleName = "";
private Context mContext;

public CommonChannelImpl(Context context) {
    this.mContext = context;
    //权限请求
    methods.put("requestPermission", false);
    //获取应用基本信息
    methods.put("getAppInfo", false);
    //web页面导航返回
    methods.put("NavigatorPop", false);
    //打开浏览器
    methods.put("openBrowser", false);
    //跳转页面
    methods.put("jumpPage", false);
    //打电话
    methods.put("callPhone", false);
}

@Override
public boolean handle(String handleName) {
    if (methods.containsKey(handleName)) {
        mHandleName = handleName;
        methods.put(handleName, true);
        return true;
    }
    return false;
}

@Override
public void run(StWebView webView, String jsonArgs) {
    long requestCode = 0;
    if (!TextUtils.isEmpty(jsonArgs)) {
        JSONObject jobj = JSON.parseObject(jsonArgs);
        if (jobj.containsKey("requestCode")) {
            requestCode = jobj.getLong("requestCode");
        }
    }
    switch (mHandleName) {
        case "requestPermission":
            EventMessage<Object> eventMessage1 = new EventMessage<>(0, requestCode, "module-permission", mHandleName, jsonArgs);
            EventBusUtil.sendEvent(eventMessage1);
            break;
        case "getAppInfo":
            EventMessage<Object> eventMessage = new EventMessage<>(0, requestCode, "component-phoneinfo", mHandleName, jsonArgs);
            EventBusUtil.sendEvent(eventMessage);
            break;
        case "NavigatorPop":
            Handler mainHandler = new Handler(Looper.getMainLooper());
            mainHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (webView.canGoBack()) {
                        webView.goBack();
                    } else {
                        if (mContext instanceof AppCompatActivity) {
                            ((AppCompatActivity) mContext).finish();
                        }
                    }
                }
            });
            break;
        case "openBrowser":
            JSONObject jsonObject = JSON.parseObject(jsonArgs);
            if (jsonObject != null) {
                String url = jsonObject.getString("url");
                if (!url.contains("http")) {
                    url = "http://" + url;
                }
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse(url));
                mContext.startActivity(intent);
            }
            break;
        case "jumpPage":
            JSONObject jsonObject1 = JSON.parseObject(jsonArgs);
            String pageUrl = jsonObject1.getString("pageUrl");
            JSONObject params = jsonObject1.getJSONObject("params");
            Map<String, Object> map = new HashMap<>();
            for (String key : params.keySet()) {
                map.put(key, params.get(key));
            }
            ARouterHelper.navigateToFlutterPage(pageUrl, map);
            break;
        case "callPhone":
            JSONObject jsonObject2 = JSON.parseObject(jsonArgs);
            String number = jsonObject2.getString("number");
            Intent intent = new Intent(Intent.ACTION_DIAL);
            intent.setData(Uri.parse("tel:" + number));
            mContext.startActivity(intent);
            break;
        default:
            break;
    }
  }
}
相关推荐
Watermelo61712 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_7482489413 分钟前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_7482356125 分钟前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖9 小时前
[react]searchParams转普通对象
开发语言·前端·javascript