From c4df98c26cde5e7e4da0bf0b72e019a01a2386c7 Mon Sep 17 00:00:00 2001 From: Tevin <tingquanren@163.com> Date: Sat, 16 Jul 2022 18:55:43 +0800 Subject: [PATCH] 实现图片文件管理 java 通讯 --- common/Bridge.js | 318 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 318 insertions(+), 0 deletions(-) diff --git a/common/Bridge.js b/common/Bridge.js index e69de29..8514c83 100644 --- a/common/Bridge.js +++ b/common/Bridge.js @@ -0,0 +1,318 @@ +/** + * 跨端双向通讯桥 + * @author Tevin + * @tutorial + * 网页对 App 发起通讯 + * 1. 【App端】App 注册接收器,用于接收网页通知 + * 在页面打开之前,App 注入方法 linking = function(paramStr) { <Native Code> } + * 2. 网页业务中 js 发起通讯 + * $bridge.invoking('methodName', {key1:'value1'}, function(res) {console.log(res)}); + * 3. 由 Bridge 转换为调用注入的方法(含callback) + * window.linking('{method:\'methodName\', param:{key1:\'value1\'}, callback:\'bridge.cb111at1541994536008\'}'); + * 4. 【App端】App 处理完后直接调用网页上的全局回调,并传递结果完成通讯 + * bridge.cb101at1541994536008('{key2:\'value2\'}'); + * App 对网页发起通讯 + * 1. 网页注册接收器,用于接收 App 通知 + * window.telling = function(dataStr) { someJs }; + * 2. 网页业务中注册接收通讯 + * $bridge.register('methodName', function(res, callback) {}); + * 3. 【App端】App 调用网页中的全局方法(至少需要在页面完成并延迟至少1秒之后再调用) + * telling('{method:\'methodName\', param:{key1:\'value1\'}, marker:\'mk222at1541994536008\'}'); + * 4. 网页完成业务逻辑处理后调用注入的方法,并传递结果完成通讯(含marker、不含callback) + * window.linking('{method:\'methodName\', param:{key2:\'value2\'}, marker:\'mk222at1541994536008\'}'); + */ + +import { Fetcher } from '@components/bases/Fetcher'; +import { Tools } from '@components/common/Tools'; +import { $fileTrans } from '@components/common/FileTransform'; + +export class Bridge { + + constructor() { + this._data = { + count: 100, + }; + this._receives = {}; + this._earlyInvok = []; + this._init(); + // 挂载到全局 + window.bridge = this; + } + + /** + * 检查 linking 方法 + * @return {string} + * @private + */ + _checkLinking() { + // 安卓注入 + if (window.aisim && window.aisim.linking) { + return 'android'; + } + // 没有注入 + else { + return ''; + } + } + + /** + * 初始化 + * @private + */ + _init(count = 0) { + // 500 * 20 毫秒后,仍然未注入,视为不工作 + if (!this._checkLinking() && count < 20) { + setTimeout(() => { + this._init(++count); + }, 500); + } + // 开始工作 + else { + this._initReceive(); + // 补发 + if (this._earlyInvok.length > 0) { + this._earlyInvok.forEach(invok => { + const { method, param, callback } = invok; + this._sendLinking(method, param, callback); + }); + } + } + } + + /** + * 发起一次发送 + * @param {string} method + * @param {object} param + * @param {function} callback - 来自网页业务逻辑的回调 + * @private + */ + _sendLinking(method, param, callback) { + // 数据检查 + if (!Tools.isObject(param)) { + console.error('$bridge.invoking 需要接受 JSON 对象!'); + return; + } + // 转换发送参数键名为下划线 + param = this.transKeyName('underline', param); + // 如果有回调,转存回调 + const name = 'cb' + this._data.count++ + 'at' + Date.now(); + this[name] = (res) => { + if (callback && Tools.isFunction(callback)) { + if (res) { + let data; + try { + // 转对象 + data = typeof res === 'string' ? JSON.parse(res) : res; + // 转换接收参数键名为驼峰 + data = this.transKeyName('camel', data); + } catch (e) { + Tools.toast('跨端通讯异常:解析数据失败!'); + return; + } + callback(data); + } else { + callback(); + } + } + delete this[name]; + }; + // 发送 + window.aisim.linking(JSON.stringify({ + method, + param, + callback: 'bridge.' + name, + })); + } + + /** + * 向 app 发起通讯 + * @param {string} method + * @param {object|function} [param] + * @param {function} [callback] + */ + invoking(method, param = {}, callback) { + // param 为函数时 + if (param && Tools.isFunction(param)) { + callback = param; + param = {}; + } + if (this._checkLinking()) { + this._sendLinking(method, param, callback); + } + // 尚未准备好,挂起 + else { + this._earlyInvok.push({ method, param, callback }); + } + } + + /** + * 初始化接收 + * @private + */ + _initReceive() { + window.telling = (res) => { + const data = typeof res === 'string' ? JSON.parse(res) : res; + const { method, param, marker } = data; + // 转换接收参数键名为驼峰 + const param2 = this.transKeyName('camel', param); + if (this._receives[method]) { + // 有通知回调 + if (marker) { + this._receives[method](param2, (param2) => { + this._sendTelling(method, param2 || {}, marker); + }); + } + // 无通知回调 + else { + this._receives[method](param2); + } + } + }; + } + + /** + * 回发App通知的响应 + * @param {string} method + * @param {object} param + * @param {string} marker + * @private + */ + _sendTelling(method, param, marker) { + // 数据检查 + if (!Tools.isObject(param)) { + console.error('$bridge.register 注册的函数需要接受 JSON 对象!'); + return; + } + // 转换发送参数键名为下划线 + param = this.transKeyName('underline', param); + // 发送 + window.aisim.linking(JSON.stringify({ + method, + param, + marker, + })); + } + + /** + * 注册接收指令,可接收 app 通知 + * @param method + * @param callback + */ + register(method, callback) { + this._receives[method] = callback; + } + + /** + * 是否有开始工作 + */ + isWorking() { + const platform = this._checkLinking(); + return platform === 'android' || platform === 'iOS'; + } + + /** + * 键名转换 + * @param type + * @param json + */ + transKeyName(type, json) { + return Fetcher.prototype.transKeyName(type, json); + } + + /* ----- 文件系统 ----- */ + + /** + * 保存文件 base64 到 java + * @param objUrl + * @param callback + */ + fileSave(objUrl, callback) { + // 分段存储 + const saveFileChunk = (baseData, index) => { + const writeData = { + fileName: baseData.fileName, + currentIdx: index, + totalIdx: baseData.total, + data: baseData.baseArr[index], + }; + this.invoking('img_write', writeData, res => { + if (res.result === false) { + Tools.toast(res.msg); + return; + } + // 按分段递归保存 + if (index < baseData.total - 1) { + setTimeout(() => { + saveFileChunk(baseData, index + 1); + }, 10); + } + // 已完成 + else { + callback && callback('bridge:' + baseData.fileName); + } + }); + }; + $fileTrans.transObjUrlToBaseData(objUrl, baseData => { + saveFileChunk(baseData, 0); + }); + } + + /** + * 从 java 读取文件 base64 + * @param fileName + * @param callback + */ + fileLoad(fileName, callback) { + if (fileName.indexOf('bridge:') < 0) { + callback(''); + return; + } + fileName = fileName.split(':')[1]; + const chunkSize = $fileTrans.getChunkSize(); + const baseArr = []; + let totalSize = 0; + let totalCount = 0; + const loadFileChunk = (index) => { + const loadData = { + fileName, + offset: chunkSize * index, + length: chunkSize, + }; + this.invoking('img_read', loadData, res => { + if (totalSize === 0) { + totalSize = res.totalSize; + totalCount = Math.ceil(res.totalSize / chunkSize); + } + baseArr.push(res.data); + // 按分段递归读取 + if (totalCount > 1 && totalCount - 1 > index) { + loadFileChunk(index + 1); + } + // 读取完成 + else { + const baseData = { + baseArr, + fileName, + }; + $fileTrans.transBaseDataToObjUrl(baseData, objUrl => { + callback && callback(objUrl); + }); + } + }); + }; + loadFileChunk(0); + } + + fileRemove(fileName) { + if (fileName.indexOf('bridge:') < 0) { + callback(''); + return; + } + fileName = fileName.split(':')[1]; + this.invoking('img_del', { fileName }); + } + +} + +// 全局服务实例 +export const $bridge = new Bridge(); -- Gitblit v1.9.1