前言
混合开发的方式有很多种比如:uniapp、react native、flutter、hybridapp等。我们这里介绍的是大部分功能还用原生,小部分功能用web这样有些功能不必android、ios开发两套而且实时性比较好不需要升级app就可实现更新,缺点是页面加载速度慢依赖网络。所谓的混合开发是指一部分功能交由web前端来做,但是web页面又不能调用手机的相机、拍照、定位等功能。最终需要Android工程师将这些web需要和原生交互的部分封装成JS提供给前端使用。
复制代码
前期准备
这里需要借助github一个叫做JsBridge的第三方库,虽然作者很长时间没有更新了不过还能用。如果不想用的话只能自己写这里我们不作讨论。这里去找这个项目的时候有坑需要注意,最新的版本不知道谁动了代码导致这个BridgeHelper类缺失,看了issue只能去下载[19年7月10日的这个版本](https://github.com/lzyzsd/JsBridge/commits/master)。
复制代码
需求
这里的目标是web需要完成调用原生的定位功能及照相功能并返回,而原生需要调用web的一个方法,这样完成两端的通信功能。
复制代码
正文
这里我们先要先准备一个js供web端使用,这个js就相当于web和android之间的一个桥梁,之间的通信都要通过这个js来建立联系。
const u = navigator.userAgent;
window.isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android终端
window.isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
/*所有调用前提必须先调用此方法注册*/
function registerBridge(callback) {
if (window.WebViewJavascriptBridge) {
return callback(WebViewJavascriptBridge);
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady',
function () {
callback(WebViewJavascriptBridge)
},
false
);
}
if (window.isIOS) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback]; // 创建一个 WVJBCallbacks 全局属性数组,并将 callback 插入到数组中。
}
const WVJBIframe = document.createElement('iframe'); // 创建一个 iframe 元素
WVJBIframe.style.display = 'none'; // 不显示
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; // 设置 iframe 的 src 属性
document.documentElement.appendChild(WVJBIframe); // 把 iframe 添加到当前文导航上。
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
window.RuibetterH5 = {
/*js调用原生,原生返回数据给js*/
getLocation:function (callback) {
registerBridge(function (bridge) {
bridge.callHandler('getLocation',{},function (data) {
callback(data)
})
})
},
/*调用原生相册*/
getSelectorImages:function(callback) {
registerBridge(function (bridge) {
bridge.callHandler("getSelectorImages",{},function (data) {
callback(data)
})
})
},
}
复制代码
编写一个web页面
1.调用原生定位
web页面代码
<div class="weui-flex__item child_two_flex_last location">点我获取原生经纬度坐标</div>
/*原生调用js,js返回数据给原生*/
$('.location').on("click", function () {
RuibetterH5.getLocation(function (response) {
$('.location').text(response)
})
})
复制代码
Android代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<include
android:id="@+id/webToolbar"
layout="@layout/toolbar"/>
<com.github.lzyzsd.jsbridge.BridgeWebView
android:id="@+id/bridgeWebView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/webToolbar"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
// 加载链接
bridge.loadUrl("http://192.168.1.7:3011/html/water_watch.html")
bridge.registerHandler("getLocation"
) { data, function ->
rxPermissions.request(Manifest.permission.ACCESS_FINE_LOCATION)
.subscribe {
if (it) {
val location = LocationUtils.getInstance().getLocations(this)
function.onCallBack(location)
} else {
val alertDialog = AlertDialog.Builder(this)
alertDialog.setTitle(AppUtils.getAppName())
alertDialog.setMessage("定位权限为必须权限,否则无法更好的为您提供定位服务,点击确定前往设置中心打开.")
alertDialog.setNegativeButton("取消"
) { dialog, which ->
dialog.dismiss()
}
alertDialog.setPositiveButton("确定") { dialog, which ->
dialog.dismiss()
PermissionSettingUtil.gotoPermission(WaterApp.context)
}
alertDialog.show()
}
}
}
复制代码
通过rxpermission请求权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"
android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus"
android:required="false" />
复制代码
添加网络请求
2.调用相册
web页面代码
<input id="uploaderInput" class="weui-uploader__input" type="file" accept="image/*" onchange="imagePreview(this)"/>
/*原生调用js,js返回数据给原生*/
function imagePreview(input){
let files = input.files;
for (let i = 0; i < files.length; i++) {
let file = files[i];
let imageType = /^image\//;
if ( !imageType.test(file.type) ) {
// alert("不是图片格式无法显示");
continue;
}
let img = document.createElement("img");
$uploaderFiles.append(img)
img.setAttribute("class","water_weui-uploader__file")
let reader = new FileReader();
reader.onload = (function(aImg) {
return function(e) {
aImg.src = e.target.result;
};
})(img);
reader.readAsDataURL(file);
}
}
复制代码
Android代码
private lateinit var bridge:BridgeWebView
bridge.webChromeClient = object :WebChromeClient(){
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
mUploadMessageArray = filePathCallback
rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE)
.subscribe {
if (it) {
PictureSelector.create(this@WaterWebViewActivity)
.openGallery(PictureMimeType.ofImage())
.imageEngine(GlideEngine.createGlideEngine())
.forResult(PictureConfig.CHOOSE_REQUEST)
} else {
val alertDialog = AlertDialog.Builder(this@WaterWebViewActivity)
alertDialog.setTitle(AppUtils.getAppName())
alertDialog.setMessage("存储权限为必须权限,否则无法更好的为您选择图片服务,点击确定前往设置中心打开.")
alertDialog.setNegativeButton("取消"
) { dialog, which ->
dialog.dismiss()
}
alertDialog.setPositiveButton("确定") { dialog, which ->
dialog.dismiss()
PermissionSettingUtil.gotoPermission(WaterApp.context)
}
alertDialog.show()
}
}
return true
}
override fun onReceivedTitle(view: WebView?, title: String?) {
super.onReceivedTitle(view, title)
title?.let {
waterBinding.webToolbar.titleTv.text = "点我原生通知Js"
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == RESULT_OK ){
if(requestCode == PictureConfig.CHOOSE_REQUEST) {
data?.let {
val localMedias = PictureSelector.obtainMultipleResult(data)
if(localMedias.isNotEmpty()) {
val uriMedias = mutableListOf<Uri>()
localMedias.forEach { localMediaItem ->
uriMedias.add(UriUtils.file2Uri(File(if(TextUtils.isEmpty(localMediaItem.androidQToPath)) localMediaItem.realPath else localMediaItem.androidQToPath)))
}
mUploadMessageArray?.onReceiveValue(uriMedias.toTypedArray())
}
}
}
}else {
// 这句话要判断不然如果调用相册不选择照片点击返回,下次点击选择相册不会触发
mUploadMessageArray?.onReceiveValue(null)
}
}
复制代码
这里用到了一个三方的选择图片框架PictureSelector
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"
android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus"
android:required="false" />
复制代码
3.原生调用web
web页面
<input id="js_input" type="text" class="weui-input" placeholder="请填写本期止码"/>
registerBridge(function (bridge) {
bridge.registerHandler("functionInJs",function (data,responseCallback) {
$("#js_input").val(data)
const responseData = 'java调用js我已收到,这是js返回给你的数据';
responseCallback(responseData); //回传数据给java
})
})
复制代码
Android代码
waterBinding.webToolbar.titleTv.setOnClickListener {
ToastUtils.showShort("我点了")
bridge.callHandler("functionInJs","原生返回的123"){data ->
ToastUtils.showShort(data)
}
}
复制代码
最终效果
结语
到此混合开发的需求就完成了,打完手工!!!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END