这两天在开发公司的一个自营商城的功能,需要配合前端的H5的开发。
中间涉及到webview的图片选择上传的功能,在这里做一下记录。
使用了Kotlin进行的开发,如有看不懂的可以评论回复。
1.图片上传会涉及到图片选择和拍照功能。相关代码如下:
//拍照代码
private fun openCamera() {
imagePaths = FileManager.imageCacheDir().absolutePath + "${System.currentTimeMillis()}.jpg"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
doTakePhotoIn7()
} else {
var intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 必须确保文件夹路径存在,否则拍照后无法完成回调
var vFile = File(imagePaths)
if (!vFile.exists()) {
vFile.mkdirs()
} else {
if (vFile.exists()) {
vFile.delete()
}
}
cameraUri = Uri.fromFile(vFile)
intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri)
startActivityForResult(intent, Short.MAX_VALUE / 3);
}
}
//选择图片
private fun toChoicePic() {
var i = Intent(action)
i?.addCategory(Intent.CATEGORY_OPENABLE)
i?.type = "image/*"
startActivityForResult(Intent.createChooser(i, "Image Chooser"), Short.MAX_VALUE / 2)
}
2.webview的相关处理:
webView.webChromeClient = object : WebChromeClient() {
private var mView: View? = null
/**
* 全屏处理
*/
override fun onShowCustomView(view: View?, callback: CustomViewCallback?) {
super.onShowCustomView(view, callback)
val parent = webView.parent as ViewGroup
parent.removeView(webView)
// 设置背景色为黑色
view?.setBackgroundColor(Color.BLACK)
parent.addView(view)
mView = view
setFullScreen()
}
/**
* 退出全屏
*/
override fun onHideCustomView() {
super.onHideCustomView()
if (mView != null) {
val parent = mView?.parent as ViewGroup
parent.removeView(mView)
parent.addView(webView)
mView = null
quitFullScreen()
}
}
// For Android < 3.0
fun openFileChooser(valueCallback: ValueCallback) {
openFileChooser(valueCallback, "", "")
}
// For Android >= 3.0
fun openFileChooser(valueCallback: ValueCallback, acceptType: String) {
openFileChooser(valueCallback, acceptType, "")
}
//For Android >= 4.1
fun openFileChooser(valueCallback: ValueCallback, acceptType: String, capture: String) {
if (uploadMessage != null) {
uploadMessage = null
}
uploadMessage = valueCallback;
toselectPicture()
}
override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback>?, fileChooserParams: FileChooserParams?): Boolean {
if (uploadMessageAboveL != null) {
uploadMessageAboveL = null
}
uploadMessageAboveL = filePathCallback;
toselectPicture()
return true
}
}
3.因为图片要求在1M以内,所以需要对图片进行相关压缩处理:
/**
* 对图片进行压缩处理
*/
private fun toCompressPic(srcPath: String?): Bitmap {
val newOpts = BitmapFactory.Options()
// 开始读入图片,此时把options.inJustDecodeBounds 设回true了
newOpts.inJustDecodeBounds = true
var bitmap = BitmapFactory.decodeFile(srcPath, newOpts)// 此时返回bm为空
newOpts.inJustDecodeBounds = false
val w = newOpts.outWidth
val h = newOpts.outHeight
// 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
val hh = 800f// 这里设置高度为800f
val ww = 480f// 这里设置宽度为480f
// 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
var be = 1// be=1表示不缩放
if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
be = (newOpts.outWidth / ww).toInt()
} else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
be = (newOpts.outHeight / hh).toInt()
}
if (be <= 0)
be = 1
newOpts.inSampleSize = be// 设置缩放比例
// 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
bitmap = BitmapFactory.decodeFile(srcPath, newOpts)
return compressImage(bitmap)// 压缩好比例大小后再进行质量压缩
}
private fun compressImage(image: Bitmap): Bitmap {
var baos = ByteArrayOutputStream()
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
var options = 100;
while (baos.toByteArray().size / 1024 > 1024) { // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
baos.reset();// 重置baos即清空baos
image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 这里压缩options%,把压缩后的数据存放到baos中
options -= 10;// 每次都减少10
}
var isBm = ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中
var bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
return bitmap;
}
4.相关压缩过程中需要涉及到Uri与文件地址之间的转换,方式如下:
/**
* 功能简述:4.4及以上获取图片的方法
* 功能详细描述:
* 注意:
* @param context
* @param uri
* @return
*/
@RequiresApi(Build.VERSION_CODES.KITKAT)
@TargetApi(Build.VERSION_CODES.KITKAT)
fun getPath(context: Context, uri: Uri): String? {
val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return "${Environment.getExternalStorageDirectory()}/${split[1]}"
}
} else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
return getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(context, contentUri, selection, selectionArgs)
}// MediaProvider
// DownloadsProvider
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
// Return the remote address
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(context, uri, null, null)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}// File
// MediaStore (and general)
return null
}
fun getDataColumn(context: Context, uri: Uri?, selection: String?,
selectionArgs: Array?): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
try {
cursor = context.contentResolver.query(uri!!, projection, selection, selectionArgs, null)
if (cursor != null && cursor!!.moveToFirst()) {
val index = cursor!!.getColumnIndexOrThrow(column)
return cursor!!.getString(index)
}
} finally {
if (cursor != null)
cursor!!.close()
}
return null
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri.authority
}
/**
* Gets the content:// URI from the given corresponding path to a file
* @param context
* @param imageFile
* @return content Uri
*/
fun getImageContentUri(context: Context, imageFile: File): Uri? {
var filePath = imageFile.absolutePath
var cursor = context.contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
Array(1) { MediaStore.Images.Media._ID },
MediaStore.Images.Media.DATA + "=? ",
Array(1) { filePath }, null);
if (cursor != null && cursor.moveToFirst()) {
var id = cursor.getInt(cursor
.getColumnIndex(MediaStore.MediaColumns._ID));
var baseUri = Uri.parse("content://media/external/images/media");
return Uri.withAppendedPath(baseUri, "" + id);
} else {
if (imageFile.exists()) {
var values = ContentValues()
values.put(MediaStore.Images.Media.DATA, filePath);
return context.contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null
}
}
}
5.onActivityResult方法内容如下:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (Short.MAX_VALUE / 2 == requestCode) {
if (Activity.RESULT_OK == resultCode) {
if (uploadMessageAboveL != null) { // 5.0 以上
onActivityResultAboveL(requestCode, resultCode, data)
} else if (uploadMessage != null) { // 5.0 以下
uploadMessage?.onReceiveValue(Uri.parse((data?.getSerializableExtra(PictureSelector.KEY_SELECTED_IMAGE) as? Image)?.path))
uploadMessage = null
}
}
} else {
refreshPhotoList(imagePaths)//刷新相册,防止取不到图片
var afterUri = Uri.parse(MediaStore.Images.Media.insertImage(
context.contentResolver, toCompressPic(imagePaths), null, null))
if (uploadMessageAboveL != null && afterUri != null) { // 5.0 以上
uploadMessageAboveL?.onReceiveValue(Array(1) { afterUri!! })
uploadMessageAboveL = null
} else if (uploadMessage != null) { // 5.0 以下
uploadMessage?.onReceiveValue(afterUri)
uploadMessage = null
} else {
cancelSelect()
}
}
if (Activity.RESULT_CANCELED == resultCode) {
cancelSelect()
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
fun onActivityResultAboveL(requestCode: Int, resultCode: Int, data: Intent?) {
var rest: Array? = null
if (data != null) {
var dataString = data.dataString
var clipData = data.clipData
if (clipData != null) {
rest = Array(clipData.itemCount) { Uri.parse("") }
for (i in 0 until clipData.itemCount) {
var item = clipData.getItemAt(i)
rest[i] = item.uri
}
}
if (dataString != null) {
//将Uri获取绝对地址
var contentToPath = getPath(context, Uri.parse(dataString))
//将选定图片进行压缩之后保存到相册并返回对应的uri
var afterUri = Uri.parse(MediaStore.Images.Media.insertImage(
context.contentResolver, toCompressPic(contentToPath), null, null))
//通过对应的uri获取压缩之后的绝对地址路径
var path2 = getPath(context, afterUri)
//刷新相册
refreshPhotoList(path2)
//将绝对地址路径转换成content://形式的Uri路径
var finalContentPath = getImageContentUri(context, File(path2))
if (finalContentPath != null)
rest = Array(1) { finalContentPath!! }
}
}
uploadMessageAboveL?.onReceiveValue(rest)
uploadMessageAboveL = null
}
/**
* 获取照片结束后
*/
private fun refreshPhotoList(path:String?) {
var f = File(path)
addImageGallery(f)
}
/** 解决拍照或保存图片到相册后在相册中找不到的问题 */
private fun addImageGallery(file: File) {
var values = ContentValues()
values.put(MediaStore.Images.Media.DATA, file.absolutePath)
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
context.contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
/**
* 注意:取消选取图片必须调用此方法,否则第二次选择会失效
* 处理重复选取图片问题
*/
private fun cancelSelect() {
if (uploadMessageAboveL != null) {
uploadMessageAboveL?.onReceiveValue(null)
uploadMessageAboveL = null
}
if (uploadMessage != null) {
uploadMessage?.onReceiveValue(null)
uploadMessage = null
}
}