最近錢旺宣布成立QBike,準備進軍單車俱樂部,將挑戰膜拜,OFO單車! 雖然新產業一直產生,可能走滴滴模式。開發行情還是不太樂觀,只能敲敲敲!歡迎一葉飄舟加入本公眾號陣營!正參加2016博客之星,點擊原文支持一票!
閱讀文章需要幾分鐘,不妨早上聽聽歌 開啟新的一天!Go!
最近在項目開發中,需要使用WebView上傳文件。默認情況下情況下,使用Android的WebView是不能夠支持上傳文件的。經過查找資料,得知需要重新WebChromeClient,根據選擇到的文件Uri,傳給頁面去上傳就可以了。
自定義WebChromeClient
先在WebViewActivity裡面自定義MyWebChromeClient,代碼如下:
public class MyWebChromeClient extends WebChromeClient { public void openFileChooser(ValueCallback<Uri> uploadMsg) {
CLog.i("UPFILE", "in openFile Uri Callback"); if (mUploadMessage != null) { mUploadMessage.onReceiveValue(null); } mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); startActivityForResult(
Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE); }
public void openFileChooser(ValueCallback uploadMsg,
String acceptType) {
CLog.i("UPFILE", "in openFile Uri Callback has accept Type" + acceptType);
if (mUploadMessage != null) { mUploadMessage.onReceiveValue(null); } mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE);
String type = TextUtils.isEmpty(acceptType) ? "*/*" : acceptType; i.setType(type); startActivityForResult(Intent.createChooser(i, "File Chooser"),
FILECHOOSER_RESULTCODE); } public void openFileChooser(ValueCallback<Uri> uploadMsg,
String acceptType, String capture) {
CLog.i("UPFILE", "in openFile Uri Callback has accept Type" + acceptType + "has capture" + capture);
if (mUploadMessage != null) { mUploadMessage.onReceiveValue(null); } mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE);
String type = TextUtils.isEmpty(acceptType) ? "*/*" : acceptType; i.setType(type); startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE); } @Override @SuppressLint("NewApi") public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
if (mUploadMessage != null) { mUploadMessage.onReceiveValue(null); }
CLog.i("UPFILE", "file chooser params:" + fileChooserParams.toString()); mUploadMessage = filePathCallback; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE);
if (fileChooserParams != null && fileChooserParams.getAcceptTypes() != null && fileChooserParams.getAcceptTypes().length > 0) { i.setType(fileChooserParams.getAcceptTypes()[0]); } else { i.setType("*/*"); } startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE); return true; }}
上面openFileChooser是系統未暴露的接口,因此不需要加Override的註解,同時不同版本有不同的參數,其中的參數,第一個ValueCallback用於我們在選擇完文件後,接收文件回調到網頁內處理,acceptType為接受的文件mime type。在Android 5.0之後,系統提供了onShowFileChooser來讓我們實現選擇文件的方法,仍然有ValueCallback,在FileChooserParams參數中,同樣包括acceptType。我們可以根據acceptType,來打開系統的或者我們自己創建文件選擇器。當然如果需要打開相機拍照,也可以自己去使用打開相機拍照的Intent去打開即可。
處理選擇的文件因為我們前面是使用startActivityForResult來打開的選擇頁面,我們會在onActivityResult中接收到選擇的結果。代碼如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == mUploadMessage)
return; Uri result = data == null || resultCode != RESULT_OK ? null : data.getData(); if (result == null) { mUploadMessage.onReceiveValue(null); mUploadMessage = null;
return; } String path = FileUtils.getPath(this, result);
if (TextUtils.isEmpty(path)) { mUploadMessage.onReceiveValue(null); mUploadMessage = null;
return; } Uri uri = Uri.fromFile(new File(path));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mUploadMessage.onReceiveValue(new Uri[]{uri}); } else { mUploadMessage.onReceiveValue(uri); } mUploadMessage = null; }}
注意事項:
1 由於不同版本的差別,Android 5.0以下的版本,ValueCallback 的onReceiveValue接收的參數類型是Uri, 5.0及以上版本接收的是Uri數組,在傳值的時候需要注意。
2 選擇文件會使用系統提供的組件或者其他支持的app,返回的uri有的直接是文件的url,有的是contentprovider的uri,因此我們需要統一處理一下,轉成文件的uri,可參考以下代碼(獲取文件的路徑)。
3 <font color="red">即使獲取的結果為null,也要傳給webview,即直接調用mUploadMessage.onReceiveValue(null),否則網頁會阻塞。</font>
4 在打release包的時候,因為我們會混淆,要特別設置不要混淆WebChromeClient子類裡面的openFileChooser方法,由於不是繼承的方法,所以默認會被混淆,然後就無法選擇文件了。
FileUtils工具類如下:
public class FileUtils { public static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority()); }
public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null;
final String column = "_data";
final String[] projection = { column };
try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) { final int column_index = cursor.getColumnIndexOrThrow(column); return cursor.getString(column_index); } } finally { if (cursor != null) cursor.close(); } return null; } @SuppressLint("NewApi")
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1]; }
}
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null); } else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":"); final 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; }
final String selection = "_id=?";
final String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection, selectionArgs); } } else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null); }
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath(); }
return null; }}
看了上面的代碼,你是不是感覺有點複雜呢?下面我們將介紹怎麼通過使用騰訊X5 Webview瀏覽器實現拍照或從相冊上傳圖片功能。
使用騰訊X5 Webview瀏覽器TBS騰訊瀏覽器服務官網:http://x5.tencent.comjar包下載:http://x5.tencent.com/doc?id=1004
集成教程:
http://www.jianshu.com/p/8a7224ff371a
http://blog.csdn.net/qq_17387361/article/details/52396338
http://www.jianshu.com/p/e4009688119b
環境調好後,我們就可以愉快的開始調試了。
public class MyWebChromeClient extends WebChromeClient { @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback, FileChooserParams fileChooserParams) { TLog.error("onShowFileChooser"); return super.onShowFileChooser(webView, valueCallback, fileChooserParams); } public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { mUploadMessage = uploadMsg; choosePicture(); } public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { mUploadMessage = uploadMsg; choosePicture(); } public void openFileChooser(ValueCallback<Uri> uploadMsg) { mUploadMessage = uploadMsg; choosePicture(); }}
這裡選擇圖片使用了三方圖片選擇組件:PhotoPicker,項目地址:https://github.com/donglua/PhotoPicker
其中choosePicture方法如下
private void choosePicture() { PhotoPicker.builder() .setPhotoCount(1) .setShowCamera(true) .setShowGif(true) .setPreviewEnabled(false) .start(TBSWebActivity.this, PhotoPicker.REQUEST_CODE); }
在onActivityResult中接收到選擇的結果,處理如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) { if (null == mUploadMessage) { return; }
if (resultCode == RESULT_OK && requestCode == PhotoPicker.REQUEST_CODE) { ArrayList<String> photos =
intent.getStringArrayListExtra(PhotoPicker.KEY_SELECTED_PHOTOS); Uri result = Uri.parse(photos.get(0)); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } else { mUploadMessage.onReceiveValue(null); } }
這裡值得強調的是,即使獲取的結果為null(比如按back鍵取消了),也要傳給webview,即直接調用mUploadMessage.onReceiveValue(null),否則網頁會阻塞。
H5前端最後簡單再說一下H5前端調用。
<div class="btn1">上傳照片 <input type="file" accept="image/*" id="uploadImage" capture="camera" onchange="selectFileImage(this);"> </div>
上傳相關的js代碼由於微信字數限制請點擊原文查看。
http://blog.csdn.net/jdsjlzx/article/details/53546260
---我是分割線---