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: FragmentVisitorBinding
    lateinit var mActivity: Activity
    private lateinit var mRoot: View

    companion object {
        const val TAG = "VisitorFragment"
    }

    private var mWebViewUrl: String = "file:///android_asset/vue_android_demo.html"

    var mAppName = MainApplication.instance.configuration.BASE_APP_LOGIN_IDENTITY
    var mSystemName = WebViewConstant.DEFAULT_SYSTEM_NAME
    private var mVisitorAndroidJs: VisitorAndroidJs = VisitorAndroidJs(this)

    private var mWebView: WebView? = null
    var mAndroidId: String = WebViewConstant.DEFAULT_DEVICE_SIGN
    var mApiKey: String = WebViewConstant.DEFAULT_API_KEY

    //回传H5时使用的对象
    private var mUploadCallback: ValueCallback<Array<Uri>>? = null

    //拍照传递的路径uri
    private 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?) {
        //设置当前fragment
        mAndroidId = 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? = activity
        activity?.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.mainWebView

        mWebView?.requestFocus()
        mWebView?.isHorizontalScrollBarEnabled = false
        mWebView?.isVerticalScrollBarEnabled = false


        val setting = mWebView?.settings
        setting?.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW;
        setting?.javaScriptEnabled = true
        //用于开启或禁用其 DOM(文档对象模型)存储功能,浏览器缓存这些数据
        setting?.domStorageEnabled = true
        //允许访问文件,默认允许
        setting?.allowFileAccess = true
        //设置不,会引起webView重新不急,默认NARROW_COLUMNS
        setting?.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS
        //自动缩放
        setting?.setSupportZoom(true)
        setting?.builtInZoomControls = true
        //自适应屏幕
        setting?.useWideViewPort = true
        setting?.loadWithOverviewMode = true
        //支持多窗口
        setting?.setSupportMultipleWindows(true)
        setting?.setAppCacheEnabled(true)
        setting?.domStorageEnabled = true
        //定位
        setting?.setGeolocationEnabled(true)
        //优先使用缓存数据,在缓存数据不存在的情况下才去获取网络数据
        setting?.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
        setting?.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.url
                var path = uri.path
                LogUtils.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 result
                return 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)
        }

    }


    @Override
    override 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">

    <WebView
        android: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 result
                return 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"
        )
        //设置当前fragment
        val fragment = mDashboardViewModel.fragment.value
        Log.d(TAG, "fragment: $fragment")
        Log.d(TAG, "fragment.isResumed: ${fragment?.isResumed}")
        //界面返回时VisitorFragment还没有Resumed
        if (fragment is VisitorFragment) {
            val visitorFragment: VisitorFragment = fragment
            visitorFragment.onActivityResultResponse(requestCode, resultCode, data)
        }
    }

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

object ChoosePhotoFile {
    private const val TAG = "ChoosePhotoFile"
    const val REQUEST_CODE: Int = 12345

    fun takePhoto(activity: Activity): Uri {
        //相机可以访问的公共位置才能存储,获取时需要读取文件权限
        val file: String =
            Environment.getExternalStorageDirectory()
                .toString() + File.separator + Environment.DIRECTORY_PICTURES + File.separator
        val 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) {
            //从相册获取,返回的intent
            if (intent != null && intent.data != null) {
                var uri: Uri = intent.data as Uri
                LogUtils.d(TAG, "file uri: $uri")
                filePathCallback?.onReceiveValue(arrayOf<Uri>(uri))
            } else {
                //从拍照中获取图片,已经返回的imageUri
                LogUtils.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 result
                return 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 = 10012
    const 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.path
            LogUtils.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.path
                LogUtils.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: FragmentVisitorBinding
    lateinit var mActivity: Activity
    private lateinit var mRoot: View

    companion object {
        const val TAG = "VisitorFragment"
    }

    private var mWebViewUrl: String = "file:///android_asset/vue_android_demo.html"

    var mAppName = MainApplication.instance.configuration.BASE_APP_LOGIN_IDENTITY
    var mSystemName = WebViewConstant.DEFAULT_SYSTEM_NAME
    private var mVisitorAndroidJs: VisitorAndroidJs = VisitorAndroidJs(this)

    private var mWebView: WebView? = null
    var mAndroidId: String = WebViewConstant.DEFAULT_DEVICE_SIGN
    var mApiKey: String = WebViewConstant.DEFAULT_API_KEY

    //回传H5时使用的对象
    private var mUploadCallback: ValueCallback<Array<Uri>>? = null

    //拍照传递的路径uri
    private 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?) {
        //设置当前fragment
        mAndroidId = 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? = activity
        activity?.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.mainWebView

        mWebView?.requestFocus()
        mWebView?.isHorizontalScrollBarEnabled = false
        mWebView?.isVerticalScrollBarEnabled = false


        val setting = mWebView?.settings
        setting?.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW;
        setting?.javaScriptEnabled = true
        //用于开启或禁用其 DOM(文档对象模型)存储功能,浏览器缓存这些数据
        setting?.domStorageEnabled = true
        //允许访问文件,默认允许
        setting?.allowFileAccess = true
        //设置不,会引起webView重新不急,默认NARROW_COLUMNS
        setting?.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS
        //自动缩放
        setting?.setSupportZoom(true)
        setting?.builtInZoomControls = true
        //自适应屏幕
        setting?.useWideViewPort = true
        setting?.loadWithOverviewMode = true
        //支持多窗口
        setting?.setSupportMultipleWindows(true)
        setting?.setAppCacheEnabled(true)
        setting?.domStorageEnabled = true
        //定位
        setting?.setGeolocationEnabled(true)
        //优先使用缓存数据,在缓存数据不存在的情况下才去获取网络数据
        setting?.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
        setting?.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.url
                var path = uri.path
                LogUtils.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 result
                return 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)
        }

    }


    @Override
    override 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照片列表,并且使用前端方式展示

创造价值,乐哉分享!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从源码学习单例模式

单例模式 单例模式是一种设计模式&#xff0c;常用于确保一个类只有一个实例&#xff0c;并提供一个全局访问点。这意味着无论在程序的哪个地方&#xff0c;只能创建一个该类的实例&#xff0c;而不会出现多个相同实例的情况。 在单例模式中&#xff0c;常用的实现方式包括懒汉…

【C语言】linux内核ipoib模块 - ipoib_send

一、中文注释 int ipoib_send(struct net_device *dev, struct sk_buff *skb,struct ib_ah *address, u32 dqpn) {struct ipoib_dev_priv *priv ipoib_priv(dev); // 获取IPoIB设备的私有数据struct ipoib_tx_buf *tx_req; // 发送请求结构体int hlen, rc; // 分别为头部长度…

安装 WSL 报错 Error code: Wsl/WININET_E_NAME_NOT_RESOLVED 问题解决

问题描述 在执行 wsl --install 安装Windows子系统Linux WSL (Windows Subsystem for Linux) 时报错&#xff1a; 无法从“https://raw.githubusercontent.com/microsoft/WSL/master/distributions/DistributionInfo.json”中提取列表分发。无法解析服务器的名称或地址 Error…

如何在本地电脑部署HadSky论坛并发布至公网可远程访问【内网穿透】

文章目录 前言1. 网站搭建1.1 网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3 Cpolar稳定隧道&#xff08;本地设置&#xff09;2.4 公网访问测试 总结 前言 经过多年的基础…

2000-2022年上市公司全要素生产率测算数据合计(原始数据+计算代码+结果)(LP法+OLS法+GMM法+固定效应法)

2000-2022年上市公司全要素生产率测算数据合计&#xff08;原始数据计算代码结果&#xff09;&#xff08;LP法OLS法GMM法固定效应法&#xff09; 1、时间&#xff1a;2000-2022年 2、范围&#xff1a;上市公司 3、指标&#xff1a;证券代码、证券简称、统计截止日期、固定资…

怎么自学python,大概要多久?python多久上手?

无限时长~~~~技术不断在更新&#xff0c;你的自学不也需要一直进行吗&#xff1f; 但如果是问&#xff1a;自学多长时间可以入门&#xff1f;或者可以找到工作&#xff1f;那我可以告诉你答案。 从零基础开始自学Python&#xff0c;依照每个人理解能力的不同&#xff0c;大致…

免费的WP模板网站推荐

免费wordpress模板下载 高端大气上档次的免费wordpress主题&#xff0c;首页大图全屏显示经典风格的wordpress主题。 https://www.wpniu.com/themes/289.html wordpress免费企业主题 深蓝色经典实用的wordpress网站模板&#xff0c;用wordpress免费企业主题搭建网站。 http…

Linux进程【补充】

文章目录 进程概念task_struct 进程创建forkvfork写时拷贝 进程状态僵尸进程孤儿进程守护进程 进程地址空间是什么为什么怎么做 进程概念 进程是一个程序的执行实例或者是担当系统资源分配的实体。当一个程序运行时&#xff0c;被从硬盘加载到内存中&#xff0c;操作系统为每个…

python 线程笔记一 (概念+示例代码)

1. 线程的概念 线程&#xff0c;可简单理解为是程序执行的一条分支&#xff0c;也是程序执行流的最小单元。线程是被系统独立调度和分派的基本单位&#xff0c;线程自己不拥有系统资源&#xff0c;只拥有一点儿在运行中必不可少的资源&#xff0c;但它可与同属于一个进程的其它…

第10届蓝桥杯Scratch图形化编程 选拔赛初级组编程题1:小猫走城堡

准备工作: 导入育最库中的“Castle 3 小猫从坐标点(-165&#xff0c;-93)出发向城堡走去。随着位置的移动&#xff0c;角色大小逐渐变小&#xff0c;最后在城堡前消失。注意1.角色大小在逐渐变化&#xff0c;运行结束再次点击绿旗&#xff0c;程序应还能再次扶行。2.角色应该是…

云原生之API网关Traefik

1. 前言 说到web服务的开源网关&#xff0c;我首先想到的是Nginx&#xff0c;最早使用的就是它&#xff0c;现在都还在使用它。系统上线了Docker Swarm集群之后&#xff0c;不继续使用Nginx直接做Docker服务的网关&#xff0c;是因为Nginx毕竟比Docker Swarm出现的早&#xff0…

YOLOv7基础 | 第2种方式:简化网络结构之yolov7.yaml(由104层简化为30层)

前言:Hello大家好,我是小哥谈。通过下载YOLOv7源码可知,原始的yolov7.yaml文件是拆开写的,比较混乱,也不好理解,并且为后续改进增添了很多困难。基于此种情况,笔者就给大家介绍一种将yolov7.yaml文件简化的方法,将104层简化为30层,并且参数量和计算量和原来是一致的,…

✅鉴权—cookie、session、token、jwt、单点登录

基于 HTTP 的前端鉴权背景cookie 为什么是最方便的存储方案&#xff0c;有哪些操作 cookie 的方式session 方案是如何实现的&#xff0c;存在哪些问题token 是如何实现的&#xff0c;如何进行编码和防篡改&#xff1f;jwt 是做什么的&#xff1f;refresh token 的实现和意义ses…

pythonJax小记(五):python: 使用Jax深度图像(正交投影和透视投影之间的转换)(持续更新,评论区可以补充)

python: 使用Jax深度图像&#xff08;正交投影和透视投影之间的转换&#xff09; 前言问题描述1. 透视投影2. 正交投影 直接上代码解释1. compute_projection_parameters 函数a. 参数解释b. 函数计算 2. ortho_to_persp 函数a. 计算投影参数&#xff1a;b. 生成像素坐标网格&am…

protobuf简单使用(二)

介绍 上一节中&#xff0c;我们介绍了protobuf&#xff0c;简单来说&#xff0c;它是一种消息数据格式&#xff0c;其作用类似于json&#xff0c;但是比json的使用效率要高。 除此以外&#xff0c;我们介绍了protobuf的简单使用&#xff0c;也就是如何可以像使用json一样&…

AI游戏初创公司“奇酷网络”,获得500万元天使投资

发布 | 大力财经 2024年新春伊始的2月25日&#xff0c;国内首家“AI游戏”应用公司“奇酷网络”正式对外宣布&#xff1a;以3,000万元人民币的估值&#xff0c;成功获得500万元人民币的融资&#xff1b;资金来源于一名百度高层和一位知名的天使投资人。 据悉&#xff0c;“奇…

进程 2月24日学习笔记

1.进程: 程序&#xff1a;存放在外存中的一段数据组成的文件 进程&#xff1a;是一个程序动态执行的过程,包括进程的创建、进程的调度、进程的消亡 2.进程相关命令: 1.top 动态查看当前系统中的所有进程信息&#xff08;根据CPU占用率排序&#xff09; PID:唯一识…

namecheap域名如何购买?通过支付宝(详细图文教程)

引言 在完成博客搭建的时候&#xff0c;我们可能需要一个好的域名&#xff0c;自己看起来才会舒服点&#xff0c;同时也可以通过google或者百度等方式搜索到自己博客。 经过实验发现&#xff0c;一个好的后缀名会增强google和百度的搜索seo&#xff0c;增加自己博客的流量。 …

平头哥IP核C906的JTAG调试器DIY教程(一)

背景 最近买了一块基于平头哥C906核&#xff0c;SOC为全志的D1s&#xff0c;的核心板&#xff0c;手工焊接在白嫖的底板上&#xff08;此处感谢百问网老师的友情支持&#xff0c;手动狗头&#xff09;。在焊接完成后&#xff0c;进行点亮跑程序的时候&#xff0c;发现没有优雅…

C++ //练习 8.13 重写本节的电话号码程序,从一个命名文件而非cin读取数据。

C Primer&#xff08;第5版&#xff09; 练习 8.13 练习 8.13 重写本节的电话号码程序&#xff0c;从一个命名文件而非cin读取数据。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /***************************************…