問題描述

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