H5调用本地相册/相机上传图片

作者: 慕涵盛华 | 来源:发表于2019-02-01 15:59 被阅读14次

    在开发中有时候会用到H5调用本地图片或者相机,像第三方的实名认证,在线客服等等都需要上传图片。H5中只需要通过<input>调用即可,ios是可以的实现的,不需要自己处理,除非客户端压缩图片,但是Android中需要自己处理,比较坑,本文记录H5调用本地图片或者相机的实现过程以及遇到的问题。

    先看效果图

    H5

    H5主要是通过input标签来获取图片

     <input class="upload-input"  accept="image/*" multiple required type="file" @change="chooseImg">
    

    Android

    通过WebView加载h5页面,监听WebView对应的方法,实现自己的逻辑。

    WebView初始化,放开对应的权限
    private fun initWebView() {
            val settings = mWebView.settings
            settings.javaScriptEnabled = true
            settings.javaScriptCanOpenWindowsAutomatically = true
            //自适应屏幕
            settings.layoutAlgorithm = SINGLE_COLUMN
            settings.loadWithOverviewMode = true
            settings.domStorageEnabled = true
            settings.databaseEnabled = true
            settings.setSupportZoom(false)
            mWebView.isHorizontalScrollBarEnabled = false
            mWebView.isVerticalScrollBarEnabled = false
            mWebView.webViewClient = webViewClient
            mWebView.webChromeClient = webViewChromeClient
    }
    
    设置WebChromeClient

    当点击input标签的时候会调用WebChromeClientonShowFileChooser()方法(5.0+)或者openFileChooser()方法(3.0+)在改方法中处理自己逻辑,这里是弹出一个原生的选择框,选择从相册还是拍照获取图片。

    private val webViewChromeClient = object : WebChromeClient() {
          override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>>,
                                         fileChooserParams: FileChooserParams): Boolean {
              mFilePathCallback = filePathCallback
              val acceptTypes = fileChooserParams.acceptTypes
              //判断文件类型
              if (acceptTypes.contains("image/*")) {
                  showSelectDialog()
              }
              return true
          }
    }
    

    ValueCallbacksWebView组件提供给我们的,它里面包含了一个或者一组Uri,然后我们在onActivityResult()里将Uri传给ValueCallbacksonReceiveValue()方法,这样就把文件回传给H5端了。

    注意:这里会遇到onShowFileChooser()只调用一次的问题,也就是第二次点击按钮的时候该方法不再调用,因为每次调用该方法都需要设置一个回调,不管是不是选择了照片,没有就回传一个null
    /**
     * 显示相册/拍照选择对话框
     */
    private fun showSelectDialog() {
        if (mSelectPhotoDialog == null) {
            mSelectPhotoDialog = SelectDialog(this, View.OnClickListener { view ->
                when (view.id) {
                    R.id.tv_camera -> startCamera()
                    R.id.tv_photo -> startAlbum()
                    //不管选择还是不选择,必须有返回结果,否则就会调用一次
                    R.id.tv_cancel -> {
                        mFilePathCallback?.onReceiveValue(null)
                        mFilePathCallback = null
                    }
                }
            })
        }
        mSelectPhotoDialog?.show()
    }
    

    拍照和打开相册功能封装到了PhotoUtils工具类中了。

    /**
     * 拍照
     * @param context Activity
     */
    fun startCamera(context: Activity) {
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        PATH_PHOTO = getSdCardDirectory(context) + "/temp.png"
        val temp = File(PATH_PHOTO)
        if (!temp.parentFile.exists()) {
            temp.parentFile.mkdirs()
        }
        if (temp.exists()) {
            temp.delete()
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
            // 通过FileProvider创建一个content类型的Uri
            val uri: Uri = FileProvider.getUriForFile(context, context.packageName + ".fileprovider", temp)
            intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
        } else {
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(temp))
        }
        context.startActivityForResult(intent, RESULT_CODE_CAMERA)
    }
    
    注意:拍照需要适配7.0,否者拿不到文件,Android 7.0 以后 通过FileProvider在应用间共享文件;此外拍完照片获取文件的时候不要通过intent来获取数据,直接通过这里指定的存储路径来获取,intent获取的一直是null
    <application
     ......
        <!-- Android7.0拍照权限适配 -->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.gfd.h5pic.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>
    

    file_paths.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <paths>
            <cache-path
                name="cache"
                path="" />
            <external-path
                name="sdcard_files"
                path="" />
            <external-path
                name="external"
                path="" />
            <external-files-path
                name="external_files"
                path="" />
            <external-cache-path
                name="external_cache"
                path="" />
            <root-path
                name="ex_sdcard_files"
                path="" />
            <!--相机相册裁剪-->
            <external-files-path
                name="camera_has_sdcard"
                path="file/" />
            <files-path
                name="camera_no_sdcard"
                path="" />
        </paths>
    </resources>
    

    从相册中选择

    /**
     * 打开相册
     * @param context Activity
     */
    fun startAlbum(context: Activity) {
        val albumIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
        albumIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        albumIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
        context.startActivityForResult(albumIntent, RESULT_CODE_PHOTO)
    }
    

    onActivityResult()方法中通过intent获取选取的照片。

    onActivityResult()方法处理

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == PhotoUtils.RESULT_CODE_CAMERA && resultCode == Activity.RESULT_OK) {
            //拍照并确定
            compressPicture(PhotoUtils.PATH_PHOTO)
        } else if (requestCode == PhotoUtils.RESULT_CODE_PHOTO && resultCode == Activity.RESULT_OK) {
            //相册选择并确定
            val result = data?.data
            val path = result?.let { PhotoUtils.getPath(this, it) }
            if (path == null) {
                mFilePathCallback?.onReceiveValue(null)
            } else {
                compressPicture(path)
            }
        } else {
            mFilePathCallback?.onReceiveValue(null)
        }
    }
    

    这里需要注意的是要判断resultCode,不然拍照完成,选择的不是确定而是取消,也是会上传图片的。

    这里使用Luban压缩以后再上传的

    /**
     * 压缩图片
     */
    private fun compressPicture(path: String) {
        PhotoUtils.compressPicture(this, path, object : PhotoUtils.OnPictureCompressListener() {
            override fun onSuccess(file: File) {
                //上传图片
                mFilePathCallback?.onReceiveValue(arrayOf(Uri.fromFile(file)))
            }
            override fun onError(e: Throwable) {
                mFilePathCallback?.onReceiveValue(null)
            }
        })
    }
    
    最后附上完整的代码

    github

    Kotlin项目实战

    相关文章

      网友评论

        本文标题:H5调用本地相册/相机上传图片

        本文链接:https://www.haomeiwen.com/subject/qhldsqtx.html