Android 混合H5 开发

1,578 阅读2分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

1. 取消app顶部样式

AndroidManifest.xml文件中修改android:theme

android:theme="@style/Theme.AppCompat.Light.NoActionBar"

2. 配置WebView

2.1. 在布局文件activity_main.xml文件中添加WebView

<WebView
    android:id="@+id/web_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

2.2. 配置WebView属性

 webView = findViewById<WebView>(R.id.web_view).apply {
            loadUrl("file:///android_asset/dist/index.html") //这行从assets里面读取H5页面
//            loadUrl("file:///${filesDir.path}/web/dist/index.html")   //这行从本地文件读取H5页面。当前配置路径:/data/user/0/com.holland.myapp[包名]/files

            //暴露调用方法。当前调用方式:window.$App.functionName[方法名](arg...)
            addJavascriptInterface(JsInterface(this@MainActivity), "$App")
            settings.apply {
                //设置支持JS
                javaScriptEnabled = true
                //设置支持缩放
                setSupportZoom(false)
                //设置支持DomStorage
                domStorageEnabled = true
            }
            //设置为app打开,而非网页打开
            this.webViewClient = object : WebViewClient() {
                override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
                    Log.d("shouldOverrideUrlLoading", "url: $url")
                    if (Uri.parse(url).host != HttpUtil.myServerHost) {
                        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
                        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                        context.startActivity(intent)
                        return true
                    }
                    return false
                }
            }
            //打印h5 err日志
            this.webChromeClient = object : WebChromeClient() {
                override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
                    when (consoleMessage.messageLevel()) {
                        MessageLevel.ERROR -> Log.e("H5", consoleMessage.message()) 
                    }
                    return super.onConsoleMessage(consoleMessage)
                }
            }
        }

2.2.1 配置调用方法JavascriptInterface

package com.holland.myapp.js_interface

import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.webkit.JavascriptInterface
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.holland.myapp.CameraActivity
import com.holland.myapp.common.ActivityResultCode
import com.holland.myapp.util.CameraUtil


class JsInterface(private val activity: Activity) {
    //使用安卓提示消息
    @JavascriptInterface
    fun onToast(msg: String?) = Toast.makeText(
        activity,
        if (msg == null) "消息为空" else if (msg.isBlank()) "消息为空" else msg,
        Toast.LENGTH_SHORT
    ).show()

    //使用CameraX拍照
    @JavascriptInterface
    fun onCameraX() = activity.startActivityForResult(
        Intent(activity, CameraActivity::class.java),
        ActivityResultCode.REQUEST_TAKE_PHOTO_CAMERA_X.ordinal
    )

    //使用Camera拍照
    @JavascriptInterface
    fun onCamera() =
        // Request camera permissions
        if (CameraActivity.REQUIRED_PERMISSIONS.all {
                ContextCompat.checkSelfPermission(
                    activity, it
                ) == PackageManager.PERMISSION_GRANTED
            }) {
            CameraUtil.openCamera(activity)
        } else {
            ActivityCompat.requestPermissions(
                activity,
                CameraActivity.REQUIRED_PERMISSIONS,
                CameraActivity.REQUEST_CODE_PERMISSIONS
            )
        }
}

2.3. 配置当前Activity属性

//设置返回事件
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    val jsCall = JsCall(webView) //自定义回调方法
    when (requestCode) {
        ActivityResultCode.REQUEST_TAKE_PHOTO_CAMERA.ordinal -> {
            jsCall.appCallJs(1, CameraUtil.currentPhotoPath)
        }
        else -> {
        }
    }
}

//设置返回键赋值给H5页面
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        if (webView.canGoBack()) {
            webView.goBack()
            return true
        } else {
            exitProcess(0)
        }
    }
    return super.onKeyDown(keyCode, event)
}

2.3.1 配置自定义回调方法JsCall

package com.holland.myapp.js_interface

import android.annotation.SuppressLint
import android.webkit.WebView

class JsCall(private val webView: WebView) {

    /**
     * @param type 1: 方法回调类型
     * @param args 2: 参数列表
     */
    @SuppressLint("ObsoleteSdkInt")
    fun appCallJs(type: Int, vararg args: String) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
            webView.evaluateJavascript("appCallJs(${composeParameter(type, *args)});", null)
        } else {
            webView.loadUrl("javascript:appCallJs(${composeParameter(type, *args)});")
        }
    }

    private fun composeParameter(type: Int, vararg args: String): String {
        return if (args.isEmpty()) {
            "'$type'"
        } else {
            return "'$type',${args.map { "'$it'" }.reduce { acc, s -> "$acc,$s" }}"
        }
    }
}

3. 配置H5(vue实现)

3.1. 页面

<template>
  <div>
    <van-field v-model="toast" center clearable label="安卓提示" placeholder="请输入提示内容">
      <template #button>
        <van-button size="small" type="primary" @click="onToast(toast)">发送</van-button>
      </template>
    </van-field>
    <van-button type="info" @click="onCamera()">Camera拍照接口</van-button>
    <van-button type="info" @click="onCameraX()">CameraX拍照接口</van-button>
    <van-button type="info" @click="test()">测试按钮</van-button>
  </div>
</template>

<script>
//引入与Android通信的方法
import AppInterface from "../uitl/AppInterface";

export default {
  name: 'HomePage',
  mixins: [AppInterface],
  data() {
    return {
      toast: null,
    }
  }
}
</script>

3.2 配置与Android通信的方法

export default {
  methods: {
    onToast(message) {
      if (window.$App) $App.onToast(message)
    },
    onCamera() {
      if (window.$App) $App.onCamera()
    },
    onCameraX() {
      if (window.$App) $App.onCameraX()
    }
  }
}

window.appCallJs = function (type, args0, args1, args2, args3, args4, args5, args6, args7, args8, args9) {
  switch (type) {
    case '1':
      /**
       * 拍照完成,摄像机回传本地图片路径
       * @param arg0 本地图片路径
       */
      break;
    default:
      if (window.$App) window.$App.onToast(`收到参数: ${type}, ${args0}, ${args1}, ${args2}, ${args3}, ${args4}, ${args5}, ${args6}, ${args7}, ${args8}, ${args9}`)
  }
}

4. 实现效果

4.1. 总体页面

image.png

4.1. 使用安卓提示

image.png

4.2. 使用CameraX

image.png

4.3. H5报错提示

image.png