問題描述

我在 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 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇