之前通过Uniapp+Html5plus实现过实现通话录音上传的功能,一直有一个痛点:录音文件很多的时候通过htmlplus来获取录音文件会很卡。
刚好看到新出的UniappX可以直接把类似ts的uts代码编译成原生代码,而且整个开发流程都可以在Hbuilder内完成,于是乎来了兴趣,试上一试!
开发思路
既然可以通过uts来实现原生开发,但是又想保持Web来进行APP的前端开发,那就直接用老一套的混合开发的方式:通过JS调用写好的原生方法。
相关代码已开源,Github:github.com/friend-nice...
1.通过uts实现读取录音文件
javascript
/* 将功能封装为类:在构造函数中设置 audioPath */
export default class Scanner {
....
/* 读取目录下最新的录音文件 */
getLatestAudio(option : QueryLatest) : LatestAudioResult {
/* 定义录音文件路径 */
if (option.audioPath != null) {
option.audioPath = this.sdRoot + option.audioPath;
const initDir = new File(option.audioPath as string);
if (!initDir.exists() || !!initDir.isDirectory()) {
option.audioPath = this.getDevicePath();
}
} else {
option.audioPath = this.getDevicePath();
}
/* 目录对象 */
const dir = new File(option.audioPath as string);
if (!dir.exists() || !dir.isDirectory()) {
throw new UniError('scanner', 0, '录音文件目录不存在');
}
const files = dir.listFiles();
if (files == null || files.size == 0) {
throw new UniError('scanner', 0, '本次通话没有产生录音文件');
}
let latest : File | null = null;
const countInt : Int = files.size;
const count : number = countInt;
option.latestTime = option.latestTime * 1000;
for (let i : Int = 0; i < countInt; i = i + 1) {
const f = files[i];
if (f != null && f.isFile()) {
const lm = f.lastModified();
if (lm >= option.latestTime) {
latest = f;
option.latestTime = lm;
}
}
}
if (latest != null) {
return { file: latest.getAbsolutePath(), count: count };
}
throw new UniError('scanner', 0, '本次通话没有产生录音文件');
}
....
}
以下省略了若干代码,仅展示核心代码,完整代码请查看上方开源项目
2.通过webview对象向js提供直接调用的方法
通过监听webview组件的message事件,来获取js发送过来的消息,然后进行对应的处理,最后通过webview对象的evalJs方法,将结果回调给前端。相关代码如下:
javascript
/* 消息监听 */
const onPostMessage = (e) => {
/* 数据 */
const _list = e.detail.data;
const _action = _list[0].action;
const _data = _list[0].data;
/* 初始化 */
if (!_wv) {
/* 获取webview对象 */
const _wvs = plus.webview.getDisplayWebview().filter(wv => {
return wv.getURL().indexOf(host) > -1;
});
/* 弹出异常 */
if (!_wvs.length) {
load.toast("服务异常");
return;
}
/* webview对象 */
_wv = _wvs[0];
_wv.onCall = (action, success, payload) => {
_wv.evalJS('window.__onCall(`' + JSON.stringify({
action,
success,
payload
}) + '`)');
}
}
/* 判断对象是否有这个属性 */
try {
if (!!scanner[_action]) {
_wv.onCall(_action, true, scanner[_action](_data));
}
} catch (e) {
const match = e.message.match(/errMsg='([^']+)'/);
_wv.onCall(_action, false, match && match[1] ? match[1] : '系统异常');
}
}
3.前端JS封装和APP通信的方法
通过下方封装的模块,可以直接通过await同步调用APP对应的方法。
javascript
/**
* NativeBridge.es6.js
* ES Module,action 级回调,默认 15 s 超时
* import { invoke } from './NativeBridge.es6.js';
* await invoke({ action:'getDeviceId', timeout:10_000 });
*/
const pool = Object.create(null); // action -> {resolve, reject, timer}
/**
* App 回传结果的唯一全局钩子
* 调用方式(Android):
* webView.evaluateJavascript('window.__onCall('{"action":"getDeviceId","success":true,"payload":"fake-123"}')', null);
* payload 可省略。
*/
window.__onCall = function (json) {
try {
const {action, success, payload} = JSON.parse(json);
/* 格式非法,直接丢弃 */
if (typeof action !== 'string') return;
const item = pool[action];
/* 已超时或重复回调 */
if (!item) return;
const {resolve, reject, timer} = item;
clearTimeout(timer);
delete pool[action];
success ? resolve(payload) : reject(new Error(payload ?? 'native error'));
} catch {
/* 非合法 JSON,直接忽略 */
}
};
/**
* 调用 App 内部方法
* @param {Object} opt
* @param {string} opt.action - 必填,方法名
* @param {any} [opt.params] - 选填,参数,默认空对象
* @param {number} [opt.timeout=15000] - 选填,超时毫秒
* @returns {Promise<any>}
*/
export default function invoke(opt = {}) {
/* 默认配置 */
const {action, data = {}, timeout = 15000} = opt;
/* 参数校验 */
if (typeof action !== 'string' || !action)
return Promise.reject(new TypeError('action is required'));
return new Promise((resolve, reject) => {
/* 如果已存在同 action 的未完成调用,先清掉 */
if (pool[action]) {
clearTimeout(pool[action].timer);
pool[action].reject(new Error('covered by new call'));
}
/* 超时器,防止调用超时 */
const timer = setTimeout(() => {
delete pool[action];
reject(new Error(`invoke ${action} timeout`));
}, timeout);
/* 回调 */
pool[action] = {resolve, reject, timer};
/* 发送消息 */
uni.postMessage({
data: {
action,
data: data
}
});
});
}
4.相关扩展
上方展示了一个通过UniappX简单实现的混合开发的框架,在此基础上可以实现更多的原生方法来扩展功能,同时兼顾性能和Web开发的便捷。