最近一直都在做与H5交互的事情,算是踩了好多坑吧,再加上个人原因好长时间没更博了,再次回到状态,于是乎更了这篇博客。
项目中有需求WebView加载H5上页面,然后响应H5上的上传文件事件,由于安卓无法像IOS的那样直接调系统原生的接口,所以只能自己封装方法,再加上android M之后运行时权限的问题,像选文件或者拍照,这种敏感的事件都需要单独处理,总之好多坑。
代码写完之后,测试的时候偶然发现的BUG还真难解。
H5上上传文件协议为:
// <input type="file" name="fileField" id="fileField" />
在安卓这边我自己封装了从图库选图或者拍照,选择完文件之后,需要回显在H5上,这些并不麻烦,WebView为我们提供
WebChromeClient 这个类,拓展这个类,然后复写里面的方法,处理起来并不是太麻烦,在此可能需要对不同版本的安卓手机做不同的处理:
代码如下:
package com;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
public class OpenFileWebChromeClient extends WebChromeClient {
public static final int REQUEST_FILE_PICKER = 1;
public ValueCallback<Uri> mFilePathCallback;
public ValueCallback<Uri[]> mFilePathCallbacks;
Activity mContext;
@Override
public void onReceivedTitle(WebView webView, String s) {
super.onReceivedTitle(webView, s);
mCenterTitle.setText(s);
}
public OpenFileWebChromeClient(Activity mContext) {
super();
this.mContext = mContext;
} // Android < 3.0 调用这个方法
public void openFileChooser(final ValueCallback<Uri> filePathCallback) {
mFilePathCallback = filePathCallback;
takeOrPickPicture();
}
// 3.0 + 调用这个方法
public void openFileChooser(final ValueCallback filePathCallback, final String acceptType) {
mFilePathCallback = filePathCallback;
takeOrPickPicture();
}
// js上传文件的<input type="file" name="fileField" id="fileField" />事件捕获 // Android > 4.1.1 调用这个方法
public void openFileChooser(final ValueCallback<Uri> filePathCallback, final String acceptType, final String capture) {
mFilePathCallback = filePathCallback;
takeOrPickPicture();
}
@Override
public boolean onShowFileChooser(final WebView webView, final ValueCallback<Uri[]> filePathCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
mFilePathCallbacks = filePathCallback;
takeOrPickPicture();
return true;
}
private void takeOrPickPicture() {
//系统选照片
// Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
// intent.addCategory(Intent.CATEGORY_OPENABLE);
// intent.setType("*/*");
mContext.startActivityForResult(Intent.createChooser(intent, "File Chooser"), REQUEST_FILE_PICKER);
Intent intent = new Intent(ResumeEditActivity.this, SelectPhotoFromActivity.class);
mContext.startActivityForResult(intent, REQUEST_FILE_PICKER);
}
}
takeOrPickPicture()调用的是自己封装的拍照选图的操作,然后给webview设置WebChromeClient之后选图操作完成
mWebView.setWebChromeClient(mOpenFileWebChromeClient);
在H5上回显则需要在当前Activity里面复写onActivityResult回调方法
代码如下:
/**
* 以下代码是为了适应H5调用本地图片并且显示在h5上
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == OpenFileWebChromeClient.REQUEST_FILE_PICKER && resultCode == Activity.RESULT_OK) {
if (mOpenFileWebChromeClient.mFilePathCallback != null) {
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
if (result != null) {
String path = ProviderPathUtils.getPath(this, result);
Uri uri = Uri.fromFile(new File(path));
mOpenFileWebChromeClient.mFilePathCallback.onReceiveValue(uri);
} else {
mOpenFileWebChromeClient.mFilePathCallback.onReceiveValue(null);
}
}
if (mOpenFileWebChromeClient.mFilePathCallbacks != null) {
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
if (result != null) {
String path = ProviderPathUtils.getPath(this, result);
Uri uri = Uri.fromFile(new File(path));
mOpenFileWebChromeClient.mFilePathCallbacks.onReceiveValue(new Uri[]{uri});
} else {
mOpenFileWebChromeClient.mFilePathCallbacks.onReceiveValue(null);
}
}
onReceiveImage(intent, mOpenFileWebChromeClient.mFilePathCallback, mOpenFileWebChromeClient.mFilePathCallbacks);
mOpenFileWebChromeClient.mFilePathCallback = null;
mOpenFileWebChromeClient.mFilePathCallbacks = null;
} else if (resultCode == Activity.RESULT_CANCELED) {
if (mOpenFileWebChromeClient.mFilePathCallbacks != null) {
//xie :直接点击取消时,ValueCallback回调会被挂起,需要手动结束掉回调,否则再次点击选择照片无响应
mOpenFileWebChromeClient.mFilePathCallbacks.onReceiveValue(null);
mOpenFileWebChromeClient.mFilePathCallbacks = null;
}
}
}
onReceiveImage方法:
private void onReceiveImage(final Intent intent, final ValueCallback<Uri> filePathCallback, final ValueCallback<Uri[]> filePathCallbacks) {
Uri imageUri = null;
String image = intent.getStringExtra("cropImageUri");
if (!TextUtils.isEmpty(image)) {
imageUri = Uri.parse(image);
}
if(filePathCallback != null) {
filePathCallback.onReceiveValue(imageUri);
}
if(filePathCallbacks != null) {
if(imageUri != null) {
filePathCallbacks.onReceiveValue(new Uri[]{imageUri});
}else {
filePathCallbacks.onReceiveValue(null);
}
}
}
至此整个过程处理完毕,我来分析下,开篇提到的BUG,其实还是因为粗心所致,问题是这样的,我们选图时会回调ValueCallBack,但是恰巧此时你并没有选图而是直接点击取消
而整个ValueCallBack会被挂起,也就是说,此次回调一直在等待回调结果,但是一直等不到结果,所以后面再有请求触发就会一直得不到响应,所以导致出现了这个问题。
那我们需要做的就是,当用户并没有选择文件的时候,我们需要手动结束调此次回调过程,避免请求被挂起。
if(resultCode==Activity.RESULT_CANCELED){
if (mOpenFileWebChromeClient.mFilePathCallbacks != null) {
//xie :直接点击取消时,ValueCallback回调会被挂起,需要手动结束掉回调,否则再次点击选择照片无响应
mOpenFileWebChromeClient.mFilePathCallbacks.onReceiveValue(null);
mOpenFileWebChromeClient.mFilePathCallbacks = null;
}
}
方法总比困难多,BUG并不怕,怕的是找不到出BUG的原因呢。