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...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
