H5获取手机相机或相册图片两种方式-Android通过webview传递多张照片给H5
需求目的: 手机机通过webView展示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照片列表,并且使用前端方式展示
创造价值,乐哉分享!
相关文章:
H5获取手机相机或相册图片两种方式-Android通过webview传递多张照片给H5
需求目的: 手机机通过webView展示H5网页,在特殊场景下,需要使用相机拍照或者从相册获取照片,上传后台。 完整流程效果: 如下图 一、H5界面样例代码 使用html文件格式,文件直接打开就可以展示布局&#…...
mysql进阶学习 | DAY 14
存储引擎 体系结构 连接层 服务层 引擎层 存储层 存储引擎 表类型 查看引擎 查看建表语句 指定存储引擎 ENGINE SHOW engins InnoDB 默认存储引擎 遵循ACID模型 支持事务 行级锁 提高并发访问性能 支持外键 FOREIGN KEY约束 保证数据完整性和正确性 对应文件 xx…...
使用GPT生成python图表
首先,生成一脚本,读取到所需的excel表格 import xlrddata xlrd.open_workbook(xxxx.xls) # 打开xls文件 table data.sheet_by_index(0) # 通过索引获取表格# 初始化奖项字典 awards_dict {"一等奖": 0,"二等奖": 0,"三等…...
[深度学习]yolov9+deepsort+pyqt5实现目标追踪
【YOLOv9DeepSORTPyQt5追踪介绍】 随着人工智能技术的飞速发展,目标追踪在视频监控、自动驾驶等领域的应用日益广泛。其中,YOLOv9作为先进的目标检测算法,结合DeepSORT多目标追踪算法和PyQt5图形界面库,能够为用户提供高效、直观…...
C#_WaitAll、WhenAll、async及await
Task.WhenAll、Task.WaitAll Task.WhenAll 和 Task.WaitAll 都是用于等待多个任务完成的方法,但它们之间有一些重要的区别。 返回类型: Task.WhenAll: 返回一个 Task 对象,该对象表示所有输入任务的联合任务。 Task.WaitAll: 没有返回值。它是一个同步方…...
leetcode hot100零钱兑换Ⅱ
本题可以看出也是背包问题,但区别于之前的01背包问题,这个是完全背包问题的变形形式。 下面介绍01背包和完全背包的区别与联系: 01背包是背包中的物品只能用一次,不可以重复使用,而完全背包则是可以重复使用。01/完全…...
路由器配置DMZ主机映射
路由器配置DMZ主机映射 光猫路由模式配置方法 光猫路由模式是用光猫进行拨号连接,所有设备通过光猫访问互联网,只需要设置光猫的DMZ主机映射地址为局域网主机即可 光猫桥接模式配置方法 光猫桥接模式,是穿透光猫,通过路由器拨…...
ubuntu22.04@Jetson Orin Nano之CSI IMX219安装
ubuntu22.04Jetson Orin Nano之CSI IMX219安装 1. 源由2. 安装2.1 硬件安装2.2 软件配置2.3 新增摄像头 3. 效果4. 参考资料 1. 源由 折腾半天时间,捣鼓这个套装摄像头(IMX219)的安装,死活就是没有这个设备。世界总是这么小,看看遇到问题的大…...
Kettle下载地址
kettle是一款基于java开发的洗数工具,可以通过图像化的操作界面,拖拉拽的操作方式,实现数据导入导出清洗等功能,还支持编写脚本进行数据处理,功能十分强大。 kettle本身是开源免费的,但它的下载地址非常难…...
密码学基本概念
1、信息安全的属性:机密性、认证(消息认证、身份认证)、完整性、不可否认性、可靠性、可用性、可控性、审计。 2、密码学是研究解决机密性、认证(消息认证、身份认证)、完整性、不可否认性这些安全问题的手段…...
9个最受欢迎的开源自动化测试框架盘点!
自动化测试框架可以帮助测试人员评估多个Web和移动应用程序的功能,安全性,可用性和可访问性。尽管团队可以自己构建复杂的自动化测试框架,但是当他们可以使用现有的开源工具,库和测试框架获得相同甚至更好的结果时,通常…...
高速稳定、网络隔离,解析“向日葵控控”远控方案在医疗行业应用
在医疗大健康领域,依托高速发展的信息化技术加速布局智能化,通过远程手段提高医疗服务质量、促进医疗资源共享、提升医疗工作效率,已成为医院和各类社区诊所等提供关键医疗服务部门近年来的发展目标之一。 同时,根据医疗领域的特殊…...
抖音视频提取软件使用功能|抖音视频下载工具
我们的抖音视频提取软件是一款功能强大、易于操作的工具,旨在解决用户在获取抖音视频时需要逐个复制链接、下载的繁琐问题。我们的软件支持通过关键词搜索和分享链接两种方式获取抖音视频,方便用户快速找到自己感兴趣的内容。 主要功能模块:…...
Django入门指南:从环境搭建到模型管理系统的完整教程
环境安装: 由于我的C的Anaconda 是安装在C盘的,但是没内存了,所有我将环境转在e盘,下面的命令是创建环境到指定目录中. conda create --prefixE:\envs\dj42 python3.9进入环境中: conda activate E:\envs\dj42…...
Elasticsearch从入门到精通-01认识Elasticsearch
Elasticsearch从入门到精通-01认识Elasticsearch 👏作者简介:大家好,我是程序员行走的鱼 🍂博主从本篇正式开始ES学习,希望小伙伴可以一起探讨 📖 本篇主要介绍和大家一块简单认识下ES并了解ES中的主要角色…...
Element UI的安装和使用
Element UI 是一个基于 Vue 2.0 的桌面端组件库,广泛用于快速构建高质量的用户界面。以下是 Element UI 组件的安装和使用的详细步骤: 1. 安装 Element UI 在开始之前,确保你已经设置好了 Vue 项目环境。如果你还没有Vue项目,可…...
c++的指针完整教程
概述:C的指针是一种特殊的变量,它存储的是另一个变量的内存地址。指针的使用可以让我们更高效地操作内存,实现动态内存分配等功能。 声明指针变量 要声明一个指针变量,需要在变量类型前加上星号(*)。例如…...
WordPress前端如何使用跟后台一样的Dashicons图标字体?
很多站长都喜欢在站点菜单或其他地方添加一些图标字体,常用的就是添加Font Awesome 图标和阿里巴巴矢量库图标iconfont。其实我们使用的 WordPress 本身就有一套管理员使用的官方图标字体 Dashicons,登录我们站点后台就能看到这些图标字体。那么有没有可…...
redisson实现延迟队列
1.pom引入redisson <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.20.1</version></dependency>整合springboot配置,这个可以参考之前整合redisson的文章,…...
【教程】N2N V3内网穿透、异地组网,包括Win/Linux/Android,包括不同内网实现adb远程连接
目录 一、背景 二、Linux 配置 并运行 N2N - Supernode (必选) 三、Linux -- 配置 并运行 N2N - 边缘节点配置 Edge(可选步骤) 四、Windows -- 配置 并运行 N2N - 边缘节点配置 Edge (可选步骤) (一)配置 TAP 虚拟网卡 (二)配置 N...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
【大模型】RankRAG:基于大模型的上下文排序与检索增强生成的统一框架
文章目录 A 论文出处B 背景B.1 背景介绍B.2 问题提出B.3 创新点 C 模型结构C.1 指令微调阶段C.2 排名与生成的总和指令微调阶段C.3 RankRAG推理:检索-重排-生成 D 实验设计E 个人总结 A 论文出处 论文题目:RankRAG:Unifying Context Ranking…...
FTXUI::Dom 模块
DOM 模块定义了分层的 FTXUI::Element 树,可用于构建复杂的终端界面,支持响应终端尺寸变化。 namespace ftxui {...// 定义文档 定义布局盒子 Element document vbox({// 设置文本 设置加粗 设置文本颜色text("The window") | bold | color(…...
欢乐熊大话蓝牙知识17:多连接 BLE 怎么设计服务不会乱?分层思维来救场!
多连接 BLE 怎么设计服务不会乱?分层思维来救场! 作者按: 你是不是也遇到过 BLE 多连接时,调试现场像网吧“掉线风暴”? 温度传感器连上了,心率带丢了;一边 OTA 更新,一边通知卡壳。…...
Vue 实例的数据对象详解
Vue 实例的数据对象详解 在 Vue 中,数据对象是响应式系统的核心,也是组件状态的载体。理解数据对象的原理和使用方式是成为 Vue 专家的关键一步。我将从多个维度深入剖析 Vue 实例的数据对象。 一、数据对象的定义方式 1. Options API 中的定义 在 Options API 中,使用 …...
