Android WebView那些坑之上傳文件

2021-02-13 Android編程精選

作者丨張磊 

來源丨BaronTalk(BaronTalk)

最近公司項目需要在WebView上調用手機系統相冊來上傳圖片,開發過程中發現在很多機器上無法正常喚起系統相冊來選擇圖片。
解決問題之前我們先來說說WebView上傳文件的邏輯:當我們在Web頁面上點擊選擇文件的控制項(<input type="file">)時,會回調WebChromeClient下的openFileChooser()(5.0及以上系統回調onShowFileChooser())。這個時候我們在openFileChooser方法中通過Intent打開系統相冊或者支持該Intent的第三方應用來選擇圖片。like this:

public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
   uploadMessage = valueCallback;
      openImageChooserActivity();
}

private void openImageChooserActivity() {
   Intent i = new Intent(Intent.ACTION_GET_CONTENT);
   i.addCategory(Intent.CATEGORY_OPENABLE);
   i.setType("image/*");
   startActivityForResult(Intent.createChooser(i,
               "Image Chooser"), FILE_CHOOSER_RESULT_CODE);
}

最後我們在onActivityResult()中將選擇的圖片內容通過ValueCallback的onReceiveValue方法返回給WebView,然後通過js上傳。代碼如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);
   if (requestCode == FILE_CHOOSER_RESULT_CODE) {
       Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
       if (uploadMessage != null) {
           uploadMessage.onReceiveValue(result);
           uploadMessage = null;
       }
   }
}

PS:ValueCallbacks是WebView組件通過openFileChooser()或者onShowFileChooser()提供給我們的,它裡面包含了一個或者一組Uri,然後我們在onActivityResult()裡將Uri傳給ValueCallbacks的onReceiveValue()方法,這樣WebView就知道我們選擇了什麼文件。到這裡你可能要問了,說了這麼多還是沒解釋為什麼在很多機型上無法喚起系統相冊或者第三方app來選擇圖片啊?!這是因為為了最求完美的Google攻城獅們對openFileChooser做了多次修改,在5.0上更是將回調方法該為了onShowFileChooser。所以為了解決這一問題,兼容各個版本,我們需要對openFileChooser()進行重載,同時針對5.0及以上系統提供onShowFileChooser()方法:

webview.setWebChromeClient(new WebChromeClient() {

       // For Android < 3.0
       public void openFileChooser(ValueCallback<Uri> valueCallback) {
           ***
       }

       // For Android  >= 3.0
       public void openFileChooser(ValueCallback valueCallback, String acceptType) {
           ***
       }

       //For Android  >= 4.1
       public void openFileChooser(ValueCallback<Uri> valueCallback,
               String acceptType, String capture) {
           ***
       }

       // For Android >= 5.0
       @Override
       public boolean onShowFileChooser(WebView webView,
               ValueCallback<Uri[]> filePathCallback,
               WebChromeClient.FileChooserParams fileChooserParams) {
           ***
           return true;
       }
   });

大家應該注意到onShowFileChooser()中的ValueCallback包含了一組Uri(Uri[]),所以針對5.0及以上系統,我們還需要對onActivityResult()做一點點處理。這裡不做描述,最後我再貼上完整代碼。當處理完這些後你以為就萬事大吉了?!當初我也這樣天真,但當我們打好release包測試的時候卻又發現沒法選擇圖片了!!!真是坑了個爹啊!!!無奈去翻WebChromeClient的源碼,發現openFileChooser()是系統API,我們的release包是開啟了混淆的,所以在打包的時候混淆了openFileChooser(),這就導致無法回調openFileChooser()了。

/**
* 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()就好了。

-keepclassmembers class * extends android.webkit.WebChromeClient{
       public void openFileChooser(...);
}

支持關於上傳文件的所有坑都填完了,最後附上完整源碼:
源碼地址丨https://github.com/BaronZ88/WebViewSample

public class MainActivity extends AppCompatActivity {

   private ValueCallback<Uri> uploadMessage;
   private ValueCallback<Uri[]> uploadMessageAboveL;
   private final static int FILE_CHOOSER_RESULT_CODE = 10000;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       WebView webview = (WebView) findViewById(R.id.web_view);
       assert webview != null;
       WebSettings settings = webview.getSettings();
       settings.setUseWideViewPort(true);
       settings.setLoadWithOverviewMode(true);
       settings.setJavaScriptEnabled(true);
       webview.setWebChromeClient(new WebChromeClient() {

           // For Android < 3.0
           public void openFileChooser(ValueCallback<Uri> valueCallback) {
               uploadMessage = valueCallback;
               openImageChooserActivity();
           }

           // For Android  >= 3.0
           public void openFileChooser(ValueCallback valueCallback, String acceptType) {
               uploadMessage = valueCallback;
               openImageChooserActivity();
           }

           //For Android  >= 4.1
           public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
               uploadMessage = valueCallback;
               openImageChooserActivity();
           }

           // For Android >= 5.0
           @Override
           public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
               uploadMessageAboveL = filePathCallback;
               openImageChooserActivity();
               return true;
           }
       });
       String targetUrl = "file:///android_asset/up.html";
       webview.loadUrl(targetUrl);
   }

   private void openImageChooserActivity() {
       Intent i = new Intent(Intent.ACTION_GET_CONTENT);
       i.addCategory(Intent.CATEGORY_OPENABLE);
       i.setType("image/*");
       startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE);
   }

   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
       super.onActivityResult(requestCode, resultCode, data);
       if (requestCode == FILE_CHOOSER_RESULT_CODE) {
           if (null == uploadMessage && null == uploadMessageAboveL) return;
           Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
           if (uploadMessageAboveL != null) {
               onActivityResultAboveL(requestCode, resultCode, data);
           } else if (uploadMessage != null) {
               uploadMessage.onReceiveValue(result);
               uploadMessage = null;
           }
       }
   }

   @TargetApi(Build.VERSION_CODES.LOLLIPOP)
   private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
       if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)
           return;
       Uri[] results = null;
       if (resultCode == Activity.RESULT_OK) {
           if (intent != null) {
               String dataString = intent.getDataString();
               ClipData clipData = intent.getClipData();
               if (clipData != null) {
                   results = new Uri[clipData.getItemCount()];
                   for (int i = 0; i < clipData.getItemCount(); i++) {
                       ClipData.Item item = clipData.getItemAt(i);
                       results[i] = item.getUri();
                   }
               }
               if (dataString != null)
                   results = new Uri[]{Uri.parse(dataString)};
           }
       }
       uploadMessageAboveL.onReceiveValue(results);
       uploadMessageAboveL = null;
   }
}

https://github.com/BaronZ88/WebViewSample

在看點這裡好文分享給更多人↓↓

相關焦點

  • Android Webview使用和遇到過的坑總結
    INTERNET權限:<uses-permission android:name="android.permission.INTERNET" />        默認情況下,WebView不支持JavaScript,web頁面的錯誤也會被忽略,如果只是用Webview來顯示網頁而不用交互,默認配置就可以了,如果需要交互,就需要自定義配置了。
  • 全面總結WebView遇到的坑及優化
    關於WebView,是開發過衝不可避免需要打交道的一個控制項,可以先通過下面這篇文章做一些了解:但是其存在很多坑,需要我們不斷的去發現、修復和優化。這篇文章講一下WebView遇到的那些坑,帶領各位爬坑。這裡如果有你沒遇到的問題,歡迎留言告訴我,我盡我所能幫你解決。
  • Android WebView 上傳文件支持全解析
    默認情況下情況下,使用Android的WebView是不能夠支持上傳文件的。而這個,也是在我們的前端工程師告知之後才了解的。因為Android的每個版本WebView的實現有差異,因此需要對不同版本去適配。花了一點時間,參考別人的代碼,這個問題已經解決,這裡把我踩過的坑分享出來。
  • WebView的坑別嫌多
    通過webview的loadUrl注意:該方式必須在webview加載完畢之後才能調用,也就是webviewClient的onPageFinished()方法回調之後,而且該方法的執行 會刷新界面,效率較低js代碼:function callJs(){
  • Android Webview知識點和遇到過的坑全總結
    正文        WebView 用來顯示網頁的一個View,它使用WebKit渲染引擎顯示web頁面,可以加載在線的或者本地的html頁面,WebView可以對頁面進行一系列操作,如歷史頁面的向前、向後,放大和縮小,執行文本搜索,與JS交互等等;在使用Webview時,請記得在AndroidManifest.xml文件中聲明
  • Android WebView 研究筆記
    :debuggerd0000000000000000: 00000002 00000000 00010000 0001 01 4640516 @webview_devtools_remote_136800000000000000000: 00000002 00000000 00010000 0001 01 17596 @jdwp-control0000000000000000
  • Android混合開發,html5自動更新爬過的坑
    最近公司讓用h5混合開發,一些頁面和功能有h5分擔,最初時候放在本地assets文件夾下,後來由於前端同事頻繁修改和更新,再加上數據安全方面考慮,決定把包放在伺服器,採用接口返回欄位,判斷是否需要下載更新,接下來就談下開發中遇到的幾個坑,給自己做個筆記,也分享給需要的朋友。
  • Android 使用騰訊X5內核, Webview瀏覽器拍照或從相冊上傳圖片
    正參加2016博客之星,點擊原文支持一票!閱讀文章需要幾分鐘,不妨早上聽聽歌 開啟新的一天!Go!最近在項目開發中,需要使用WebView上傳文件。默認情況下情況下,使用Android的WebView是不能夠支持上傳文件的。經過查找資料,得知需要重新WebChromeClient,根據選擇到的文件Uri,傳給頁面去上傳就可以了。
  • Android WebView簡單整理
    記得第一次用webview的時候,就會個loadUrl,最近想著自己寫個簡單瀏覽器玩玩,發現了一些問題,於是寫一篇文章,文章分兩大類,一類為使用
  • Android中使用WebView與JS交互全解析
    編寫布局文件activity_main.xml布局的內容很簡單,就是嵌套一個WebView控制項; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.webkit.JavascriptInterface
  • App自動化測試 | Android WebView測試
    WebView測試環境準備手機端PC端查看手機瀏覽器版本adb shell pm list package | grep webviewadb shell pm dump com.android.browser
  • Android WebView和JS交互詳細教程
    Android webview和JS的交互已經是老生常談了,坑很多、問題也很多。
  • Android爬坑之旅之WebView
    的介紹實在太過詳細,像我這樣的懶人,有更好的文章是不會自己去寫的,所以刪了自己寫的,將大牛博主的博客分享出來,感興趣同學的可以一起看一看:Android WebView 開發詳解(一) Android WebView 開發詳解(二) Android WebView 開發詳解(三)了解完WebView的基本用法,那就來總結下最近項目中遇到的關於WebView的坑
  • 用安卓 WebView 做一個「套殼」應用
    -- 展示一個 WebView --><WebView    android:id="@+id/webview"    android:layout_width="match_parent"    android:layout_height="match_parent" />在 Android 工程中,「app
  • 實戰 | 封裝解決WebView的那些坑
    Justson的博客地址:http://www.jianshu.com/u/a52f305fac1cWebView 是 Android 最複雜以及最強大的一個控制項(最多坑) , 一大堆的 setting 讓人摸不著頭腦 , 很多時候壓根不知道這個設置有什麼用 ,加上 WebViewClient 和 WebChromeClient 做為內部類 , 一堆業務邏輯
  • 你真的了解webview麼?
    另外,還有一些網絡機頂盒裡的交互,也是webview在和我們打交道,比如一些早期的IPTV裡的EPG都是運行在webview裡的,它們基於webkit內核,儘管我們使用的交互方式是遙控器。當然,今天我們會從native的角度切入,帶大家認識真正的webview。
  • Android如何獲取WebView內容高度
    ><com.trs.studyview.view.TRSScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/scrollView"
  • WebView詳解與簡單實現Android與H5互調
    WebView常用方法加載界面,其次還有 LoadData 和 LoadDataWithBase 方法//加載assets目錄下的test.html文件webView.loadUrl("file:///android_asset/test.html");//加載網絡資源(注意要加上網絡權限)webView.loadUrl
  • [譯]使用開發工具來調試 Beta 版 WebView
    您可以使用這個工具查看崩潰報告是否已經上傳到我們的伺服器,如果有必要強制上傳它,然後提交一個 bug。這確保了我們的團隊擁有快速解決這些故障所需的所有信息,並確保在您的應用程式中有一個更流暢的用戶體驗。
  • WebView 全面乾貨指南
    ="match_parent"><com.webview.SafeWebView    android:id="@+id/web_view"    android:layout_width="match_parent"    android:layout_height="match_parent"/></LinearLayout>