WebView上傳檔案的深坑與研究

影響身邊的人發表於2016-12-22

WebView上傳檔案的深坑與研究

直接切入主題了

最近公司專案裡線上使用者反饋出一個bug,介入的第三方平臺中有個新增圖片的功能,當點選H5中的按鈕的時候,調不起本地檔案管理器,在很多手機上都出現這種情況

原因如下

刨除定製化的webview來說,原生webview是支援上傳檔案的。但是眾多版本的迭代擴充套件,api引數也不一樣。一般拿到上傳檔案的需求時,大家都會照搬android brower的程式碼(聰明),api如下:

        // Android > 4.1.1 呼叫這個方法
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
            LogUtils.d("openFileChooser 4.1.1 = ");
        }

        // 3.0 + 呼叫這個方法
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
            LogUtils.d("openFileChooser 3.0 = ");
        }

        // Android < 3.0 呼叫這個方法
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            LogUtils.d("openFileChooser < 3.0 = ");
        }複製程式碼

之前專案裡的程式碼當然也是這樣寫的,後來還懷疑過是否需要和前端聯調,後來查了一下發現了第一個坑:

5.0之後,系統提供了onShowFileChooser來讓我們實現選擇檔案的方法。

看了Sam的文章知道了這點:Sam


之後,本以為沒有問題了,測試的時候發現,坑又來了。

我們可以看出,在這個功能上,有很明顯的api升級痕跡,在試過眾多手機之後,發現,4.4.0到4.4.2的系統無法呼叫這個api,當然也無從上傳檔案了,是怎麼回事?

在這篇部落格找到了詳細的答案:穿衣助手技術部落格

原來google在4.4更改webkit核心為chromium之後,把這個api刪除了!一般升級api,都會保留對原有api的支援,@deprecated掉舊的api,加入新的api,但是google卻2個都沒做,開發團隊解釋,我們正在開發一個新的共有的api,這個api會更好,我們會在正式版本上推出。結果呢,在4.4.3版本,他又把這個api加回來了

於是怎麼解決呢,網路上大多都放棄或是沒有解決方案了,太過於麻煩,有這麼三個方式:
參考這個,不過並沒有給出具體的程式碼,只有思路,並且只有第三個思路是較靠譜的

  • 第一種,H5直接使用新的video標籤通過獲取navigator的getUserMedia來獲取視訊流stream中擷取一張圖的方式來實現,不過這種方式在mobile上的支援比較晚。其中,android是從Android5.0才開始支援,ios未知。所以這種實現思路不做考慮。

  • 第二種,H5直接用以前Html舊有的input標籤來實現。其中這種方式在ios支援上還不錯,在android上的支援則不怎麼令人滿意。因為它直接涉及到了webkit在android各個平臺不同的實現方式,有很大的風險性。但是,出於方便讓ios能直接呼叫的原因這個方式的可行性還是很大的。當然,問題並非不能解決,這個下面再講。

  • 第三種,H5直接利用js和本地進行互動來實現。這種方式的採用很成功的例子就是微信公眾賬號的實現,它通過實現一套js庫來支援網頁的各種呼叫

關於第三種方式的解決方案如下:

點我下載

用js呼叫的本地方法如下:

 final class ModuleJavaScriptInterface {

        ModuleJavaScriptInterface() {
        }

        @JavascriptInterface
        public void finishWebview(String json) {
            if (!TextUtils.isEmpty(json)) {
                Toast.makeText(BrowserActivity.this, "json= " + json, Toast.LENGTH_SHORT).show();
            }
        }

        @JavascriptInterface
        public void uploadImage() {
            Toast.makeText(BrowserActivity.this, "upload ", Toast.LENGTH_SHORT).show();


            //開啟相簿
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), KITKAT_RESULTCODE);

            /*
            在onActivityResult中  KITKAT_RESULTCODE

            1 上傳圖片
            2 上傳成功後 將伺服器返回的URL 返回給 js  : UploadedFileName


            String UploadedFileName = "";
            mWebView.loadUrl("javascript:CheckImage('" + UploadedFileName + "')");
             */

        }
    }複製程式碼

當然還有一個小坑

之前專案裡面已經處理好了,所以並沒有顯現出來,如下:

呼叫系統app選擇檔案的時候,若彈出選擇框,cancel掉選擇框之後,發現webview無響應了,無法重新整理,載入,點選,甚至退出這個activity也無法載入!後果很嚴重~
原因是當你選擇上傳檔案的時候,webview的ValueCallback物件(就是選擇圖片的回撥)會持有這個webview,在沒有收到回撥之前,你無法對這個webview做任何的操作!
知道原因之後,就很好解決了,如果cancel了,那麼直接呼叫該物件的onReceiveValue()方法,傳入null即可,webview就可以正常操作了


最後別忘了取消混淆的問題呦


最後給出剩下的程式碼事例:

public class SafeWebViewClient extends WebViewClient {  
    @Override  
    public void onProgressChanged(WebView view, int newProgress) {  
        super.onProgressChanged(view, newProgress);  
        activity.mWebLoadingProgressBar.setProgress(newProgress);  
        if (newProgress >= 90 && activity.mWebLoadingProgressBar.getVisibility() == View.VISIBLE) {  
            activity.mWebLoadingProgressBar.setVisibility(View.GONE);  
        }  
    }  

    // For Android < 3.0
    public void openFileChooser(ValueCallback<Uri> uploadMsg) {  

        activity.mUploadMessage = uploadMsg;  
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
        i.addCategory(Intent.CATEGORY_OPENABLE);  
        i.setType("image/*");  
        activity.startActivityForResult(Intent.createChooser(i, "File Chooser"), activity.FILECHOOSER_RESULTCODE);  

    }  

    // For Android 3.0+  
    public void openFileChooser(ValueCallback uploadMsg, String acceptType) {  
        activity.mUploadMessage = uploadMsg;  
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
        i.addCategory(Intent.CATEGORY_OPENABLE);  
        i.setType("*/*");  
        activity.startActivityForResult(Intent.createChooser(i, "File Browser"), activity.FILECHOOSER_RESULTCODE);  
    }  

    //For Android 4.1  
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {  
        activity.mUploadMessage = uploadMsg;  
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
        i.addCategory(Intent.CATEGORY_OPENABLE);  
        i.setType("image/*");  
        activity.startActivityForResult(Intent.createChooser(i, "File Chooser"), activity.FILECHOOSER_RESULTCODE);  

    }  

    //For Android 5.0  
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)  
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {  
        // make sure there is no existing message  
        if (activity.uploadMessage != null) {  
            activity.uploadMessage.onReceiveValue(null);  
            activity.uploadMessage = null;  
        }  
        activity.uploadMessage = filePathCallback;  
        Intent intent = fileChooserParams.createIntent();  
        try {  
            activity.startActivityForResult(intent, activity.REQUEST_SELECT_FILE);  
        } catch (ActivityNotFoundException e) {  
            activity.uploadMessage = null;  
            return false;  
        }  
        return true;  
    }  

}複製程式碼

選中圖片之後走:

public void onActivityResult(int requestCode, int resultCode, Intent intent) {  
    if (requestCode == FILECHOOSER_RESULTCODE) {  
        if (null == mUploadMessage) return;  
        Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();  
        mUploadMessage.onReceiveValue(result);  
        mUploadMessage = null;  
    } else if (requestCode == REQUEST_SELECT_FILE) {  
        if (uploadMessage == null) return;  
        uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));  
        uploadMessage = null;  
    }複製程式碼

Thanks && END

相關文章