By default, webViews on Android do not support uploading files. And this, also in our front end engineer informed after understanding. Because each version of Android WebView implementation is different, so need to adapt to different versions. After a little time and reference to other people’s code, the problem has been solved. Here are the potholes I stepped on.

The main idea is to override the WebChromeClient and then receive the selected file Uri in the WebViewActivity and pass it to the page to upload.

Create an inner class for WebViewActivity

Public class XHSWebChromeClient extends WebChromeClient {// For Android 3.0+ public void openFileChooser(ValueCallback) 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); } // For Android 3.0+ public void openFileChooser(ValueCallback uploadMsg, String acceptType) {CLog. "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); } For Android 4.1 public void openFileChooser(ValueCallback 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); } //Android 5.0+ @override @SuppressLint("NewApi") public Boolean onShowFileChooser(WebView WebView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { if (mUploadMessage ! = null) { mUploadMessage.onReceiveValue(null); } CLog i. (" UPFILE ", "the file chooser params:" + fileChooserParams. The 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; }}Copy the code

The above openFileChooser is an unexposed interface of the system, so there is no need to add the annotation of Override. Meanwhile, different versions have different parameters. The first parameter, ValueCallback, is used to receive the file callback to the webpage after we select the file. AcceptType indicates the MIME type of the accepted file. After Android 5.0, onShowFileChooser was provided to allow us to implement the file selection method, which still has ValueCallback, and also includes acceptType in the FileChooserParams parameter. We can open the system’s file selector according to acceptType or we can create our own file selector. Of course, if you want to open the camera to take a photo, you can also use the Intent to open the camera to take a photo.

Process the selected file

The above is to open the response to select the file interface, we also need to deal with the receipt of the file, to the web page to respond. Because we opened the selection page using startActivityForResult, we will receive the selection result in onActivityResult. Show code:

@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;
        }
        CLog.i("UPFILE", "onActivityResult" + result.toString());
        String path =  FileUtils.getPath(this, result);
        if (TextUtils.isEmpty(path)) {
            mUploadMessage.onReceiveValue(null);
            mUploadMessage = null;
            return;
        }
        Uri uri = Uri.fromFile(new File(path));
        CLog.i("UPFILE", "onActivityResult after parser uri:" + uri.toString());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mUploadMessage.onReceiveValue(new Uri[]{uri});
        } else {
            mUploadMessage.onReceiveValue(uri);
        }

        mUploadMessage = null;
    }
}Copy the code

The above code basically calls ValueCallback’s onReceiveValue method and sends the result back to the Web.

Notice what else is important

For Android versions 5.0 or later, The onReceiveValue of ValueCallback receives a Uri. For Android versions 5.0 or later, the onReceiveValue of ValueCallback receives a Uri array.

Select a file using a component provided by the system or other supported app. The uri returned is either the URL of the file directly or the URI of the ContentProvider. Therefore, we need to unify the processing and convert it into the URI of the file.

Calling getPath converts the Uri to the Path of the real file, and you can then generate the Uri of the file yourself

public class FileUtils { /** * @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()); } /** * Get the value of the data column for this Uri. This is useful for * MediaStore Uris, and other file-based ContentProviders. * * @param context The context. * @param uri The Uri to query. * @param selection  (Optional) Filter used in the query. * @param selectionArgs (Optional) Selection arguments used in the query. * @return  The value of the _data column, which is typically a file path. */ 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; } /** * Get a file path from a Uri. This will get the the path for Storage Access * Framework Documents, as well as the _data field for the MediaStore and * other file-based ContentProviders. * * @param context The context. *  @param uri The Uri to query. * @author paulburke */ @SuppressLint("NewApi") public static String getPath(final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; // DocumentProvider if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider 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]; } // TODO handle non-primary volumes } // DownloadsProvider 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); } // MediaProvider 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); } } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { return getDataColumn(context, uri, null, null); } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; }}Copy the code

Again, even if the obtained results is null, also want to the web, namely direct call mUploadMessage. OnReceiveValue (null), or web pages will be blocked.

And finally, when we’re releasing the release package, because we’re going to get confused, we’re going to set it up specifically not to confuse the openFileChooser method in the WebChromeClient subclass, because it’s not an inherited method, so it’s going to get confused by default, and we’re not going to be able to select the file.

That’s it.

The original address: blog. Isming. Me / 2015/12/21 /… Please indicate the source.