问题描述

我在 android webview 上使用<input type="file"> 。我得到它的工作感谢这个线程:File Upload in WebView

但接受的答案 (或任何其他) 不再适用于 android 4.4 kitkat webview 。

有谁知道如何解决?

它也不适用于目标 18 。

我已经看了一些 android 4.4 源代码,似乎 WebChromeClient 没有改变,但我认为 setWebChromeClient 不再适用于 kitkat webview,或至少不是 openFileChooser 功能。

最佳解决方案

更新 2:使用 phonegap /cordova 有一个更简单的插件

https://github.com/MaginSoft/MFileChooser

更新:Cesidio DiBenedetto 插件的示例项目

https://github.com/jcesarmobile/FileBrowserAndroidTest

我在 android 开源项目上开了一个 issue,答案是:

Status: WorkingAsIntended

unfortunately, openFileChooser is not a public API. We are working on a public API in future releases of Android.

对于那些使用 phonegap /cordova,这个解决方法发布在错误跟踪器上:

Cesidio DiBenedetto added a comment – 28/Mar/14 01:27

Hey all, I’ve been experiencing this issue as well so I wrote a Cordova FileChooser plugin to a “band-aid” for the time being. Basically, in Android 4.4(KitKat), as mentioned in previous comments, the file dialog is not opened. However the onclick event is still fired on so you can call the FileChooser plugin to open a file dialog and upon selection, you can set a variable that contains the full path to the file. At this point, you can use the FileTransfer plugin to upload to your server and hook into the onprogress event to show progress. This plugin is mainly configured for Android 4.4 so I would recommend to continue to use the native file dialogs for earlier versions of Android. There might be issues with the plugin as I have not fully tested all possible scenarios on many devices, but I have installed it on a Nexus 5 and it worked fine.

https://github.com/cdibened/filechooser

没有测试,因为我建立了我自己的解决方法

chrome 开发商的评论

We will be a adding a public API to WebViewClient in next major release to handle file requests.

看来他们现在认为它是一个 bug,他们会修复它

次佳解决方案

我设法在我的应用程序中实现了所提到的 Cesidio DiBenedetto’s workaround 。这是非常有用的,但是对于以前从未使用过 PhoneGap/Cordove 的人 (像我一样) 可能会有点棘手。所以这里有一点我在实施的时候放在一起。

Apache Cordova 是一个平台,可让您使用网络技术构建多平台移动应用。关键的特点是将本机 API 导出为 JavaScript,从而提供了一种在本地应用程序之间进行通信的方式。典型的 PhoneGap /Cordova 应用程序是一个静态网站,它与一个 APK 中的 Cordova 层捆绑在一起。但是您可以使用 Cordova 显示远程网站,这就是我们的案例。

解决方法的工作方式如下:与标准 WebView 不同,我们使用 CordovaWebView 来显示我们的网站。当用户单击浏览以选择文件时,我们使用标准 JavaScript(jQuery …) 捕获该点击,并使用 Cordova API,我们激活 Cesidio DiBenedetto 的本机端的文件选择器插件,这将打开一个漂亮的文件浏览器。当用户选择一个文件时,该文件将被发送回我们上传到我们的 Web 服务器的 JavaScript 端。

要了解的重要事情是您需要向您的网站添加 Cordova 支持。好的,现在的实际如何…

首先,您必须将 Cordova 添加到您现有的应用程序中。我跟着 this documentation 。有些步骤对我来说不清楚,所以我会尝试解释一下:

  1. 在您的应用程序外部下载并解压缩 Cordova,并按照所述构建 cordova-3.4.0.jar 。由于 local.properties 文件丢失,可能会首次失败。您将被指示如何在错误输出中创建它; 您只需要指向用于构建 Android 应用程序的 SDK 。

  2. 将编译的 jar 文件复制到您的应用程序库目录中,并将 jar 作为库添加。如果您使用像我这样的 Android Studio,只需确保您在 dependencies 中的 compile fileTree(dir: 'libs', include: ['*.jar', '*.aar'])内部 build.gradle 。然后只需点击同步项目与毕业文件按钮,你会没事的。

  3. 您不必创建/res/xml/main.xml 文件。您可以将 CordovaWebView 视为与标准 WebView 相同的方式,以便将其直接放置到布局文件中。

  4. 现在只需按照原始文档中的步骤 5-7 将您自己的活动放在一起,即 CordobaWebView 将在其中运行。检查您下载的 Cordova 软件包中的/framework/src/org/apache/cordova/CordovaActivity.java 是个好主意。您可以简单地复制要实现的大多数方法。 6. 步骤对于我们的目的至关重要,因为它将使用 filechooser 插件。

  5. 不要在任何地方复制任何 HTML 和 JavaScript 文件,我们稍后将其添加到您的网站。

  6. 不要忘记复制 config.xml 文件 (您不必更改它) 。

要将您的网站加载到 CordovaWebView 中,只需将其网址传递给 cwv.loadUrl(),而不是 Config.getStartUrl()

其次,您必须将 FileChooser plugin 添加到您的应用程序。由于我们没有使用标准的 Cordova 设置,我们不能按照自述文件中的指示命中 cordova plugin add,我们必须手动添加。

  1. 下载存储库并将源文件复制到您的应用程序。确保 res 文件夹的内容到您的应用程序 res 文件夹。现在可以忽略 JavaScript 文件。

  2. READ_EXTERNAL_STORAGE 权限添加到您的应用程序。

  3. 将以下代码添加到/res/xml/config.xml 中:

<feature name="FileChooser">
    <param name="android-package" value="com.cesidiodibenedetto.filechooser.FileChooser" />
</feature>

现在是将 Cordova 支持添加到您的网站的时候了。它比听起来更简单,您只需将 cordova.js 链接到您的网站,但是有两件事要了解。

首先,每个平台 (Android,iOS,WP) 都有自己的 cordova.js,所以确保使用 Android 版本 (您可以在/framework /assets /www 中下载的 Cordova 软件包中找到它) 。

第二,如果您的网站将从 CordovaWebView 和标准浏览器 (台式机或移动设备) 访问,只有当页面显示在 CordovaWebView 中时,才能加载 cordova.js 。我发现几种方法来检测 CordovaWebView,但下面的一个是为我工作的。以下是您网站的完整代码:

function getAndroidVersion(ua) {
    var ua = ua || navigator.userAgent;
    var match = ua.match(/Androids([0-9.]*)/);
    return match ? parseFloat(match[1]) : false;
};

if (window._cordovaNative && getAndroidVersion() >= 4.4) {
    // We have to use this ugly way to get cordova working
    document.write('<script src="/js/cordova.js" type="text/javascript"></script>');
}

请注意,我们也在检查 Android 版本。此解决方法仅适用于 KitKat 。

此时您应该能够从您的网站手动调用 FileChooser 插件。

var cordova = window.PhoneGap || window.Cordova || window.cordova;
cordova.exec(function(data) {}, function(data) {}, 'FileChooser', 'open', [{}]);

这应该打开文件浏览器,让你选择一个文件。请注意,只有在触发事件设备后才能完成此操作。要测试它,只需使用 jQuery 将该代码绑定到某个按钮。

最后一步是把这一切放在一起,并获得上传表单的工作。要实现这一点,您可以简单地按照 “自述” 中描述的 Cesidio DiBenedetto 说明。当用户在 FileChooser 中选择文件时,文件路径将返回到 JavaScript 端,另一个 Cordova 插件 FileTransfer 用于执行实际上传。这意味着该文件是在本地上传的,而不是 CordovaWebView(如果我正确理解) 。

我不想添加另一个 Cordova 插件到我的应用程序,我也不知道如何使用 cookie(我需要发送 cookie 请求,因为只有验证用户被允许上传文件),所以我决定做我的方式我修改了 FileChooser 插件,因此它不会返回路径,而是返回整个文件。所以当用户选择一个文件时,我读取它的内容,使用 base64 进行编码,将其作为 JSON 传递到客户端,然后对其进行解码,并使用 JavaScript 将其发送到服务器。它的工作原理,但有一个明显的缺点,base64 是相当 CPU 要求,所以应用程序可能会冻结一点点,当大文件上传时。

要做到这一点,首先将此方法添加到 FileUtils 中:

public static byte[] readFile(final Context context, final Uri uri) throws IOException {
    File file = FileUtils.getFile(context, uri);
    return org.apache.commons.io.FileUtils.readFileToByteArray(file);
}

请注意,它使用 Apache Commons 库,所以不要忘记包含它或实现文件读取其他不需要外部库的方法。

接下来,修改 FileChooser.onActivityResult 方法来返回文件内容,而不是其路径:

// Get the URI of the selected file
final Uri uri = data.getData();
Log.i(TAG, "Uri = " + uri.toString());
JSONObject obj = new JSONObject();
try {
    obj.put("filepath", FileUtils.getPath(this.cordova.getActivity(), uri));
    obj.put("name", FileUtils.getFile(this.cordova.getActivity(), uri).getName());
    obj.put("type", FileUtils.getMimeType(this.cordova.getActivity(), uri));

    // attach the actual file content as base64 encoded string
    byte[] content = FileUtils.readFile(this.cordova.getActivity(), uri);
    String base64Content = Base64.encodeToString(content, Base64.DEFAULT);
    obj.put("content", base64Content);

    this.callbackContext.success(obj);
} catch (Exception e) {
    Log.e("FileChooser", "File select error", e);
    this.callbackContext.error(e.getMessage());
}

最后,这是您将在您的网站上使用的代码 (需要 jQuery):

var cordova = window.PhoneGap || window.Cordova || window.cordova;
if (cordova) {
    $('form.fileupload input[type="file"]', context).on("click", function(e) {
        cordova.exec(
            function(data) {
                var url = $('form.fileupload', context).attr("action");

                // decode file from base64 (remove traling = first and whitespaces)
                var content = atob(data.content.replace(/s/g, "").replace(/=+$/, ""));

                // convert string of bytes into actual byte array
                var byteNumbers = new Array(content.length);
                for (var i = 0; i < content.length; i++) {
                    byteNumbers[i] = content.charCodeAt(i);
                }
                var byteContent = new Uint8Array(byteNumbers);

                var formData = new FormData();
                var blob = new Blob([byteContent], {type: data.type});
                formData.append('file', blob, data.name);

                $.ajax({
                    url: url,
                    data: formData,
                    processData: false,
                    contentType: false,
                    type: 'POST',
                    success: function(data, statusText, xhr){
                        // do whatever you need
                    }
                });
            },
            function(data) {
                console.log(data);
                alert("error");
            },
            'FileChooser', 'open', [{}]);
    });
}

那就是这一切。花了我几个小时来完成这个工作,所以我以一种谦虚的希望分享我的知识,这可能有助于某人。

第三种解决方案

如果任何人仍然在寻找一个解决方案来使用在 kitkat 上的 webview 来输入文件。

openFileChooser 在 Android 4.4 https://code.google.com/p/android/issues/detail?id=62220 中点击时不调用

可以使用称为 Crosswalk 的 chrome 基库解决此 https://crosswalk-project.org/documentation/downloads.html

步骤 1. 将从上述链接下载的 xwalk_core_library android 项目导入作为库的项目 2. 在您的布局中,xml 添加以下内容

       <org.xwalk.core.XWalkView
            android:id="@+id/webpage_wv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
        />

3. 在您的活动的 onCreate 方法中,执行以下操作

mXwalkView = (XWalkView) context.findViewById(R.id.webpage_wv);
mXwalkView.setUIClient(new UIClient(mXwalkView));
mXwalkView.load(navigateUrl, null); //navigate url is your page URL
  1. 添加活动类变量 private ValueCallback mFilePathCallback; 私人 XWalkView mXwalkView

  2. 文件输入对话框现在应该显示出来。但是,您需要提供回调才能获取文件并将其发送到服务器。

  3. 您需要覆盖您的活动的 onActivityResult

    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
      super.onActivityResult(requestCode, resultCode, intent);
    
    if (mXwalkView != null) {
    
        if (mFilePathCallback != null) {
            Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
                    : intent.getData();
            if (result != null) {
                String path = MediaUtility.getPath(getActivity(), result);
                Uri uri = Uri.fromFile(new File(path));
                mFilePathCallback.onReceiveValue(uri);
            } else {
                mFilePathCallback.onReceiveValue(null);
            }
        }
    
        mFilePathCallback = null;
    }
    mXwalkView.onActivityResult(requestCode, resultCode, intent);
    
    }
    
  4. MediaUtility 类可以在 Get real path from URI, Android KitKat new storage access framework 中找到。请参阅 Paul Burke 的答案

  5. 要获取 mFilePathCallback 的数据对象,请在您的活动中创建一个子类

    class UIClient extends XWalkUIClient {
    public UIClient(XWalkView xwalkView) {
        super(xwalkView);
    }
    
    public void openFileChooser(XWalkView view,
            ValueCallback<Uri> uploadFile, String acceptType, String capture) {
        super.openFileChooser(view, uploadFile, acceptType, capture);
    
        mFilePathCallback = uploadFile;
        Log.d("fchooser", "Opened file chooser.");
    }
    

    }

  6. 你完成了 fileupload 现在应该可以工作了。不要忘记将 Crosswalk 所需的权限添加到清单中。 uses-permission 机器人:名字= “android.permission.ACCESS_FINE_LOCATION” uses-permission 机器人:名字= “android.permission.ACCESS_NETWORK_STATE” uses-permission 机器人:名字= “android.permission.ACCESS_WIFI_STATE” uses-permission 机器人:名字= “android.permission.CAMERA” uses-permission 机器人:名字= “android.permission.INTERNET” uses-permission 机器人:name =“android.permission.MODIFY_AUDIO_SETTINGS”uses-permission android:name =“android.permission.RECORD_AUDIO”uses-permission android:name = “android.permission.WAKE_LOCK” uses-permission android:name =“android.permission.WRITE_EXTERNAL_STORAGE”

第四种方案

文件选择器在 webview 中现在在最新的 Android 版本 4.4.3 中。

尝试使用 Nexus 5 自己。

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛