Android手把手教你实现混合开发

前言

混合开发的方式有很多种比如: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页面

image-20210711155358313.png

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)
            }
        }
复制代码

最终效果

image-20210711161528194.png

结语

到此混合开发的需求就完成了,打完手工!!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享