H5获取手机相机或相册图片两种方式-Android通过webview传递多张照片给H5

需求目的: 手机机通过webView展示H5网页,在特殊场景下,需要使用相机拍照或者从相册获取照片,上传后台。

完整流程效果: 如下图
H5调用相机相册使用组件库方式流程

一、H5界面样例代码

使用html文件格式,文件直接打开就可以展示布局;一会在andriod webview中直接加载

<!DOCTYPE html>
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><div id="app"><h1>alllalalallalal 默认会被覆盖</h1>
</div>
<template id="why"><div><h2>{{message}}</h2><h2>{{counter}}</h2><button @click="increment">+1</button><button @click="decrement">-1</button><h1 style="text-align: center;">{{ title }}</h1><div><h2 style="text-align: center;">android选中照片H5展示</h2><!--HTML5提供了<input type="file">元素来实现选取文件的功能,在WebView表现为调用onShowFileChooser--><input accept="image/*" capture="camera" ref="imgFile" type="file" multiple@change="previewFiles"><div id="preview"><img v-for="imgSrc in imageSources" :src="imgSrc" :key="imgSrc"style="max-width: 100px; max-height: 100px; margin: 10px;"></div></div></div>
</template><body>
<!-- 引入 Vue 3 的 CDN 资源网络加载不了 -->
<!-- <script src="https://unpkg.com/vue@next"></script> -->
<!-- 引入 Vue 3 的 CDN 资源,本地引用 -->
<script src="vue3.2.12global.js"></script>
<script>Vue.createApp({template: '#why',data: function () {return {message: "功能开发中,敬请期待!",counter: 100,pictureSelectorContent: "相机选择结果:",imageSources: [] // 存储图片的数据URL}},// 在你的 Vue 组件中处理 Webview 传递的数据mounted() {// 设置全局函数,用于接收 WebView 调用// window.pictureSelectorResult = this.pictureSelectorResult;},methods: {increment() {this.counter++;console.log("点击了+1");},decrement() {this.counter--;consloe.log("点击了-1");},startPictureSelector() {window.android.startPictureSelector();},previewFiles() {const files = this.$refs.imgFile.files;this.imageSources = [];for (let i = 0; i < files.length; i++) {const file = files[i];const reader = new FileReader();reader.onload = (e) => {this.imageSources.push(e.target.result);};reader.readAsDataURL(file);}},},}).mount("#app")</script>
<style>h1 {font-size: 80px;font-weight: bold;margin-bottom: 20px;}h2 {font-size: 20px;font-weight: bold;color: #C8EFD4;}h3 {font-size: 10px;font-weight: bold;color: #C8EFD4;}button1 {font-size: 60px;padding: 10px 20px;background-color: #007bff;color: #fff;border: none;border-radius: 4px;cursor: pointer;margin-bottom: 20px;margin-top: 20px;text-align: center;/* 将文字水平居中显示 */display: flex;/* 将按钮设置为flex容器 */align-items: center;/* 将文字在垂直方向上居中显示 */}</style>
</body></html>

上述代码是前端代码,使用vue3框架展示一个基础 加减demo界面(不重要的冗余),以及 一个打开文件的按钮以及展示图片
在这里插入图片描述

其中HTML5提供了元素来实现选取文件的功能,当在WebView表现为调用onShowFileChooser后,回调图片uri列表一一获取并展示

二、Android打开相机以及相册的两种方式

方式一:android 原生方式

实际效果和流程示图
在这里插入图片描述

1.android界面逻辑代码
这边使用的是kotlin语言,使用的fragment界面展示,使用binding加载了布局,以及声明了webview组件,在webview上导入html链接,使本地H5界面展示


class VisitorFragment : Fragment() {private lateinit var binding: FragmentVisitorBindinglateinit var mActivity: Activityprivate lateinit var mRoot: Viewcompanion object {const val TAG = "VisitorFragment"}private var mWebViewUrl: String = "file:///android_asset/vue_android_demo.html"var mAppName = MainApplication.instance.configuration.BASE_APP_LOGIN_IDENTITYvar mSystemName = WebViewConstant.DEFAULT_SYSTEM_NAMEprivate var mVisitorAndroidJs: VisitorAndroidJs = VisitorAndroidJs(this)private var mWebView: WebView? = nullvar mAndroidId: String = WebViewConstant.DEFAULT_DEVICE_SIGNvar mApiKey: String = WebViewConstant.DEFAULT_API_KEY//回传H5时使用的对象private var mUploadCallback: ValueCallback<Array<Uri>>? = null//拍照传递的路径uriprivate var mImageUri: Uri? = null/*** onCreate方法是Activity生命周期的第一个回调方法* ,当Activity被创建时被调用。* 在这个方法中,你可以进行一些初始化的操作,比如设置布局、绑定控件、初始化数据等。** @param savedInstanceState If the fragment is being re-created from* a previous saved state, this is the state.*/override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = FragmentVisitorBinding.inflate(layoutInflater)mRoot = binding.root//记录val viewModel = ViewModelProvider(requireActivity())[DashboardViewModel::class.java]viewModel.setFragment(this)LogUtils.d(TAG, "onCreate")}/*** onCreateView方法是Fragment生命周期的回调方法,* 当Fragment创建并绘制其用户界面时被调用。* 在这个方法中,你可以通过返回一个View对象来定义Fragment的用户界面。* 它常用于加载布局文件、查找和初始化控件等操作。** @return*/override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View {LogUtils.d(TAG, "onCreateView")initView(mRoot, layoutInflater, null)return mRoot}fun initView(parent: View?, inflater: LayoutInflater?, container: ViewGroup?) {//设置当前fragmentmAndroidId = DeviceUtils.getUniqueId(mActivity)initWebView()mWebView?.loadUrl(mWebViewUrl)//弹出展示链接showToast("${getString(R.string.current_develop_environment)}$mWebViewUrl")initData()}override fun onAttach(context: Context) {super.onAttach(context)mActivity = context as Activity}/*** @param msg 内容*/fun showToast(msg: String?) {val activity: Activity? = activityactivity?.runOnUiThread {Toast.makeText(activity,msg,Toast.LENGTH_SHORT).show()}}private fun initData() {}override fun onResume() {super.onResume()LogUtils.d(TAG, "onResume")}@SuppressLint("SetJavaScriptEnabled")private fun initWebView() {LogUtils.d(TAG, "initWebView")mWebView = binding.mainWebViewmWebView?.requestFocus()mWebView?.isHorizontalScrollBarEnabled = falsemWebView?.isVerticalScrollBarEnabled = falseval setting = mWebView?.settingssetting?.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW;setting?.javaScriptEnabled = true//用于开启或禁用其 DOM(文档对象模型)存储功能,浏览器缓存这些数据setting?.domStorageEnabled = true//允许访问文件,默认允许setting?.allowFileAccess = true//设置不,会引起webView重新不急,默认NARROW_COLUMNSsetting?.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS//自动缩放setting?.setSupportZoom(true)setting?.builtInZoomControls = true//自适应屏幕setting?.useWideViewPort = truesetting?.loadWithOverviewMode = true//支持多窗口setting?.setSupportMultipleWindows(true)setting?.setAppCacheEnabled(true)setting?.domStorageEnabled = true//定位setting?.setGeolocationEnabled(true)//优先使用缓存数据,在缓存数据不存在的情况下才去获取网络数据setting?.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORKsetting?.savePassword = false//设置js接口mVisitorAndroidJs.let { mWebView?.addJavascriptInterface(it, "android") }//页面不跳转浏览器mWebView?.webViewClient = object : WebViewClient() {override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {LogUtils.d(TAG, "url: $url")view.loadUrl(url)return true}override fun shouldInterceptRequest(view: WebView,request: WebResourceRequest): WebResourceResponse? {var uri = request.urlvar path = uri.pathLogUtils.d(TAG, "uri: $uri, path: $path")return super.shouldInterceptRequest(view, request)}}//webView官方打开文件选取方法onShowFileChooser,把网页回传文件mWebView?.webChromeClient = object : WebChromeClient() {//API >=21(android 5.0.1)回调此方法override fun onShowFileChooser(webView: WebView?,filePathCallback: ValueCallback<Array<Uri>>?,fileChooserParams: FileChooserParams?): Boolean {mUploadCallback = filePathCallback//使用拍照或者打开文件mImageUri = ChoosePhotoFile.takePhoto(mActivity)//设置false会IllegalStateException: Duplicate showFileChooser resultreturn true}}}fun onActivityResultResponse(requestCode: Int, resultCode: Int, intent: Intent?) {LogUtils.d(TAG,"onActivityResultResponse requestCode $requestCode, resultCode:$resultCode")// 扫描二维码/条码回传if (requestCode == ScanCodeUtils.REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) {LogUtils.d(TAG, "onActivityResultResponse ${intent?.extras}")if (intent == null) {//弹出展示链接showToast("扫描结果为空")return}//传递给js,格式是"scanCodeResult('${data.extras}')",其中单引号很重要,可能导致js script error//codedContent是组件中定义的参数名setEvaluateJavascript("scanCodeResult('${intent.getStringExtra("codedContent")}')")} else if (requestCode == ChoosePhotoFile.REQUEST_CODE) {//拍照,界面跳回后,结果文件的使用ChoosePhotoFile.takeActivityResult(requestCode, intent, mUploadCallback, mImageUri)}}@Overrideoverride fun onDestroy() {//防止更新dialog内存泄漏super.onDestroy()}/*** 登录成功后跳转*/open fun loginSuccessJump() {}/*** 给网页传值* 传递给js,格式是"scanCodeResult('${data.extras}')"* 其中单引号很重要,可能导致js script error*/private fun setEvaluateJavascript(jspMethodAndValue: String) {LogUtils.d(TAG,"setEvaluateJavascript mWebView ${mWebView}, jspMethodAndValue $jspMethodAndValue")mWebView?.evaluateJavascript(jspMethodAndValue, ValueCallback<String>() {LogUtils.d(TAG, "给网页传值为: $jspMethodAndValue")})}
}

android layout布局

<?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"><WebViewandroid:id="@+id/main_web_view"android:layout_width="match_parent"android:layout_height="match_parent" /></androidx.constraintlayout.widget.ConstraintLayout>

2.获取照片的主要思路是两个方法:

  • webView官方打开文件选取方法onShowFileChooser,把网页回传文件
        //webView官方打开文件选取方法onShowFileChooser,把网页回传文件mWebView?.webChromeClient = object : WebChromeClient() {//API >=21(android 5.0.1)回调此方法override fun onShowFileChooser(webView: WebView?,filePathCallback: ValueCallback<Array<Uri>>?,fileChooserParams: FileChooserParams?): Boolean {mUploadCallback = filePathCallback//使用拍照或者打开文件mImageUri = ChoosePhotoFile.takePhoto(mActivity)//设置false会IllegalStateException: Duplicate showFileChooser resultreturn true}}
  • 拍照或者相册选中后界面跳回后,使用ValueCallback<Array>回传
    fun onActivityResultResponse(requestCode: Int, resultCode: Int, intent: Intent?) {LogUtils.d(TAG,"onActivityResultResponse requestCode $requestCode, resultCode:$resultCode")if (requestCode == ChoosePhotoFile.REQUEST_CODE) {//拍照或者相册选中后界面跳回后,结果文件的使用ChoosePhotoFile.takeActivityResult(requestCode, intent, mUploadCallback, mImageUri)}}
  • 以上onActivityResultResponse方法需要在actvity onActivityResult方法中使用
    (因为我这里是activity嵌套fragment的,如果直接在activity使用webview就不需我这太麻烦)
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)LogUtils.d(TAG,"onActivityResult requestCode11 $requestCode, resultCode:$resultCode")//设置当前fragmentval fragment = mDashboardViewModel.fragment.valueLog.d(TAG, "fragment: $fragment")Log.d(TAG, "fragment.isResumed: ${fragment?.isResumed}")//界面返回时VisitorFragment还没有Resumedif (fragment is VisitorFragment) {val visitorFragment: VisitorFragment = fragmentvisitorFragment.onActivityResultResponse(requestCode, resultCode, data)}}

3.打开相机和相册的工具类

object ChoosePhotoFile {private const val TAG = "ChoosePhotoFile"const val REQUEST_CODE: Int = 12345fun takePhoto(activity: Activity): Uri {//相机可以访问的公共位置才能存储,获取时需要读取文件权限val file: String =Environment.getExternalStorageDirectory().toString() + File.separator + Environment.DIRECTORY_PICTURES + File.separatorval fileName = "Image_${SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())}.jpg"val realFile = File(file, fileName)val imageUri = Uri.fromFile(realFile)LogUtils.d(TAG, "realFile:$realFile, imageUri: $imageUri")// 拍照后获取图片需要文件权限,界面跳转直接拿文件不需要权限//检查申请读文件权限CheckPermissionUtils.requestPermissions(activity,arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA))//调起相机,拍一张照片val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)//调起相册,取一张照片val photoIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)//选择方式,拍照或者相册val chooserIntent = Intent.createChooser(photoIntent, "Image Chooser")chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf<Parcelable>(captureIntent))activity.startActivityForResult(chooserIntent, REQUEST_CODE)return imageUri}/*** H5针对从拍照或者相册中选中的图片做处理* @param imageUri 拍照返回的数据,*/fun takeActivityResult(requestCode: Int,intent: Intent?,filePathCallback: ValueCallback<Array<Uri>>?,imageUri: Uri?) {if (requestCode == REQUEST_CODE) {//从相册获取,返回的intentif (intent != null && intent.data != null) {var uri: Uri = intent.data as UriLogUtils.d(TAG, "file uri: $uri")filePathCallback?.onReceiveValue(arrayOf<Uri>(uri))} else {//从拍照中获取图片,已经返回的imageUriLogUtils.d(TAG, "take photo imageUri: $imageUri")if (imageUri != null) {filePathCallback?.onReceiveValue(arrayOf<Uri>(imageUri))} else {filePathCallback?.onReceiveValue(null)}}}}
}
方式二:使用android 组件库是实现-朋友圈获取照片功能

实际效果和流程示图
在这里插入图片描述
1.获取照片的主要思路是两个方法-替换

  • webView官方打开文件选取方法onShowFileChooser,把网页回传文件
        //webView官方打开文件选取方法onShowFileChooser,把网页回传文件mWebView?.webChromeClient = object : WebChromeClient() {//API >=21(android 5.0.1)回调此方法override fun onShowFileChooser(webView: WebView?,filePathCallback: ValueCallback<Array<Uri>>?,fileChooserParams: FileChooserParams?): Boolean {mUploadCallback = filePathCallback//使用拍照或者打开文件
//                mImageUri = ChoosePhotoFile.takePhoto(mActivity)//模拟微信朋友圈获取照片模式LogUtils.d(TAG,"onShowFileChooser")PictureSelectorUtils.startPictureSelector(mActivity)//设置false会IllegalStateException: Duplicate showFileChooser resultreturn true}}
  • 拍照或者相册选中后界面跳回后,使用ValueCallback<Array>回传
    fun onActivityResultResponse(requestCode: Int, resultCode: Int, intent: Intent?) {LogUtils.d(TAG,"onActivityResultResponse requestCode $requestCode, resultCode:$resultCode")if (requestCode == PictureSelectorUtils.REQUEST_PICTURE_SELECTOR) {LogUtils.d(TAG, "onActivityResultResponse REQUEST_PICTURE_SELECTOR")PictureSelectorUtils.takeActivityResult(requestCode, intent, mUploadCallback)}}

2.打开相机和相册的工具类

object PictureSelectorUtils {const val REQUEST_PICTURE_SELECTOR = 10012const val TAG = "PictureSelectorUtils"fun startPictureSelector(activity: Activity) {LogUtils.d(TAG, "startPictureSelector")// 拍照后获取图片需要文件权限,界面跳转直接拿文件不需要权限//检查申请读文件权限
//        CheckPermissionUtils.requestPermissions(
//            activity,
//            arrayOf(
//                Manifest.permission.CAMERA,
//                Manifest.permission.READ_EXTERNAL_STORAGE,
//                Manifest.permission.WRITE_EXTERNAL_STORAGE
//            )
//        )//插件里自带了静态权限以及权限校验PictureSelector.create(activity).openGallery(PictureMimeType.ofImage()).imageEngine(GlideEngine) // Please refer to the Demo GlideEngine.java.isWeChatStyle(true) // 是否开启微信图片选择风格.selectionMode(PictureConfig.MULTIPLE).forResult(REQUEST_PICTURE_SELECTOR)}fun getPictures(data: Intent): MutableList<Uri> {val selectList = PictureSelector.obtainMultipleResult(data)LogUtils.d(TAG, "getPicture selectList: $selectList")// 将照片路径转换成 Uri 列表val imageUris: MutableList<Uri> = ArrayList()if (selectList.isEmpty()) {LogUtils.d(TAG, "getPicture selectList isEmpty")return imageUris}for (imagePath in selectList) {var path = imagePath.pathLogUtils.d(TAG, "path: $path")val uri = Uri.parse(path)LogUtils.d(TAG, "uri: $uri")imageUris.add(uri)}LogUtils.d(TAG, "imageUris: ${imageUris.toString()}")return imageUris}/*** H5针对从文件钟选中的图片做处理*/fun takeActivityResult(requestCode: Int,intent: Intent?,filePathCallback: ValueCallback<Array<Uri>>?,) {if (requestCode == REQUEST_PICTURE_SELECTOR) {val selectList = PictureSelector.obtainMultipleResult(intent)LogUtils.d(TAG, "getPicture selectList: $selectList")// 将照片路径转换成 Uri 列表val imageUris: MutableList<Uri> = ArrayList()if (selectList.isEmpty()) {LogUtils.d(TAG, "getPicture selectList isEmpty")filePathCallback?.onReceiveValue(null)return}for (imagePath in selectList) {var path = imagePath.pathLogUtils.d(TAG, "path: $path")val uri = Uri.parse(path)LogUtils.d(TAG, "uri: $uri")imageUris.add(uri)}LogUtils.d(TAG, "imageUris: ${imageUris.toString()}")filePathCallback?.onReceiveValue(imageUris.toTypedArray())}}}

其中使用第三方组件库-实现类似朋友圈获取照片的样式,需要引入一下依赖

    //照片获取类微信朋友圈implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0'

拍照后获取图片需要文件权限,界面跳转直接拿文件不需要权限,且第三方插件库里自带了静态权限以及权限请求

3.我完整的代码——类似朋友圈获取界面逻辑

class VisitorFragment : Fragment() {private lateinit var binding: FragmentVisitorBindinglateinit var mActivity: Activityprivate lateinit var mRoot: Viewcompanion object {const val TAG = "VisitorFragment"}private var mWebViewUrl: String = "file:///android_asset/vue_android_demo.html"var mAppName = MainApplication.instance.configuration.BASE_APP_LOGIN_IDENTITYvar mSystemName = WebViewConstant.DEFAULT_SYSTEM_NAMEprivate var mVisitorAndroidJs: VisitorAndroidJs = VisitorAndroidJs(this)private var mWebView: WebView? = nullvar mAndroidId: String = WebViewConstant.DEFAULT_DEVICE_SIGNvar mApiKey: String = WebViewConstant.DEFAULT_API_KEY//回传H5时使用的对象private var mUploadCallback: ValueCallback<Array<Uri>>? = null//拍照传递的路径uriprivate var mImageUri: Uri? = null/*** onCreate方法是Activity生命周期的第一个回调方法* ,当Activity被创建时被调用。* 在这个方法中,你可以进行一些初始化的操作,比如设置布局、绑定控件、初始化数据等。** @param savedInstanceState If the fragment is being re-created from* a previous saved state, this is the state.*/override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = FragmentVisitorBinding.inflate(layoutInflater)mRoot = binding.root//记录val viewModel = ViewModelProvider(requireActivity())[DashboardViewModel::class.java]viewModel.setFragment(this)LogUtils.d(TAG, "onCreate")}/*** onCreateView方法是Fragment生命周期的回调方法,* 当Fragment创建并绘制其用户界面时被调用。* 在这个方法中,你可以通过返回一个View对象来定义Fragment的用户界面。* 它常用于加载布局文件、查找和初始化控件等操作。** @return*/override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View {LogUtils.d(TAG, "onCreateView")initView(mRoot, layoutInflater, null)return mRoot}fun initView(parent: View?, inflater: LayoutInflater?, container: ViewGroup?) {//设置当前fragmentmAndroidId = DeviceUtils.getUniqueId(mActivity)initWebView()mWebView?.loadUrl(mWebViewUrl)//弹出展示链接showToast("${getString(R.string.current_develop_environment)}$mWebViewUrl")initData()}override fun onAttach(context: Context) {super.onAttach(context)mActivity = context as Activity}/*** @param msg 内容*/fun showToast(msg: String?) {val activity: Activity? = activityactivity?.runOnUiThread {Toast.makeText(activity,msg,Toast.LENGTH_SHORT).show()}}private fun initData() {}override fun onResume() {super.onResume()LogUtils.d(TAG, "onResume")}@SuppressLint("SetJavaScriptEnabled")private fun initWebView() {LogUtils.d(TAG, "initWebView")mWebView = binding.mainWebViewmWebView?.requestFocus()mWebView?.isHorizontalScrollBarEnabled = falsemWebView?.isVerticalScrollBarEnabled = falseval setting = mWebView?.settingssetting?.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW;setting?.javaScriptEnabled = true//用于开启或禁用其 DOM(文档对象模型)存储功能,浏览器缓存这些数据setting?.domStorageEnabled = true//允许访问文件,默认允许setting?.allowFileAccess = true//设置不,会引起webView重新不急,默认NARROW_COLUMNSsetting?.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS//自动缩放setting?.setSupportZoom(true)setting?.builtInZoomControls = true//自适应屏幕setting?.useWideViewPort = truesetting?.loadWithOverviewMode = true//支持多窗口setting?.setSupportMultipleWindows(true)setting?.setAppCacheEnabled(true)setting?.domStorageEnabled = true//定位setting?.setGeolocationEnabled(true)//优先使用缓存数据,在缓存数据不存在的情况下才去获取网络数据setting?.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORKsetting?.savePassword = false//设置js接口mVisitorAndroidJs.let { mWebView?.addJavascriptInterface(it, "android") }//页面不跳转浏览器mWebView?.webViewClient = object : WebViewClient() {override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {LogUtils.d(TAG, "url: $url")view.loadUrl(url)return true}override fun shouldInterceptRequest(view: WebView,request: WebResourceRequest): WebResourceResponse? {var uri = request.urlvar path = uri.pathLogUtils.d(TAG, "uri: $uri, path: $path")return super.shouldInterceptRequest(view, request)}}//webView官方打开文件选取方法onShowFileChooser,把网页回传文件mWebView?.webChromeClient = object : WebChromeClient() {//API >=21(android 5.0.1)回调此方法override fun onShowFileChooser(webView: WebView?,filePathCallback: ValueCallback<Array<Uri>>?,fileChooserParams: FileChooserParams?): Boolean {mUploadCallback = filePathCallback//使用拍照或者打开文件
//                mImageUri = ChoosePhotoFile.takePhoto(mActivity)//模拟微信朋友圈获取照片模式LogUtils.d(TAG,"onShowFileChooser")PictureSelectorUtils.startPictureSelector(mActivity)//设置false会IllegalStateException: Duplicate showFileChooser resultreturn true}}}fun onActivityResultResponse(requestCode: Int, resultCode: Int, intent: Intent?) {LogUtils.d(TAG,"onActivityResultResponse requestCode $requestCode, resultCode:$resultCode")// 扫描二维码/条码回传if (requestCode == ScanCodeUtils.REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) {LogUtils.d(TAG, "onActivityResultResponse ${intent?.extras}")if (intent == null) {//弹出展示链接showToast("扫描结果为空")return}//传递给js,格式是"scanCodeResult('${data.extras}')",其中单引号很重要,可能导致js script error//codedContent是组件中定义的参数名setEvaluateJavascript("scanCodeResult('${intent.getStringExtra("codedContent")}')")
//        } else if (requestCode == ChoosePhotoFile.REQUEST_CODE) {
//            //拍照,界面跳回后,结果文件的使用
//            ChoosePhotoFile.takeActivityResult(requestCode, intent, mUploadCallback, mImageUri)} else if (requestCode == PictureSelectorUtils.REQUEST_PICTURE_SELECTOR) {LogUtils.d(TAG, "onActivityResultResponse REQUEST_PICTURE_SELECTOR")PictureSelectorUtils.takeActivityResult(requestCode, intent, mUploadCallback)}}@Overrideoverride fun onDestroy() {//防止更新dialog内存泄漏super.onDestroy()}/*** 登录成功后跳转*/open fun loginSuccessJump() {}/*** 给网页传值* 传递给js,格式是"scanCodeResult('${data.extras}')"* 其中单引号很重要,可能导致js script error*/private fun setEvaluateJavascript(jspMethodAndValue: String) {LogUtils.d(TAG,"setEvaluateJavascript mWebView ${mWebView}, jspMethodAndValue $jspMethodAndValue")mWebView?.evaluateJavascript(jspMethodAndValue, ValueCallback<String>() {LogUtils.d(TAG, "给网页传值为: $jspMethodAndValue")})}
}

三、总结一下

  • H5调用公共获取图片文件方法,
  • 在android手机端,H5主要依赖Webview,
  • 这边在webview声明并重写该方法onShowFileChooser
  • 使用工具类打开相机或相册,可以两种方式安卓原生方式或者利用第三方组件库方式
  • 选中图片,返回uri列表给H5
  • H5收到uri照片列表,并且使用前端方式展示

创造价值,乐哉分享!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/697793.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【每日一题】2583. 二叉树中的第 K 大层和-2024.2.23

题目: 2583. 二叉树中的第 K 大层和 给你一棵二叉树的根节点 root 和一个正整数 k 。 树中的 层和 是指 同一层 上节点值的总和。 返回树中第 k 大的层和(不一定不同)。如果树少于 k 层,则返回 -1 。 注意,如果两个节点与根节点的距离相同,则认为它们在同一层。 示…

canvas水波纹效果,jquery鼠标水波纹插件

canvas水波纹效果&#xff0c;jquery鼠标水波纹插件 效果展示 jQuery水波纹效果&#xff0c;canvas水波纹插件 HTML代码片段 <div class"scroll04wrap"><h3>发展历程</h3><div class"scroll04"><p>不要回头&#xff0c;一…

前端工程Bem架构及其封装

文章目录 简介语法在vue3项目中引用sass创建bem.scss文件修改vite.config.tsvue文件中使用结果 这是我学习记录的笔记&#xff0c;如有不正&#xff0c;欢迎补充 简介 首先认识一下什么是bem架构&#xff1f;BEM的意思就是块&#xff08;block&#xff09;、元素&#xff08;e…

【DDD】学习笔记-发布者—订阅者模式

在领域设计模型中引入了领域事件&#xff0c;并不意味着就采用了领域事件建模范式&#xff0c;此时的领域事件仅仅作为一种架构或设计模式而已&#xff0c;属于领域设计模型的设计要素。在领域设计建模阶段&#xff0c;如何选择和设计领域事件&#xff0c;存在不同的模式&#…

nginx-ingress-controller组件中Nginx的版本升级

参考链接&#xff1a;https://blog.csdn.net/qq_22824481/article/details/133761302 https://blog.csdn.net/mengfanshaoxia/article/details/127155020 https://blog.csdn.net/weixin_39961559/article/details/87935873 概要 业务区k…

JAVAEE初阶 JVM(一)

JVM的热门话题 一. JVM中的内存区域划分1.经典笔试题. 二. JVM的类加载机制 一. JVM中的内存区域划分 1.经典笔试题. 二. JVM的类加载机制

wondows10用Electron打包threejs的项目记录

背景 电脑是用的mac&#xff0c;安装了parallels desktop ,想用electron 想同时打包出 苹果版本和windows版本。因为是在虚拟机里安装&#xff0c;它常被我重装&#xff0c;所以记录一下打包的整个过程。另外就是node生态太活跃&#xff0c;几个依赖没记录具体版本&#xff0…

lora网关智慧工厂三色灯安灯状态采集钡铼技术S281

LoRa网关结合钡铼技术S281模块在智慧工厂三色灯安灯状态采集方面具有广泛的应用前景。智慧工厂的安全生产管理对于企业生产经营至关重要&#xff0c;而三色灯安灯是工厂安全生产管理的重要指示灯&#xff0c;通过LoRa无线通信技术和钡铼技术S281模块&#xff0c;可以实现对三色…

使用 package.json 配置代理解决 React 项目中的跨域请求问题

使用 package.json 配置代理解决 React 项目中的跨域请求问题 当我们在开发前端应用时&#xff0c;经常会遇到跨域请求的问题。为了解决这个问题&#xff0c;我们可以通过配置代理来实现在开发环境中向后端服务器发送请求。 在 React 项目中&#xff0c;我们可以使用 package…

MES系统中的手动排产和自动排产-助力生产效率

企业在排产管理中面临的问题&#xff1a; 大多数的企业在调度排产过程中&#xff0c;都会遇到以下问题。首先是插单非常的多&#xff0c;计划调整困难&#xff0c;会经常性的发生原材料、零部件的备货不足。计划按MRP或库存展示计算出需求后将产生大量工单&#xff0c;这些工单…

《剑指Offer》笔记题解思路技巧优化_Part_6

《剑指Offer》笔记&题解&思路&技巧&优化_Part_6 &#x1f60d;&#x1f60d;&#x1f60d; 相知&#x1f64c;&#x1f64c;&#x1f64c; 相识&#x1f622;&#x1f622;&#x1f622; 开始刷题&#x1f7e1;1.LCR 168. 丑数—— 丑数&#x1f7e2;2. LCR 16…

Kubernetes服务网络Ingress网络模型分析、安装和高级用法

文章目录 1、Ingres简介2、Ingres网络模型分析3、安装Ingress4、使用4.1、搭建测试环境4.2、域名访问4.3、路径重写&#xff08;高级用法&#xff09;4.4、流量限制&#xff08;高级用法&#xff09; 5、总结 1、Ingres简介 Ingress翻译过来是“入口”的意思&#xff0c;也就是…

切换分支时候IDEA提示:workspace associated with branch feature has been restored

切换分支时候IDEA提示&#xff1a;workspace associated with branch feature has been restored 这个消息是指与"feature"分支关联的工作区已经恢复。在Git中&#xff0c;工作区是指你当前正在进行修改和编辑的文件和目录。当你切换分支时&#xff0c;Git会自动将工…

怎么把试卷图片转换成word?这4种方法一看就会

怎么把试卷图片转换成word&#xff1f;在数字化日益盛行的今天&#xff0c;我们常常会面临将纸质试卷或图片中的试卷内容转化为Word文档的需求。无论是为了对试卷内容进行编辑、修改&#xff0c;还是为了在线共享、远程教学&#xff0c;将图片转换为Word文档都成为了至关重要的…

集成TinyMCE富文本编辑器

若依的基础上集成TinyMCE富文本编辑器 前端bootstrap TinyMCE官网链接 TinyMCE所需静态资源下载链接 开源项目-若依链接 将TinyMCE静态资源包放入项目中&#xff1b; 代码引入css&#xff1a; <!-- 引入TinyMCE CSS --><link th:href"{/ajax/libs/tinymce/j…

SD-WAN:快速改造升级企业原有网络架构

随着企业信息化的推进&#xff0c;传统网络架构已难以满足企业日益复杂和多样化的组网互联需求。企业在不断提高对网络的要求&#xff0c;包括各办公点的互联数据传输、资源共享、视频会议、ERP、OA、邮箱系统、云服务等应用需求&#xff0c;以及对网络运维工作的简化和降低难度…

Spring Event 快速入门

请直接看原文 : Spring Event&#xff0c;贼好用的业务解耦神器&#xff01; (qq.com) -------------------------------------------------------------------------------------------------------------------------------- 前言 Spring Event 同步使用 Spring Event 异…

ADAS智能驾驶测试知多少?

当涉及ADAS&#xff08;Advanced Driver Assistance Systems&#xff09;智能驾驶的测试时&#xff0c;有一个完整的测试体系可以用来评估系统的性能和功能。 1. 传感器测试 1.1 传感器校准测试 描述&#xff1a;确保传感器&#xff08;如雷达、摄像头、激光雷达等&#xff09;…

【stm32】hal库学习笔记-UART/USART串口通信(超详细!)

【stm32】hal库学习笔记-UART/USART串口通信 hal库驱动函数 CubeMX图形化配置 导入LCD.ioc RTC设置 时钟树配置 设置LSE为RTC时钟源 USART设置 中断设置 程序编写 编写主函数 /* USER CODE BEGIN 2 */lcd_init();lcd_show_str(10, 10, 16, "Demo12_1:USART1-CH340&q…

Java中各种O(PO,BO,DTO,VO等) 是不是人为增加系统复杂度?

Java中各种O(PO,BO,DTO,VO等) 是不是人为增加系统复杂度&#xff1f; 在Java和其他编程语言的开发过程中&#xff0c;经常会用到几个以"O"结尾的缩写&#xff0c;比如PO,BO,DTO,VO等等&#xff0c;O在这里是Object的缩写&#xff0c;不同的O代表了不同的数据类型&am…