SoFunction
Updated on 2025-03-11

Android WebView upload files support full parsing

By default, using Android WebView cannot support uploading files. And this was only known after our front-end engineers informed us. Because the implementation of each version of Android WebView is different, it needs to be adapted to different versions. I spent a little time referring to other people's code. This problem has been solved. Here I share the pitfalls I have struck.
The main idea is to rewrite the WebChromeClient, and then receive the selected file Uri in the WebViewActivity and pass it to the page to upload it.
Create an internal class of WebViewActivity

public class XHSWebChromeClient extends WebChromeClient {

  // For Android 3.0+
  public void openFileChooser(ValueCallback<Uri> uploadMsg) {
    ("UPFILE", "in openFile Uri Callback");
    if (mUploadMessage != null) {
      (null);
    }
    mUploadMessage = uploadMsg;
    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
    (Intent.CATEGORY_OPENABLE);
    ("*/*");
    startActivityForResult((i, "File Chooser"), FILECHOOSER_RESULTCODE);
  }

  // For Android 3.0+
  public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
    ("UPFILE", "in openFile Uri Callback has accept Type" + acceptType);
    if (mUploadMessage != null) {
      (null);
    }
    mUploadMessage = uploadMsg;
    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
    (Intent.CATEGORY_OPENABLE);
    String type = (acceptType) ? "*/*" : acceptType;
    (type);
    startActivityForResult((i, "File Chooser"),
        FILECHOOSER_RESULTCODE);
  }

  // For Android 4.1
  public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
    ("UPFILE", "in openFile Uri Callback has accept Type" + acceptType + "has capture" + capture);
    if (mUploadMessage != null) {
      (null);
    }
    mUploadMessage = uploadMsg;
    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
    (Intent.CATEGORY_OPENABLE);
    String type = (acceptType) ? "*/*" : acceptType;
    (type);
    startActivityForResult((i, "File Chooser"), FILECHOOSER_RESULTCODE);
  }


//Android 5.0+
  @Override
  @SuppressLint("NewApi")
  public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
    if (mUploadMessage != null) {
      (null);
    }
    ("UPFILE", "file chooser params:" + ());
    mUploadMessage = filePathCallback;
    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
    (Intent.CATEGORY_OPENABLE);
    if (fileChooserParams != null && () != null
        && ().length > 0) {
      (()[0]);
    } else {
      ("*/*");
    }
    startActivityForResult((i, "File Chooser"), FILECHOOSER_RESULTCODE);
    return true;
  }
}

The above openFileChooser is an interface that is not exposed by the system, so there is no need to add Override annotation. At the same time, different versions have different parameters. The first ValueCallback is used to receive the file callback to the webpage after we select the file, and acceptType is the accepted file mime type. After Android 5.0, the system provides onShowFileChooser to enable us to implement the method of selecting files, and there is still ValueCallback. In the FileChooserParams parameter, acceptType is also included. We can open the system according to acceptType or create a file selector ourselves. Of course, if you need to turn on the camera to take pictures, you can also use the Intent to turn on the camera to open it.
Process the selected file
The above is the interface for opening the response selection file. We also need to process the response after receiving the file and pass it to the web page. Because we are using startActivityForResult to open the selection page, we will receive the selection result in the onActivityResult. Show code:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  (requestCode, resultCode, data);
  if (requestCode == FILECHOOSER_RESULTCODE) {
    if (null == mUploadMessage) return;
    Uri result = data == null || resultCode != RESULT_OK ? null : ();
    if (result == null) {
      (null);
      mUploadMessage = null;
      return;
    }
    ("UPFILE", "onActivityResult" + ());
    String path = (this, result);
    if ((path)) {
      (null);
      mUploadMessage = null;
      return;
    }
    Uri uri = (new File(path));
    ("UPFILE", "onActivityResult after parser uri:" + ());
    if (.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      (new Uri[]{uri});
    } else {
      (uri);
    }

    mUploadMessage = null;
  }
}

The above code mainly calls the onReceiveValue method of ValueCallback and passes the result back to the web.
Notice, what else to say is important
Due to the differences between different versions, for Android versions below 5.0, the parameter type received by ValueCallback is Uri, and the Uri array is received by version 5.0 and above. You need to pay attention to when passing values.
When selecting a file, you will use the components provided by the system or other supported apps. Some of the returned uri are directly the url of the file, and some are the uri of the contentprovider. Therefore, we need to process it uniformly. If you convert it to the uri of the file, you can refer to the following code (get the file path).
Calling getPath can convert Uri to the Path of the real file, and then you can generate the Uri of the file by yourself.

public class FileUtils {
  /**
   * @param uri The Uri to check.
   * @return Whether the Uri authority is ExternalStorageProvider.
   */
  public static boolean isExternalStorageDocument(Uri uri) {
    return "".equals(());
  }

  /**
   * @param uri The Uri to check.
   * @return Whether the Uri authority is DownloadsProvider.
   */
  public static boolean isDownloadsDocument(Uri uri) {
    return "".equals(());
  }

  /**
   * @param uri The Uri to check.
   * @return Whether the Uri authority is MediaProvider.
   */
  public static boolean isMediaDocument(Uri uri) {
    return "".equals(());
  }

  /**
   * 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 = ().query(uri, projection, selection, selectionArgs,
          null);
      if (cursor != null && ()) {
        final int column_index = (column);
        return (column_index);
      }
    } finally {
      if (cursor != null)
        ();
    }
    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 = .SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && (context, uri)) {
      // ExternalStorageProvider
      if (isExternalStorageDocument(uri)) {
        final String docId = (uri);
        final String[] split = (":");
        final String type = split[0];

        if ("primary".equalsIgnoreCase(type)) {
          return () + "/" + split[1];
        }

        // TODO handle non-primary volumes
      }
      // DownloadsProvider
      else if (isDownloadsDocument(uri)) {

        final String id = (uri);
        final Uri contentUri = (
            ("content://downloads/public_downloads"), (id));

        return getDataColumn(context, contentUri, null, null);
      }
      // MediaProvider
      else if (isMediaDocument(uri)) {
        final String docId = (uri);
        final String[] split = (":");
        final String type = split[0];

        Uri contentUri = null;
        if ("image".equals(type)) {
          contentUri = .EXTERNAL_CONTENT_URI;
        } else if ("video".equals(type)) {
          contentUri = .EXTERNAL_CONTENT_URI;
        } else if ("audio".equals(type)) {
          contentUri = .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(())) {
      return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(())) {
      return ();
    }

    return null;
  }

}

Furthermore, even if the result obtained is null, it must be passed to the web, that is, it is called directly (null), otherwise the web page will be blocked.
Finally, when we are typing the release package, we will be confused, so we should set it not to confuse the openFileChooser method in the WebChromeClient subclass. Since it is not an inherited method, it will be obfuscated by default, and then the file cannot be selected.
That's it.
Original address:/2015/12/21/android-webview-upload-file/

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.