1. 程式人生 > >WebView上傳檔案遇到的坑openFileChooser

WebView上傳檔案遇到的坑openFileChooser

最近公司專案碰到WebView上傳圖片的問題,在這方面糾結了挺長時間,把解決問題的思路記錄一下。

至於 WebView 需要的配置方面這裡就不說了,自行百度。

首先 WebView 要重寫 WebViewClient 的 shouldOverrideUrlLoading 方法,讓連結直接
在APP內部開啟而不是跳轉到系統瀏覽器或者是第三方瀏覽器:

 webview.setWebViewClient(new WebViewClient(){
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        return
super.shouldOverrideUrlLoading(view, url); } });

上面這個還不是遇到的問題,真正的問題就在於在 WebView 的連結裡通過按鈕開啟手機相簿,
但是安卓這麼多版本又不會自動相容,這就需要我們自己去做相容。上網瞭解到在安卓5.0版本
以後SDK中開放的方法是 onShowFileChooser ,而在較低版本中是 openFileChooser 。然後在
API21(由於專案引用23的SDK,此方法被隱藏,不太清楚是不是21)之後 openFileCHooser 就
被 Google 隱藏掉了。

/**
     * Tell the
client to open a file chooser. * @param uploadFile A ValueCallback to set the URI of the file to upload. * onReceiveValue must be called to wake up the thread.a * @param acceptType The value of the 'accept' attribute of the input tag * associated with this file picker. * @param capture The value of
the 'capture' attribute of the input tag * associated with this file picker. * * @deprecated Use {@link #showFileChooser} instead. * @hide This method was not published in any SDK version. */ @SystemApi @Deprecated public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) { uploadFile.onReceiveValue(null); }

從上面程式碼可以看出此方法被隱藏了,所以正常情況下這個方法是無法呼叫的。
但是如果非要用被隱藏的方法,那被隱藏的方法怎麼才能引用呢?

第一種方法 可以利用反射機制。《這種方法自行百度搜索》

下面說說我在專案中用到的方法:

因為 openFileChooser 方法被隱藏 ,所以在 WebChromeClient 中重寫的時候就沒有此方法。
於是我便自己寫一個類去繼承 WebChromeClient 。在類中寫相容各個版本的 openFileChooser
方法。程式碼如下:

    private ValueCallback<Uri> mUploadMessage;
    private ValueCallback<Uri[]> uploadMessage; // 用於5.0以上
    private static final int FILECHOOSER_RESULTCODE = 1;
    public static final int REQUEST_SELECT_FILE = 100;


public class MyWebChromeClient extends WebChromeClient {
        /**
         * {@inheritDoc}
         * 安卓3.0-
         */
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            openFileChooser(uploadMsg, "");
        }

        /**
         * {@inheritDoc}
         * 安卓3.0 - 4.0
         */
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
            mUploadMessage = uploadMsg;
            Intent i = null;
            if (Build.VERSION.SDK_INT < 19) {
                i = new Intent(Intent.ACTION_GET_CONTENT);
                i.setType("image/*");
            } else {
                i = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
            }
            startActivityForResult(Intent.createChooser(i, "選擇圖片"), FILECHOOSER_RESULTCODE);
        }

        /**
         * {@inheritDoc}
         * 安卓 4.0 - 5.0
         */
        protected void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
            openFileChooser(uploadMsg, acceptType);
        }

        /**
        *  安卓5.0+
        */
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
            if (uploadMessage != null) {
                uploadMessage.onReceiveValue(null);
                uploadMessage = null;
            }

            uploadMessage = filePathCallback;

            Intent intent = fileChooserParams.createIntent();
            try {
                startActivityForResult(Intent.createChooser(intent, "選擇圖片"), REQUEST_SELECT_FILE);
            } catch (ActivityNotFoundException e) {
                uploadMessage = null;
                Toast.makeText(getApplicationContext(), "檔案上傳失敗", Toast.LENGTH_LONG).show();
                return false;
            }
            return true;
        }
    }

然後需要重寫 onActivityResult 方法接收返回的資料即可:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (requestCode == REQUEST_SELECT_FILE) {
                if (uploadMessage == null)
                    return;
                Uri result = (data == null || resultCode != RESULT_OK) ? null : data.getData();
                if (result != null) {
                    uploadMessage.onReceiveValue(new Uri[]{result});
                } else {
                    uploadMessage.onReceiveValue(new Uri[]{});
                }
                uploadMessage = null; //每次置空是為了防止下次點選按鈕選擇圖片無響應
            }
        } else if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage)
                return;
            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
            mUploadMessage.onReceiveValue(result); //注意此處
            mUploadMessage = null;
        } else
            Toast.makeText(getApplicationContext(), "上傳檔案失敗", Toast.LENGTH_LONG).show();

        return;
    }

到這裡工作就完成了90%,什麼?到這裡還沒完成?

是的,在部分機型上上傳圖片之後,但是在WebView中顯示圖片縮圖確實TXT文件型別。
這是為什麼?我個人理解認為是5.0以下與5.0以上圖片返回的Uri格式不一致造成的。

預覽為TXT格式

一時間就把我難住了,嘗試了各種方法,包括安卓不同版本系統開啟相簿的方式都無效。
突然大神附體似的就想到了會不會和返回 Uri 的格式不同有關?選擇的圖片肯定都是有
自己的路徑的,我可不可以獲取到圖片的路徑呢?

同時又發現有一個 Uri.fromFile() 的方法,而又可以將圖片的路徑轉換成File型別。於是
拿起就是幹馬上嘗試。

於是有了以下程式碼:

// 根據圖片的Uri得到圖片的路徑
private String getImagePath(Uri originalUri) {
        String[] pro = {MediaStore.Images.Media.DATA};
        Cursor cursor = getContentResolver().query(originalUri, pro, null, null, null);
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        String path = cursor.getString(column_index);
        return path;
    }

然後在onActivityResult中呼叫,完整程式碼:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (requestCode == REQUEST_SELECT_FILE) {
                if (uploadMessage == null)
                    return;
                Uri result = (data == null || resultCode != RESULT_OK) ? null : data.getData();
                if (result != null) {
                    uploadMessage.onReceiveValue(new Uri[]{result});
                } else {
                    uploadMessage.onReceiveValue(new Uri[]{});
                }
                uploadMessage = null;
            }
        } else if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage)
                return;
            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
            Uri uri = Uri.fromFile(new File(getImagePath(result)));
            mUploadMessage.onReceiveValue(uri); // 轉換後
            mUploadMessage = null;
        } else
            Toast.makeText(getApplicationContext(), "檔案上傳失敗", Toast.LENGTH_LONG).show();

        return;
    }

正如上面,因為mUploadMessage.onReceiveValue(uri)中需要接收一個Uri型別的引數
因此又需要將圖片路徑轉換成Uri。這樣就奇蹟般的起效了。然後就能正常的顯示圖片。

**

以上純屬個人理解,如有錯誤歡迎指出,一起共同學習。

**