使用JSBridge框架来实现Android与H5(JS)交互

1.首先我们来了解一下什么是JSBridge?

在开发中,为了追求开发的效率以及移植的便利性,一些展示性强的页面我们会偏向于使用h5来完成,功能性强的页面我们会偏向于使用native来完成,而一旦使用了h5,为了在h5中尽可能的得到native的体验,我们native层需要暴露一些方法给js调用,比如,弹Toast提醒,弹Dialog,分享等等,有时候甚至把h5的网络请求放到native去完成。

JSBridge做得好的一个典型就是微信,微信给开发者提供了JS SDK,该SDK中暴露了很多微信native层的方法,比如支付,定位等。

请注意:使用JSBridge框架来实现Android与H5交互和

普通的Android与H5交互(详见混合开发:Android和H5(Js)交互_android和h5混合开发_ErwinNakajima的博客-CSDN博客)是不一样的,Android端和H5端写法都不一样,所以如果Android端使用JSBridge框架写了Android与H5交互,要同步到H5端,让他也改成这种方式,不然Android与H5交互无法生效。

2.引入jsbridge依赖,在app的build.gradle中添加依赖

dependencies {
api "com.github.lzyzsd:jsbridge:1.0.4"
}

在项目的build.gradle中添加,然后同步项目。

repositories {
    maven { url "https://jitpack.io" }
}

3.xml中的代码,这里用的ViewDataBinding方式写的:




    

        

    

    

        

            

                

                

                    

                        

                    

                    

                

            

        

        

            

            

            

        

        

        

    

4.BaseActivity中的代码:

package com.phone.library_common.base

import android.app.ActivityManager
import android.content.Intent
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.os.Looper
import android.os.Process
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import com.gyf.immersionbar.ImmersionBar
import com.phone.library_common.BaseApplication
import com.phone.library_common.R
import com.phone.library_common.manager.ActivityPageManager
import com.phone.library_common.manager.CrashHandlerManager
import com.phone.library_common.manager.LogManager
import com.phone.library_common.manager.ResourcesManager
import com.phone.library_common.manager.ToolbarManager
import com.phone.library_common.manager.ToolbarManager.Companion.assistActivity
import com.qmuiteam.qmui.widget.QMUILoadingView
import com.trello.rxlifecycle3.components.support.RxAppCompatActivity

abstract class BaseBindingRxAppActivity : RxAppCompatActivity(), IBaseView {

    private val TAG = BaseBindingRxAppActivity::class.java.simpleName
    protected lateinit var loadView: QMUILoadingView
    protected lateinit var layoutParams: FrameLayout.LayoutParams

    //该类绑定的ViewDataBinding
    protected lateinit var mDatabind: DB
    protected lateinit var mRxAppCompatActivity: RxAppCompatActivity
    protected lateinit var mBaseApplication: BaseApplication
    private var mActivityPageManager: ActivityPageManager? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mRxAppCompatActivity = this
        mBaseApplication = application as BaseApplication
        mActivityPageManager = ActivityPageManager.get()
        mActivityPageManager?.addActivity(this)

        mDatabind = DataBindingUtil.setContentView(this, initLayoutId())
        mDatabind.lifecycleOwner = mRxAppCompatActivity
        initData()
        initViews()
        loadView = QMUILoadingView(this)
        loadView.also {
            it.visibility = View.GONE
            it.setSize(100)
            it.setColor(ResourcesManager.getColor(R.color.color_333333))
        }
        layoutParams = FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT
        )
        layoutParams.gravity = Gravity.CENTER
        addContentView(loadView, layoutParams)
        initLoadData()
    }

    override fun onConfigurationChanged(newConfig: Configuration) {
        //非默认值
        if (newConfig.fontScale != 1f) {
            resources
        }
        super.onConfigurationChanged(newConfig)
    }

    override fun getResources(): Resources? { //还原字体大小
        val res = super.getResources()
        //非默认值
        if (res.configuration.fontScale != 1f) {
            val newConfig = Configuration()
            newConfig.setToDefaults() //设置默认
            res.updateConfiguration(newConfig, res.displayMetrics)
        }
        return res
    }

    protected abstract fun initLayoutId(): Int

    protected open fun setToolbar(isDarkFont: Boolean) {
        if (isDarkFont) {
            ImmersionBar.with(this) //原理:如果当前设备支持状态栏字体变色,会设置状态栏字体为黑色,如果当前设备不支持状态栏字体变色,会使当前状态栏加上透明度,否则不执行透明度
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(R.color.color_FFFFFFFF) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .keyboardEnable(true)
                .init()
        } else {
            ImmersionBar.with(this)
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(R.color.color_FF198CFF) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .keyboardEnable(true)
                .init()
        }
        ToolbarManager.assistActivity(findViewById(android.R.id.content))
    }

    protected open fun setToolbar(isDarkFont: Boolean, isResizeChildOfContent: Boolean) {
        if (isDarkFont) {
            ImmersionBar.with(this) //原理:如果当前设备支持状态栏字体变色,会设置状态栏字体为黑色,如果当前设备不支持状态栏字体变色,会使当前状态栏加上透明度,否则不执行透明度
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(R.color.color_FFFFFFFF) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .keyboardEnable(true)
                .init()
        } else {
            ImmersionBar.with(this)
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(R.color.color_FF198CFF) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .keyboardEnable(true)
                .init()
        }
        if (isResizeChildOfContent) {
            ToolbarManager.assistActivity(findViewById(android.R.id.content))
        }
    }

    protected open fun setToolbar(isDarkFont: Boolean, color: Int) {
        if (isDarkFont) {
            ImmersionBar.with(this) //原理:如果当前设备支持状态栏字体变色,会设置状态栏字体为黑色,如果当前设备不支持状态栏字体变色,会使当前状态栏加上透明度,否则不执行透明度
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(color) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .init()
        } else {
            ImmersionBar.with(this)
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(color) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .init()
        }
        ToolbarManager.assistActivity(findViewById(android.R.id.content))
    }

    protected open fun setToolbar(
        isDarkFont: Boolean,
        color: Int,
        isResizeChildOfContent: Boolean
    ) {
        if (isDarkFont) {
            ImmersionBar.with(this) //原理:如果当前设备支持状态栏字体变色,会设置状态栏字体为黑色,如果当前设备不支持状态栏字体变色,会使当前状态栏加上透明度,否则不执行透明度
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(color) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .init()
        } else {
            ImmersionBar.with(this)
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(color) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .init()
        }
        if (isResizeChildOfContent) {
            ToolbarManager.assistActivity(findViewById(android.R.id.content))
        }
    }

    protected open fun setToolbar2(isDarkFont: Boolean, statusBarColor: Int) {
        if (isDarkFont) {
            ImmersionBar.with(this) //原理:如果当前设备支持状态栏字体变色,会设置状态栏字体为黑色,如果当前设备不支持状态栏字体变色,会使当前状态栏加上透明度,否则不执行透明度
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(statusBarColor) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .keyboardEnable(true)
                .init()
        } else {
            ImmersionBar.with(this)
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(statusBarColor) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .keyboardEnable(true)
                .init()
        }
    }

    /**
     * 顶部栏吸顶专用
     */
    protected open fun setToolbar2(
        isDarkFont: Boolean,
        statusBarColor: Int,
        isResizeChildOfContent: Boolean
    ) {
        if (isDarkFont) {
            val window = window
            window.clearFlags(
                WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
                        or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
            )
            window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
            window.statusBarColor = Color.TRANSPARENT
            //        window.setNavigationBarColor(Color.TRANSPARENT);
            ImmersionBar.with(this) //原理:如果当前设备支持状态栏字体变色,会设置状态栏字体为黑色,如果当前设备不支持状态栏字体变色,会使当前状态栏加上透明度,否则不执行透明度
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(statusBarColor) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .keyboardEnable(true)
                .init()
        } else {
            ImmersionBar.with(this) //原理:如果当前设备支持状态栏字体变色,会设置状态栏字体为黑色,如果当前设备不支持状态栏字体变色,会使当前状态栏加上透明度,否则不执行透明度
                .statusBarDarkFont(isDarkFont)
                .statusBarColor(statusBarColor) //状态栏颜色,不写默认透明色
                //                    .autoStatusBarDarkModeEnable(true, 0.2f) //自动状态栏字体变色,必须指定状态栏颜色才可以自动变色哦
                .keyboardEnable(true)
                .init()
            val window = window
            window.clearFlags(
                WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
                        or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
            )
            window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
            window.statusBarColor = Color.TRANSPARENT
            //        window.setNavigationBarColor(Color.TRANSPARENT);
        }
        if (isResizeChildOfContent) {
            assistActivity(findViewById(android.R.id.content))
        }
    }

    protected abstract fun initData()

    protected abstract fun initViews()

    protected abstract fun initLoadData()

    protected open fun showToast(message: String?, isLongToast: Boolean) {
        //        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
        if (!mRxAppCompatActivity.isFinishing) {
            val toast: Toast
            val duration: Int
            duration = if (isLongToast) {
                Toast.LENGTH_LONG
            } else {
                Toast.LENGTH_SHORT
            }
            toast = Toast.makeText(mRxAppCompatActivity, message, duration)
            toast.setGravity(Gravity.CENTER, 0, 0)
            toast.show()
        }
    }

    protected open fun showCustomToast(
        left: Int, right: Int,
        textSize: Int, textColor: Int,
        bgColor: Int, height: Int,
        roundRadius: Int, message: String?,
        isLongToast: Boolean
    ) {
        val frameLayout = FrameLayout(this)
        val layoutParams = FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.WRAP_CONTENT,
            FrameLayout.LayoutParams.WRAP_CONTENT
        )
        frameLayout.layoutParams = layoutParams
        val textView = TextView(this)
        val layoutParams1 = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, height)
        textView.layoutParams = layoutParams1
        textView.setPadding(left, 0, right, 0)
        textView.textSize = textSize.toFloat()
        textView.setTextColor(textColor)
        textView.gravity = Gravity.CENTER
        textView.includeFontPadding = false
        val gradientDrawable = GradientDrawable() //创建drawable
        gradientDrawable.setColor(bgColor)
        gradientDrawable.cornerRadius = roundRadius.toFloat()
        textView.background = gradientDrawable
        textView.text = message
        frameLayout.addView(textView)
        val toast = Toast(this)
        toast.setView(frameLayout)
        if (isLongToast) {
            toast.duration = Toast.LENGTH_LONG
        } else {
            toast.duration = Toast.LENGTH_SHORT
        }
        toast.show()
    }

    open fun isOnMainThread(): Boolean {
        return Looper.getMainLooper().thread.id == Thread.currentThread().id
    }

    protected open fun startActivity(cls: Class<*>?) {
        val intent = Intent(this, cls)
        startActivity(intent)
    }

    protected open fun startActivityForResult(cls: Class<*>?, requestCode: Int) {
        val intent = Intent(this, cls)
        startActivityForResult(intent, requestCode)
    }

    open fun getActivityPageManager(): ActivityPageManager? {
        return mActivityPageManager
    }

    private fun killAppProcess() {
        LogManager.i(TAG, "killAppProcess")
        val manager =
            mBaseApplication.getSystemService(ACTIVITY_SERVICE) as ActivityManager
        val processInfos = manager.runningAppProcesses
        // 先杀掉相关进程,最后再杀掉主进程
        for (runningAppProcessInfo in processInfos) {
            if (runningAppProcessInfo.pid != Process.myPid()) {
                Process.killProcess(runningAppProcessInfo.pid)
            }
        }
        LogManager.i(TAG, "执行killAppProcess,應用開始自殺")
        val crashHandlerManager = CrashHandlerManager.get()
        crashHandlerManager?.saveTrimMemoryInfoToFile("执行killAppProcess,應用開始自殺")
        try {
            Thread.sleep(1000)
        } catch (e: InterruptedException) {
            LogManager.i(TAG, "error")
        }
        Process.killProcess(Process.myPid())
        // 正常退出程序,也就是结束当前正在运行的 java 虚拟机
        System.exit(0)
    }

    override fun onDestroy() {
        if (mActivityPageManager?.mIsLastAliveActivity?.get() == true) {
            killAppProcess()
        }
        mActivityPageManager?.removeActivity(mRxAppCompatActivity)
        super.onDestroy()
    }

}

5.Activity中的代码:

package com.phone.module_square.ui

import android.net.Uri
import android.os.Build
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import com.alibaba.android.arouter.facade.annotation.Route
import com.github.lzyzsd.jsbridge.CallBackFunction
import com.github.lzyzsd.jsbridge.DefaultHandler
import com.google.gson.Gson
import com.phone.library_common.base.BaseBindingRxAppActivity
import com.phone.library_common.bean.UserBean
import com.phone.library_common.manager.LogManager
import com.phone.library_common.manager.ResourcesManager
import com.phone.module_square.R
import com.phone.module_square.databinding.SquareActivityJsbridgeBinding


@Route(path = "/module_square/jsbridge")
class JsbridgeActivity : BaseBindingRxAppActivity() {

    companion object {
        private val TAG = JsbridgeActivity::class.java.simpleName
    }

    override fun initLayoutId(): Int {
        return R.layout.square_activity_jsbridge
    }

    override fun initData() {

    }

    override fun initViews() {
        setToolbar(true)
        mDatabind.imvBack.setColorFilter(ResourcesManager.getColor(R.color.color_80000000))
        mDatabind.tevFunctionToJs.setOnClickListener {
            val userBean = UserBean()
            userBean.id = 1
            userBean.userId = "100"
            userBean.birthday = "1998.05.10"
            userBean.salary = 7000.0
            mDatabind.webView.callHandler(
                "functionToJs",
                Gson().toJson(userBean),
                CallBackFunction {
                    LogManager.i(TAG, "reponse data from JS $it")
                })
        }
        mDatabind.tevFunctionToJs2.setOnClickListener {
            mDatabind.webView.callHandler(
                "functionToJs2",
                "data from Android",
                CallBackFunction { data ->
                    LogManager.i(TAG, "reponse data from JS $data")
                })
        }
        mDatabind.webView.apply {
            setDefaultHandler(DefaultHandler())
            getSettings().setAllowFileAccess(true)
            getSettings().setAppCacheEnabled(true)
            getSettings().setDatabaseEnabled(true)
            // 允许网页定位
            // 允许网页定位
            getSettings().setGeolocationEnabled(true)
            // 允许网页弹对话框
            // 允许网页弹对话框
            getSettings().setJavaScriptCanOpenWindowsAutomatically(true)
            // 加快网页加载完成的速度,等页面完成再加载图片
            // 加快网页加载完成的速度,等页面完成再加载图片
            getSettings().setLoadsImagesAutomatically(true)
            // 开启 localStorage
            // 开启 localStorage
            getSettings().setDomStorageEnabled(true)
            // 设置支持javascript// 本地 DOM 存储(解决加载某些网页出现白板现象)
            // 设置支持javascript// 本地 DOM 存储(解决加载某些网页出现白板现象)
            getSettings().setJavaScriptEnabled(true)
            // 进行缩放
            // 进行缩放
            getSettings().setBuiltInZoomControls(true)
            // 设置UserAgent
            // 设置UserAgent
            getSettings().setUserAgentString(getSettings().getUserAgentString() + "app")

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                // 解决 Android 5.0 上 WebView 默认不允许加载 Http 与 Https 混合内容
                getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW)
            }
            webChromeClient = object : WebChromeClient() {
                fun openFileChooser(
                    uploadMsg: ValueCallback, AcceptType: String?, capture: String?
                ) {
                    this.openFileChooser(uploadMsg)
                }

                fun openFileChooser(uploadMsg: ValueCallback, AcceptType: String?) {
                    this.openFileChooser(uploadMsg)
                }

                fun openFileChooser(uploadMsg: ValueCallback) {
//                    mUploadMessage = uploadMsg
//                    pictureSelector()
                }

                override fun onShowFileChooser(
                    webView: WebView,
                    filePathCallback: ValueCallback>,
                    fileChooserParams: FileChooserParams
                ): Boolean {
//                    mUploadMessageArray = filePathCallback
//                    pictureSelector()
                    return true
                }
            }
            loadUrl("file:///android_asset/jsbridge_js_java_interaction.html")


            registerHandler(
                "functionToAndroid"
            ) { data, function ->
                LogManager.i(
                    TAG,
                    "functionToAndroid handler = callNativeHandler, data from web = $data"
                )
                function.onCallBack("reponse data from Android 中文 from Java")
            }


            registerHandler(
                "functionToAndroid2"
            ) { data, function ->
                LogManager.i(
                    TAG,
                    "functionToAndroid2 handler = callNativeHandler, data from web = $data"
                )
                function.onCallBack("reponse data from Android 中文 from Java")
            }
        }
    }

    override fun initLoadData() {

    }

    override fun showLoading() {

    }

    override fun hideLoading() {

    }

}


6.JS的代码:



    
    
        js调用java
    



7.Android调用JS方法:

mDatabind.webView.callHandler(
                "functionToJs2",
                "data from Android",
                CallBackFunction { data ->
                    LogManager.i(TAG, "reponse data from JS $data")
                })

JS方法接收Android数据

bridge.registerHandler("functionToJs2", function(data2, responseCallback) {
                document.getElementById("show").innerHTML = ("data2 from Java: = " + data2);
                if (responseCallback) {
                    var responseData = "Javascript2 Says Right back aka!";
                    responseCallback(responseData);
                }
            });

8.JS调用Android方法:

function testClick2() {
            var str1 = document.getElementById("text1").value;
            var str2 = document.getElementById("text2").value;

            //call native method
            window.WebViewJavascriptBridge.callHandler(
                'functionToAndroid2'
                , {'param': '中文测试2'}

                , function(responseData) {
                    document.getElementById("show").innerHTML = "functionToAndroid2 send responseData from java, data = " + responseData
                }
            );
        }

Android方法接收JS数据

registerHandler(
                "functionToAndroid2"
            ) { data, function ->
                LogManager.i(
                    TAG,
                    "functionToAndroid2 handler = callNativeHandler, data from web = $data"
                )
                function.onCallBack("reponse data from Android 中文 from Java")
            }

如对此有疑问,请联系qq1164688204。

推荐Android开源项目

项目功能介绍:原本是RxJava2 和Retrofit2 项目,现已更新使用Kotlin+RxJava2+Retrofit2+MVP架构+组件化和
Kotlin+Retrofit2+协程+MVVM架构+组件化, 添加自动管理token 功能,添加RxJava2 生命周期管理,集成极光推送、阿里云Oss对象存储和高德地图定位功能。

项目地址:https://gitee.com/urasaki/RxJava2AndRetrofit2

你可能感兴趣的:(android,html5,javascript)