一、概述
这几天由于项目的需要,需要我们app端实现H5的图片上传功能,经过一系列的研究知道了webview中的一个方法,通过实现这个方法就能实现webview上传图片的功能。
二、思路
首先我们需要实现openFileChooser这个方法通过,这个方法相当于一个接口,如果你实现了这个接口的相当于给了webview读取本地文件的权利,不过这个方法在android3.0、4.1、5.0都有过更新,所以我们要去实现多个openFileChooser方法。在实现了openFileChooser这个方法之后JS只是读取到了我们这边的文件数据,但是上传功能我们还是要自己去做,我们在openFileChooser中去上传文件,上传之后我们需要通过onActivityResult方法获取这个图片的Url,然后在去调用上传的方法去上传这个文件。好了思路就是这样,下面我们看一下代码。首先我们去实现openFileChooser方法:
private class MyWebChromeClient extends WebChromeClient { // For Android < 3.0 public void openFileChooser(ValueCallback<Uri> valueCallback) { mUploadMessage = valueCallback; goToPhotos(); } // For Android >= 3.0 public void openFileChooser(ValueCallback valueCallback, String acceptType) { mUploadMessage = valueCallback; goToPhotos(); } //For Android >= 4.1 public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) { mUploadMessage = valueCallback; goToPhotos(); } //5.0 @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { if (mUploadMessage != null) { mUploadMessage.onReceiveValue(null); } mValueCallback = filePathCallback; goToPhotos(); return true; }
}
goToPhotos方法其实就是一个跳转相册的方法:
/**
* 进入本地图库
*/
private void goToPhotos() {
Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT); // "android.intent.action.GET_CONTENT"
String IMAGE_UNSPECIFIED = "image/*"; // ①
innerIntent.setType(IMAGE_UNSPECIFIED); // 查看类型
Intent wrapperIntent = Intent.createChooser(innerIntent, null);
startActivityForResult(wrapperIntent, FILECHOOSER_RESULTCODE);
}
这个注释①我说一下,这个地方是设置查看文件的类型,image是代表的是图片的意思,后面的*代表格式,也就是代表全部格式的图片,其实还可以设置image/.png 等类型的图片
最后一步,看一下我们回调然后上传图片:
/**
* 返回文件选择
*/
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == FILECHOOSER_RESULTCODE) {
uploadImgFromSysPhotos(resultCode, intent);
}
}
/**
*
*
* @param resultCode
* @param intent
* @author andYa
*/
private void uploadImgFromSysPhotos(int resultCode, Intent intent) {
if (mUploadMessage != null) {//5.0以下
Uri result = intent == null || resultCode != RESULT_OK ? null
: intent.getData();
mUploadMessage.onReceiveValue(result);
} else if (mValueCallback != null) {//5.0+
Uri[] uris = new Uri[1];
uris[0] = intent == null || resultCode != RESULT_OK ? null
: intent.getData();
mValueCallback.onReceiveValue(uris);
}
}
}
好了基本的功能就可以实现了不过如果你自己写过之后你会发现有几个问题,第一个问题,如果我们进入相册然后没选图片就返回了,那么我们第二次点击上传图片的地方的时候是不能进入相册的。第二个问题就是有些5.0以下的手机是上传不了文件的。第三个问题也就是如果在release环境上面我们是上传不了文件的。
三、坑的解决
问题一:
其实之所以出现问题一的原因就是当你进入相册之后退出来,JS一直在等待着你选择图片Url的回调,你没选肯定是没有回调的,所以会一直卡在这个地方选择不了。我们的解决办法就是当没有选择图片的时候我们去回调一个null来让JS停止等待。下面看一下我们改进的方法。
private void uploadImgFromSysPhotos(int resultCode, Intent intent) {
if (mUploadMessage != null) {//5.0以下
Uri result = intent == null || resultCode != RESULT_OK ? null
: intent.getData();
if(result != null){
mUploadMessage.onReceiveValue(result);
}else if(resultCode == RESULT_CANCELED){
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = null;
} else if (mValueCallback != null) {//5.0+
Uri[] uris = new Uri[1];
uris[0] = intent == null || resultCode != RESULT_OK ? null
: intent.getData();
if (uris[0]!=null){
mValueCallback.onReceiveValue(uris);
}else if(resultCode == RESULT_CANCELED){
mValueCallback.onReceiveValue(null);
}
mValueCallback = null;
}
}
我们在代码中去判断回调的Url是否为空,如果为空的话我们就去onReceiveValue(null)就可以了,这样就不会造成等待的状态了。
问题二:
这个问题造成的原因就是,在android4.几的版本,我有点忘记了,有时候返回的路径是contendprovide的url,不是标准的url,这样js就不能处理,所以我们要处理一下url下面代码:
/**
* 上传图片,调用系统图库 与h5 file标签交互
*
* @param resultCode
* @param intent
* @author linjinpeng 2015年11月30日 14:25:20
*/
private void uploadImgFromSysPhotos(int resultCode, Intent intent) {
if (mUploadMessage != null) {//5.0以下
Uri result = intent == null || resultCode != RESULT_OK ? null
: intent.getData();
if(result != null){
mUploadMessage.onReceiveValue(Uri.parse(getImageAbsolutePath(this,result)));
}else if(resultCode == RESULT_CANCELED){
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = null;
} else if (mValueCallback != null) {//5.0+
Uri[] uris = new Uri[1];
uris[0] = intent == null || resultCode != RESULT_OK ? null
: intent.getData();
if (uris[0]!=null){
mValueCallback.onReceiveValue(uris);
}else if(resultCode == RESULT_CANCELED){
mValueCallback.onReceiveValue(null);
}
mValueCallback = null;
}
}
@TargetApi(19)
public static String getImageAbsolutePath(Activity context, Uri imageUri) {
if (context == null || imageUri == null)
return null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, imageUri)) {
if (isExternalStorageDocument(imageUri)) {
String docId = DocumentsContract.getDocumentId(imageUri);
String[] split = docId.split(":");
String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
} else if (isDownloadsDocument(imageUri)) {
String id = DocumentsContract.getDocumentId(imageUri);
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
} else if (isMediaDocument(imageUri)) {
String docId = DocumentsContract.getDocumentId(imageUri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection, selectionArgs);
}
} // MediaStore (and general)
else if ("content".equalsIgnoreCase(imageUri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(imageUri))
return imageUri.getLastPathSegment();
return getDataColumn(context, imageUri, null, null);
}
// File
else if ("file".equalsIgnoreCase(imageUri.getScheme())) {
return imageUri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
String column = MediaStore.Images.Media.DATA;
String[] projection = { column };
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
我们在onReceiveResult这个方法中进行转换,就可以解决这个问题了。
问题三:
这个问题很简单,其实就是混淆的原因造成的。关于混淆我就不多说了,我们只要将openFileChooser这个方法添加一下混淆就可以了。添加混淆在app\proguard\app-rules.pro里面加上下面这句话就可以了。
- -keepclassmembers class * extends android.webkit.WebChromeClient {
- public void openFileChooser(...);
- }
不过我用的是@Keep注释进行混淆的,在某个方法上面加上@Keep的时候,就可以混淆通过,不过我们要在混淆文件中加上
-keepclasseswithmembers class * { @android.support.annotation.Keep <methods>; }
因为你这个类里面用了好多keep
这样做的好处就是以后的混淆只要在方法中加上keep就可以了,不用再添加混淆文件了,非常方便。这篇文章基本结束了,如果有什么错误或者有什么需要了解的可以在下面留言。
看一下整体的代码:
package com.example.andya.webviewloadpictest;
import java.io.File;
import java.net.URL;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.Keep;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
private ValueCallback<Uri> mUploadMessage;
private WebView mWebView;
private ValueCallback<Uri[]> mValueCallback;
private int selectImgMax = 1;//选取图片最大数量
private int photosType = 0;//图库类型
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mWebView = (WebView) findViewById(R.id.web_view);
WebSettings webSettings = mWebView.getSettings();
mWebView.setWebChromeClient(new MyWebChromeClient());
// 设置WebView属性,能够执行Javascript脚本
assert mWebView != null;
webSettings.setJavaScriptEnabled(true);
// 设置可以访问文件
webSettings.setAllowFileAccess(true);
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
mWebView.setWebViewClient(new MyWebViewClient(this));
mWebView.loadUrl("https://www.sojump.hk/m/12005519.aspx");
}
private class MyWebViewClient extends WebViewClient{
private Context mContext;
public MyWebViewClient(Context context){
super();
mContext = context;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.d(TAG,"URL地址:" + url);
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
Log.i(TAG, "onPageFinished");
super.onPageFinished(view, url);
}
}
public static final int FILECHOOSER_RESULTCODE = 1;
private static final int REQ_CAMERA = FILECHOOSER_RESULTCODE+1;
private class MyWebChromeClient extends WebChromeClient {
// For Android < 3.0
@Keep
public void openFileChooser(ValueCallback<Uri> valueCallback) {
mUploadMessage = valueCallback;
goToPhotos();
}
// For Android >= 3.0
@Keep
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
mUploadMessage = valueCallback;
goToPhotos();
}
//For Android >= 4.1
@Keep
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
mUploadMessage = valueCallback;
goToPhotos();
}
//5.0
@Override
public boolean onShowFileChooser(WebView webView,
ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams) {
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
}
mValueCallback = filePathCallback;
goToPhotos();
return true;
}
}
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"), FILECHOOSER_RESULTCODE);
}
/**
* 进入本地图库
*/
private void goToPhotos() {
Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT); // "android.intent.action.GET_CONTENT"
String IMAGE_UNSPECIFIED = "image/*";
innerIntent.setType(IMAGE_UNSPECIFIED); // 查看类型
Intent wrapperIntent = Intent.createChooser(innerIntent, null);
startActivityForResult(wrapperIntent, FILECHOOSER_RESULTCODE);
}
/**
* 返回文件选择
*/
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == FILECHOOSER_RESULTCODE) {
uploadImgFromSysPhotos(resultCode, intent);
}
}
/**
* 上传图片,调用系统图库 与h5 file标签交互
*
* @param resultCode
* @param intent
* @author andYa
*/
private void uploadImgFromSysPhotos(int resultCode, Intent intent) {
if (mUploadMessage != null) {//5.0以下
Uri result = intent == null || resultCode != RESULT_OK ? null
: intent.getData();
if(result != null){
mUploadMessage.onReceiveValue(Uri.parse(getImageAbsolutePath(this,result)));
}else if(resultCode == RESULT_CANCELED){
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = null;
} else if (mValueCallback != null) {//5.0+
Uri[] uris = new Uri[1];
uris[0] = intent == null || resultCode != RESULT_OK ? null
: intent.getData();
if (uris[0]!=null){
mValueCallback.onReceiveValue(uris);
}else if(resultCode == RESULT_CANCELED){
mValueCallback.onReceiveValue(null);
}
mValueCallback = null;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
if (requestCode != FILECHOOSER_RESULTCODE || mValueCallback == 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)};
}
}
mValueCallback.onReceiveValue(results);
mValueCallback = null;
}
@TargetApi(19)
public static String getImageAbsolutePath(Activity context, Uri imageUri) {
if (context == null || imageUri == null)
return null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, imageUri)) {
if (isExternalStorageDocument(imageUri)) {
String docId = DocumentsContract.getDocumentId(imageUri);
String[] split = docId.split(":");
String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
} else if (isDownloadsDocument(imageUri)) {
String id = DocumentsContract.getDocumentId(imageUri);
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
} else if (isMediaDocument(imageUri)) {
String docId = DocumentsContract.getDocumentId(imageUri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection, selectionArgs);
}
} // MediaStore (and general)
else if ("content".equalsIgnoreCase(imageUri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(imageUri))
return imageUri.getLastPathSegment();
return getDataColumn(context, imageUri, null, null);
}
// File
else if ("file".equalsIgnoreCase(imageUri.getScheme())) {
return imageUri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
String column = MediaStore.Images.Media.DATA;
String[] projection = { column };
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int 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.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}
转载请注明出处,谢谢。