欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android WebView 上传文件支持全解析

程序员文章站 2024-03-04 17:07:05
默认情况下情况下,使用android的webview是不能够支持上传文件的。而这个,也是在我们的前端工程师告知之后才了解的。因为android的每个版本webview的实现...

默认情况下情况下,使用android的webview是不能够支持上传文件的。而这个,也是在我们的前端工程师告知之后才了解的。因为android的每个版本webview的实现有差异,因此需要对不同版本去适配。花了一点时间,参考别人的代码,这个问题已经解决,这里把我踩过的坑分享出来。
主要思路是重写webchromeclient,然后在webviewactivity中接收选择到的文件uri,传给页面去上传就可以了。
创建一个webviewactivity的内部类

public class xhswebchromeclient extends webchromeclient {

  // for android 3.0+
  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);
  }

  // for android 3.0+
  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);
  }

  // for android 4.1
  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);
  }


//android 5.0+
  @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中接收到选择的结果。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;
  }
}

以上代码主要就是调用valuecallback的onreceivevalue方法,将结果传回web。
注意,其他要说的,重要
由于不同版本的差别,android 5.0以下的版本,valuecallback 的onreceivevalue接收的参数类型是uri, 5.0及以上版本接收的是uri数组,在传值的时候需要注意。
选择文件会使用系统提供的组件或者其他支持的app,返回的uri有的直接是文件的url,有的是contentprovider的uri,因此我们需要统一处理一下,转成文件的uri,可参考以下代码(获取文件的路径)。
调用getpath可以将uri转成真实文件的path,然后可以自己生成文件的uri

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;
  }

}

再有,即使获取的结果为null,也要传给web,即直接调用muploadmessage.onreceivevalue(null),否则网页会阻塞。
最后,在打release包的时候,因为我们会混淆,要特别设置不要混淆webchromeclient子类里面的openfilechooser方法,由于不是继承的方法,所以默认会被混淆,然后就无法选择文件了。
就这样吧。
原文地址:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。