WebView上傳文件的深坑與研究

直接切入主題了

最近公司項目裏線上用戶反饋出一個bug,介入的第三方平臺中有個添加圖片的功能,當點擊H5中的按鈕的時候,調不起本地文件管理器,在不少手機上都出現這種狀況javascript

緣由以下

刨除定製化的webview來講,原生webview是支持上傳文件的。可是衆多版本的迭代擴展,api參數也不同。通常拿到上傳文件的需求時,你們都會照搬android brower的代碼(聰明),api以下:html

// 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的文章知道了這點:Samjava


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

咱們能夠看出,在這個功能上,有很明顯的api升級痕跡,在試過衆多手機以後,發現,4.4.0到4.4.2的系統沒法調用這個api,固然也無從上傳文件了,是怎麼回事?ios

在這篇博客找到了詳細的答案:穿衣助手技術博客web

原來google在4.4更改webkit內核爲chromium以後,把這個api刪除了!通常升級api,都會保留對原有api的支持,@deprecated掉舊的api,加入新的api,可是google卻2個都沒作,開發團隊解釋,咱們正在開發一個新的共有的api,這個api會更好,咱們會在正式版本上推出。結果呢,在4.4.3版本,他又把這個api加回來了json

因而怎麼解決呢,網絡上大多都放棄或是沒有解決方案了,太過於麻煩,有這麼三個方式:
參考這個,不過並無給出具體的代碼,只有思路,而且只有第三個思路是較靠譜的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

相關文章
相關標籤/搜索