原地址:Android5.0及以上系统WebView上传图片遇到的那些坑_fuzhongbin的博客-CSDN博客
之前项目中有做在WebView中上传文件的操作(调用系统相册选择照片来进行上传),当我们在web页面上点击选择文件的控件
(<input type="file">)时,会回調WebChromeClient下的openFileChoose();(5.0及以上的系统会掉的是onShowFileChooser())。这个
时候我们在这两个方法中通过Intent打开系统相册活着支持改Intent的第三方应用来选择图片。如下代码:
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
private void openImageChooserActivity() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i,
"Image Chooser"), FILE_CHOOSER_RESULT_CODE);
}
最后我们在onActivityForResult()中将选择的图片的内容通过ValueCallback的onReceiveValue方法返回给WebView,然后通过Js
上传。代码如下:、
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_CHOOSER_RESULT_CODE) {
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (uploadMessage != null) {
uploadMessage.onReceiveValue(result);
uploadMessage = null;
}
}
}
VauleCallback是WebView组件通过openFileChooser()或者onShowFileChooser()提供给我们的,它里面包含一个或者一组
Uri,然后我们在onActivityResult()里将Uri传给ValueCallback的onReceiveValue()方法,这样WebView就知道我们选择了什么
文件。
很多5.0以上的系统使用openFileChooser()方法不能唤起系统相册或第三方app来选择图片,所以在5.0以上要重写
onShowFileChooser(),所以为了解决这个问题兼容各个版本,我们需要对openFileChooser()进行重载,同时针对5.0及以上系统
提供onShowFileChooser()方法:
webview.setWebChromeClient(new WebChromeClient() {
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
***
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
***
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback,
String acceptType, String capture) {
***
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView,
ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams) {
***
return true;
}
});
上述***代表你要打开系统相册并缓存valueCallback或filePathCallback;
大家应该注意到在onShowfileChooser()中的ValueCallback包含了一组Uri(Uri[]),所以针对5.0以上的系统我们还需要在
onActivityForResult()中做一点点处理,见下面完整代码。
处理完这个还有一步需要处理,那就是在混淆文件中混淆openFileChooser(),如果在混淆文件中不进行处理坑会导致打包好之后
的apk安装到真机上无法选择图片,解决方案如下:
-keepclassmembers class * extends android.webkit.WebChromeClient{
public void openFileChooser(...);
}
下面贴出完整源码:
public class MainActivity extends AppCompatActivity {
private ValueCallback<Uri> uploadMessage;
private ValueCallback<Uri[]> uploadMessageAboveL;
private final static int FILE_CHOOSER_RESULT_CODE = 10000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webview = (WebView) findViewById(R.id.web_view);
assert webview != null;
WebSettings settings = webview.getSettings();
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setJavaScriptEnabled(true);
webview.setWebChromeClient(new WebChromeClient() {
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
uploadMessageAboveL = filePathCallback;
openImageChooserActivity();
return true;
}
});
String targetUrl = "file:///android_asset/up.html";
webview.loadUrl(targetUrl);
}
private void openImageChooserActivity() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_CHOOSER_RESULT_CODE) {
if (null == uploadMessage && null == uploadMessageAboveL) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (uploadMessageAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
} else if (uploadMessage != null) {
uploadMessage.onReceiveValue(result);
uploadMessage = null;
}
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)
return;
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
String dataString = intent.getDataString();
ClipData clipData = intent.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null)
results = new Uri[]{Uri.parse(dataString)};
}
}
uploadMessageAboveL.onReceiveValue(results);
uploadMessageAboveL = null;
}
}
注意事项:
做完这些可能调用系统相册是没有问题的,但是如果自己写的一个选择图片的页面,我们通常可能会在onActivityForResult()中
回传的是一个放置选中图片绝对路径的List<String> path=new List();这样的话可能在5.0及以上系统有些选择图片之后不能显
示,比如我亲测的有华为手机6.0,7.0系统,因为Android系统相册默认的回传路径是相对路径是可以正常上传,所以我猜测不能
显示的原因跟路径有关,这种情况下我们需要在onActivityForResult()中对5.0及以上的系统处理代码中将绝对路径转换为相对路
径,绝对路径转相对路径的的代码如下:
public static Uri getImageContentUri(Context context, String filePath) {//File imageFile
//String filePath = imageFile.getAbsolutePath();//根据文件来获取路径
Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID }, MediaStore.Images.Media.DATA + "=? ",
new String[] { filePath }, null);
if (cursor != null && cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse("content://media/external/images/media");
return Uri.withAppendedPath(baseUri, "" + id);
} else {
if (StringUtil.isNotEmpty(filePath)) {//imageFile.exists()判断文件存不存在
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, filePath);
return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null;
}
}
}
本人建议在onActivityForResult()方法中可以将imageFile传递回来便于处理,传递绝对路径的话就将绝对路径转换为相对路径然
后通过ValueCallback(results)将路径传递进去即可。
如果上传之后在华为6。0,7.0系统中不能显示,首先要确保有下面这两个设置:
mWebview.getSettings().setJavaScriptEnabled(true);//启用js
mWebview.getSettings().setBlockNetworkImage(false);//解决图片不显示
如果这两个都有设置,就可能是你用https获取的图片引用的是http的,因为Android WebView从Lollipop开始默认不允许混合模式,https当中不能加载http资源,需要设置开启。
则需要在WebSetting中添加如下代码进行设置:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){//
webSettings.setMixedContentMode(android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}