需求目的: 手机机通过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: 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照片列表,并且使用前端方式展示
创造价值,乐哉分享!