货拉拉Android H5离线方案
Android Hybrid 方案之 离线文件加载
可调式协议页面chrome://inspect/#devices
图片引自
更新及相关配置加载
上传时将文件MD5值记录,后通过接口以json方式下达,用以给客户端校验
菜单点击后 ↬ 离线包版本,本地是否有 ↬ 无则当场加载 ↬ 有则离线加载
//离线包check请求后台返参
{
"bisName":"enteprise-1.0.3", //业务线名-版本名(对应包名)
"updateFlag":0,//-1降一级-2降低两级0没有更新1更新
"downloadUrl":"",//下载包的地址
"effectMode":0,//0冷启动生效 1:立即生效
"versionCode":12,//版本code码
"versionName":"1.0.3",//版本codeName码
"desc":"版本描述",
"md5":"ads123dsafgl1243kmk",
"whiteDomain":["http://www.baidu/com",""]//白色域名名单,只有这些才会被正确访问,其它的譬如小视频网站就会被拦截
}
//离线包check请求参数
{
"bisName":"业务线",//租户号
"os":"android",//android、ios、pc、other
"versionCode":12
}
加载首页规律
首页地址:
host + 包文件名 + path + ...
示例:https://www.baidu.com/?offweb=act3-2108-turntable
需要包名是因为需要校验解压包中的index.html,以此判定是否有离线包
原生导航栏动态设置
H5页面传递给原生数据标题栏根据如下数据配置进行动态更改,标题栏Topic不可隐藏,没有时传""
,isShowTitleBar用来动态控制标题栏是否隐藏true:展示 false:隐藏,默认不配制为显示状态
teaBarLeftBtn1 | teaBarTopic | teaBarRightBtn2 | teaBarRightBtn1 |
---|---|---|---|
左1按钮 | 主标题 | 右二按钮 | 右一按钮 |
{
"isShowTitleBar":true,
"teaBarTopic": "主标题",
"teaBarLeftBtn1": {
"iconUrl": "",
"show": true
},
"teaBarRightBtn2": {
"iconUrl": "",
"show": true
},
"teaBarRightBtn1": {
"iconUrl": "",
"show": true
}
}
标题栏按钮点击调用js方法,js方法根据业务调用本地native方法或者自有逻辑,js方法分别为
teaBarLeftBtn1Tap(jsonParams);
teaBarRightBtn2Tap(jsonParams);
teaBarRightBtn1Tap(jsonParams);
过程:原生点击 ↬ 原生调用JS的固定方法teaBarLeftBtn1Tap(jsonParams)
↬ JS进行点击逻辑处理,其可以是JS自己的逻辑也可以调用native方法。
回调交互
- JS给原生传入大量参数时使用prompt(),由原生拦截弹窗获取prompt本来要弹框的内容
- JS定义名为
methodsCall(callCode,exeStatus,result)
的方法给原生调用。
说明:JS封装回调统一方法methodsCall(callCode,exeStatus,result)
,exeStatus
目前只有success
,failure
,cancel
,此方法由Android调用JS统一处理回调code的方法,js自己封装一份调用的类来统一管理原生调用与回调处理,JS需自己封装超时处理,原生交互与后台请求逻辑相似,交互习惯模仿微信小程序,cancell
时并不是调用错误,而是这个操作被原生取消,那么js从funMap中主动将本次缓存的调用方法移除即可。
示例:
//js封装类中定义一个全局变量funcMap方法
const funcMap = new Map();
//js封装类中定义一个方法名为`methodsCall`
function methodsCall(callCode,exeStatus,result){
console.log("回调callCode", "$callCode;$exeStatus;result");
const func = funcMap.get(callCode)
if(func){
func(exeStatus,result);
} else {
console.error(`No function registered for code: ${code}`);
}
funcMap.delete(callCode);
}
//js封装类中定义一个方法用于被具体业务调用
selectFile(func){
const callCode = {即时生成callCode}
window.TeaJSBridge.selectFile(callCode);
funcMap.set(callCode,func);
}
//方法中设置超时从map中取消,且方法取消
//业务调用
selectFile(function(status,result){
//todo 具体业务逻辑
})
//Android端调用数据示例:js接收3个参数,第三个不同方法数据也会不同
javascript:methodsCall('1001','success','{"size":56952,"kbsize":55.6171875,"fileExt":"jpg","path":"%2Fstorage%2Femulated%2F0%2FAndroid%2Fdata%2Fcom.xyzl.android.mpass%2Fcache%2F1739504957884%2F2038.jpg"}')
前端打包
- 静态资源打离线包使用相对路径
缺失:真实前端打包、前端真实网络调用
JS可调用的原生方法。
带 * 号的是Android与ios都有该方法,不带 * 号的暂时只有Android拥有该方法
示例:
window.TeaJSBridge.selectFile("10003");
说明:window对象挂载的对象名称为:TeaJSBridge
;JS需要拿回调结果的话第一个参数传入callCode;在Android中若原生方法的入参无callCode则可以直接返回String不走回调
- initTitlebar(String params)
每个页面都必须调用的方法,传入的字符串为固定格式,具体见标题原生导航栏动态设置
下 json字符串 - *selectFile(String callCode) 选择文件(DucumentFile方式选择)
//方法返回数据
Android:
{"size":1812186,"kbsize":1769.712890625,"fileExt":"mp4","path":"%2Fstorage%2Femulated%2F0%2FAndroid%2Fdata%2Fcom.xyzl.android.mpass%2Fcache%2F1740562622403%2Fstart-55b51670.mp4"}
把返回值传给uploadFile即可完成上传(json与地址字符串都支持)
- *uploadFile(String callCode,String path) 上传文件
返回给js调用端的数据由服务端透传 - *takePhoto(String callCode) 拍照
//返回结果为URLEncoder之后的路径;ios暂时没有使用URLEncoder进行编码
%2Fstorage%2Femulated%2F0%2FAndroid%2Fdata%2Fcom.xyzl.android.mpass%2Ffiles%2FPictures%2Fimage_2828668122646479783.png
- *openAlbum(String callCode,int openType) 打开相册 openType=0:单选,openType>0多选,支持相册选择视频,ios暂不支持多选
//相册的结果:
同上 selectFile(String callCode) 返回值
* encrypt(String callCode,String content)无需回调直接返回加密字符串(仅Android可用)
* decrypt(String content)解密字符串,种子在原生端定义不可传入(仅Android可用)
- setStorage(String fileName, String key, String value) 本地存储
- getStorage(String fileName, String key) 获取本地存储
- *closeH5() H5主动关闭自己
- *prePage() 前一页,若不可退则结束当前WebView
- *goPage(String routeUrl, boolean singleWebView) 前往路由所在页,singleWebView为true时在原实例上跳转路由,singleWebView为false时新开一个原生Activity
- *goBackOrForward(int step) 前进或后退步数页,step为正负整数,若不可退则结束当前WebView
- *downloadBySysBrowser(String url) 传入一个下载链接调用系统浏览器进行下载,WebView本身只有下载触发接口不具备下载功能
*poiSearch(String callCode, String keyword, String city, double lat, double lng, int radius, int pageNum, int pageSize)此方法为定位点附近的兴趣点搜索,搜索结果会返回一个地址列表,入参radius
为半径,keyword
为兴趣点搜索关键字,只需要获取附近地址列表时建议为空,其将搜索默认数据(因高德地图基础商业授权费废弃)
[{"name":"北京建工","address":"江苏省南京市雨花台区雨花南路与雨花大道交叉口东80米","latitude":31.99254,"longitude":118.782037},{"name":"中兴通讯南京雨花一期研发中心东区","address":"江苏省南京市雨花台区紫荆花路68号","latitude":31.989445,"longitude":118.776352},{"name":"南京阅城农贸市场管理有限公司","address":"江苏省南京市雨花台区紫荆花路与花神大道交叉口东220米","latitude":31.988048,"longitude":118.77701},{"name":"长发.诸公营销中心","address":"江苏省南京市雨花台区雨花南路5号","latitude":31.993232,"longitude":118.778289},{"name":"中兴","address":"江苏省南京市雨花台区紫荆花路68号","latitude":31.989945,"longitude":118.77488}]
*geocodeSearch(String callCode, double latitude, double longitude, float radius) 逆地理编码,将坐标转为话文字地理信息,附带默认的poi列表信息,radius
为检索半径(因高德地图基础商业授权费废弃)
{"address":"江苏省南京市雨花台区雨花街道雨花台中学(紫荆花路校区)","poiList":[{"name":"雨花台中学(紫荆花路校区)","address":"江苏省南京市雨花台区紫荆花路","latitude":31.988721,"longitude":118.779096}]}
- *getLocation(String callCode,boolean cache) 获取定位信息
{
"title": "",
"address": "江苏省南京市浦口区团结路7号",
"latitude": 32.031182,
"longitude": 118.649183,
"adCode": "320111",
"cityCode": "025",
"province": "江苏省",
"city": "南京市",
"district": "浦口区",
"coordType": "GCJ02",
"locationTime": "2025-07-24 17:46:06",
"accuracy": 42,
"locationType": 5
}
IOS仅仅支持如下字段
let resultDic: [String: Any] = [
"title": reGeocode.poiName,
"address": reGeocode.formattedAddress,
"latitude": location.coordinate.latitude,
"longitude": location.coordinate.longitude,
"city":reGeocode.city,
"adCode":reGeocode.adcode,
"cityCode":reGeocode.citycode,
"province":reGeocode.province,
"district":reGeocode.district
]
- calculateLineDistance(String callCode,double[] pointA,double[] pointB) 计算两个坐标点的直线(飞行)距离,返回值单位:米
javascript:methodsCall('5716691753337898030','success','{"result":"3769.888"}')
{
"result":"3769.888"
}
Android端返回的多余字段可以辅助调试查看
showLoading(String txtContent,boolean cancellable) 展示一个加载框,可添加提示语、可控制是否点击可退出,txtContent为空时提示语不展示hideLoading() 隐藏加载框relogin(boolean cover) 重新登录,cover = true:用于当前页面没登录不能查看时展示登录页面,此时登录页面覆盖在当前页面,登录完成后当前页面会刷新,cover = false:用于结束掉所有页面全新重登录- *scanQr(String callCode) 扫描二维码,返回二维码信息字符串
- checkNfcStatus()检测NFC状态,返回值有 1:有NFC并且已经打开、-1:NFC尚未打开、0:没有NFC。
- 接收NFC结果;NFC需要前端自己根据之前的JS SDK进行function注册,原生只对NFC的触发结果负责,当触发后我们针对固定CallCode进行调用,原生端callcode = 20008;触发后返回给js通用接收方法的结果格式为
{
"content": "",//NFC扫码结果
"cardId": ""//NFC卡片id
}
-
showDateTimePicker(String formatMode,String callCode)调用一个原生时间选择弹框
image.png
formatMode的值如下:
public static final String FORMAT_MODE_1 = "YYYY-MM-DD HH:MM:SS";
public static final String FORMAT_MODE_2 = "YYYY-MM-DD";
public static final String FORMAT_MODE_3 = "YYYY-MM-DD HH:MM";
暂未实现方法:
recordVoice
openSignBoard
playMedia