1. 前言
拍照上传或者上传图片、视频、文档等文件,这种需求对于移动端来说是很基本的。Android原生实现这种需求没什么特别大的难度。但如果是嵌套的H5页面的话,就需要踩点坑了。Android 的这个 WebView 真是个不让人省心的控件啊!
2. 解决方案
废话不多说,直接上关键代码。首先,重写 WebView 的 WebChromeClient 中的 openFileChooser() 方法。
mWebView.setWebChromeClient(new WebChromeClient() {
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mAcceptType = "*/*";
toOpenFileChooser(uploadMsg);
}
// For Android >= 3.0
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
if (TextUtils.isEmpty(acceptType)) {
mAcceptType = "*/*";
}
mAcceptType = acceptType;
toOpenFileChooser(uploadMsg);
}
// For Android >= 4.1.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
if (TextUtils.isEmpty(acceptType)) {
mAcceptType = "*/*";
}
mAcceptType = acceptType;
toOpenFileChooser(uploadMsg);
}
// For Android >= 5.0
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
String[] acceptTypes = fileChooserParams.getAcceptTypes();
if (acceptTypes.length > 0) {
if (TextUtils.isEmpty(acceptTypes[0])) {
mAcceptType = "*/*";
} else {
mAcceptType = acceptTypes[0];
}
} else {
mAcceptType = "*/*";
}
toOpenFileChooserAnother(filePathCallback);
return true;
}
});
Android的碎片化真严重,得重写4个方法,但根据参数的情况,只有2种处理方案。这里其实有一个大坑,就是Android 5.0以下的部分设备,会触发不了 openFileChooser() 方法。而且这个大坑没有任何解决方法,好在现在都2020年了,大多数人用的系统都是5.0以上的。
toOpenFileChooser(uploadMsg) 和 toOpenFileChooserAnother(filePathCallback) 这两个方法的具体代码如下所示:
private static final int REQUEST_CODE_FILE_CHOOSER = 0;
private static final int REQUEST_CODE_FILE_CHOOSER_ANOTHER = 1;
/**
* 拍照上传的请求码
*/
private static final int REQUEST_CODE_PHOTO_UPLOAD = 3;
/**
* 打开文件选择器,适用于Android 5.0 以下
*
* @param uploadMsg 加载文件的信息
*/
private void toOpenFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType(mAcceptType);
startActivityForResult(Intent.createChooser(i, getString(R.string.choose_app)), REQUEST_CODE_FILE_CHOOSER);
}
/**
* 打开文件选择器,适用于Android 5.0 及以上
*
* @param uploadMsg 加载文件的信息
*/
private void toOpenFileChooserAnother(ValueCallback<Uri[]> uploadMsg) {
mUploadMessageAnother = uploadMsg;
if ((mAcceptType.equals("*/*") || mAcceptType.contains("image"))
&& ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) {
long oldTime = PreferencesUtils.getLong(mContext, Constant.PREF_REQUEST_CAMERA_TIME);
long newTime = System.currentTimeMillis();
if (oldTime == -1) { // 首次要提示申请权限
PreferencesUtils.putLong(mContext, Constant.PREF_REQUEST_CAMERA_TIME, newTime);
toRequestCameraPermission();
return;
} else {
if (newTime - oldTime >= 7 * 24 * 60 * 60 * 1000) { // 7天后再提示申请权限
PreferencesUtils.putLong(mContext, Constant.PREF_REQUEST_CAMERA_TIME, newTime);
toRequestCameraPermission();
return;
}
}
}
toOpenFileChooserAnother();
}
/**
* 打开文件选择器,适用于Android 5.0 及以上
*/
private void toOpenFileChooserAnother() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if ((mAcceptType.equals("*/*") || mAcceptType.contains("image"))
&& takePictureIntent.resolveActivity(mContext.getPackageManager()) != null) {
File photoFile = null;
try {
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA).format(new Date());
String imageFileName = "PIC_" + timeStamp + "_";
File storageDir = new File(DirUtils.getPictures());
if (!storageDir.exists()) {
storageDir.mkdirs();
}
photoFile = File.createTempFile(imageFileName, ".jpg", storageDir);
}
} catch (Exception e) {
Log.d(Constant.TAG, mClassName + " Unable to create Image File", e);
}
if (photoFile != null) {
mCameraPhotoPath = photoFile.getAbsolutePath();
Log.d(Constant.TAG, "图片路径 : " + mCameraPhotoPath);
takePictureIntent.putExtra("PhotoPath", "file:" + mCameraPhotoPath);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
} else {
takePictureIntent = null;
}
} else {
takePictureIntent = null;
}
Intent[] intentArray;
if (takePictureIntent != null) {
intentArray = new Intent[]{takePictureIntent};
} else {
intentArray = new Intent[0];
}
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType(mAcceptType);
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.choose_app));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
startActivityForResult(chooserIntent, REQUEST_CODE_FILE_CHOOSER_ANOTHER);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
switch (requestCode) {
case REQUEST_CODE_FILE_CHOOSER:
if (mUploadMessage == null) {
return;
}
Uri result = (intent == null || resultCode != RESULT_OK) ? null : intent.getData();
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
break;
case REQUEST_CODE_FILE_CHOOSER_ANOTHER: // Android 5.0 及以上
if (mUploadMessageAnother == null) {
return;
}
Uri[] results = null;
if (resultCode == RESULT_OK) {
if (intent != null) {
results = new Uri[]{intent.getData()};
} else {
if (mCameraPhotoPath != null) {
results = new Uri[]{Uri.parse("file:" + mCameraPhotoPath)};
}
}
} else {
if (!TextUtils.isEmpty(mCameraPhotoPath)) {
File file = new File(mCameraPhotoPath);
file.delete();
}
}
mUploadMessageAnother.onReceiveValue(results);
mUploadMessageAnother = null;
break;
case REQUEST_CODE_PHOTO_UPLOAD:
toOpenFileChooserAnother();
break;
default:
super.onActivityResult(requestCode, resultCode, intent);
break;
}
}
拍照上传,是需要申请“相机权限”的,上面的 toRequestCameraPermission() 方法就是申请权限的,具体代码我就不贴出来了。如果想看完整的demo,可以查看我放在GitHub上面的项目 AndroidWebView。
如果想进一步交流和学习的同学,可以加一下QQ群哦!