WebView 上傳文件 WebChromeClient之openFileChooser函數


原鏈接:http://blog.saymagic.cn/2015/11/08/webview-upload.html?utm_source=tuicool&utm_medium=referral

 

從零開始

我們在xml中寫入一個簡單的Webview組件:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. tools:context=".MainActivity">
  5. <WebView
  6. android:id="@+id/webview"
  7. android:layout_width="fill_parent"
  8. android:layout_height="fill_parent"
  9. android:layout_margin="5dp"></WebView>
  10. </RelativeLayout>

然后在Java代碼中使用其加載一個能夠提供上傳服務的URL:

  1. WebView webview = (WebView) findViewById(R.id.webview);
  2. webview.loadUrl(A_UPLOAD_URL);

之后,要加網絡權限:

  1. <uses-permission android:name="android.permission.INTERNET"></uses-permission>

如果想讓Webview能夠訪問本地資源,SD卡的讀寫權限也是避免不了的:

  1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

最后,我們運行,會發現根本不能訪問本地資源。Why?

讓我們來填補第一個坑:

支持上傳文件

Webview執行上傳操作的邏輯是這樣的:首先准備上傳時會回調WebChromeClient類下的openFileChooser方法,在這個方法中給我們機會發起Intent來打開支持提供文件的第三方應用,最后在onActivityResult回調中將第三方應用提供的內容通過一個叫做ValueCallback的參數返回給Webview(詳細點來說:ValueCallback是在openFileChooser 方法里由webview提供給我們的,里面包裹一個Uri,我們在onActivityResult 里將選中的Uri反饋給ValueCallback,這時候相當於Webview就知道我們選擇了什么文件),因此,我們需要為Webview設置一個提供openFileChooser方法的WebChromeClient,這個方法在不同版本的Android中參數是不同的,為此我們一般需要寫三個重載函數,大致像這個樣子:

  1. private ValueCallback<Uri> mUploadMessage;
  2. //設置`WebChromeClient`:
  3. webview.setWebChromeClient(new WebChromeClient(){
  4. public void openFileChooser(ValueCallback<Uri> uploadMsg) {
  5. Log.d(TAG, "openFileChoose(ValueCallback<Uri> uploadMsg)");
  6. mUploadMessage = uploadMsg;
  7. Intent i = new Intent(Intent.ACTION_GET_CONTENT);
  8. i.addCategory(Intent.CATEGORY_OPENABLE);
  9. i.setType("*/*");
  10. MainActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
  11. }
  12. public void openFileChooser( ValueCallback uploadMsg, String acceptType ) {
  13. Log.d(TAG, "openFileChoose( ValueCallback uploadMsg, String acceptType )");
  14. mUploadMessage = uploadMsg;
  15. Intent i = new Intent(Intent.ACTION_GET_CONTENT);
  16. i.addCategory(Intent.CATEGORY_OPENABLE);
  17. i.setType("*/*");
  18. MainActivity.this.startActivityForResult(
  19. Intent.createChooser(i, "File Browser"),
  20. FILECHOOSER_RESULTCODE);
  21. }
  22. public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){
  23. Log.d(TAG, "openFileChoose(ValueCallback<Uri> uploadMsg, String acceptType, String capture)");
  24. mUploadMessage = uploadMsg;
  25. Intent i = new Intent(Intent.ACTION_GET_CONTENT);
  26. i.addCategory(Intent.CATEGORY_OPENABLE);
  27. i.setType("*/*");
  28. MainActivity.this.startActivityForResult( Intent.createChooser( i, "File Browser" ), MainActivity.FILECHOOSER_RESULTCODE );
  29. }
  30. });
  31. //onActivityResult回調
  32. @Override
  33. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  34. super.onActivityResult(requestCode, resultCode, data);
  35. if(requestCode==FILECHOOSER_RESULTCODE)
  36. {
  37. if (null == mUploadMessage && null == mUploadCallbackAboveL) return;
  38. Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
  39. if (mUploadMessage != null) {
  40. mUploadMessage.onReceiveValue(result);
  41. mUploadMessage = null;
  42. }
  43. }
  44. }

還有重要的一點:如果這個上傳操作涉及到JS操作,別忘記對Webview開啟對JS的支持:

  1. WebSettings settings = webview.getSettings();
  2. settings.setJavaScriptEnabled(true);

這樣,打個debug包測試看以下,不出意外我們的Webview應該可以支持上傳操作了。

別高興得太早,如果這個時候產品要將release包推向市場,當你把release包交給產品時,你會發現你的Webview又不能上傳了,什么情況?

請聽Webview上傳操作的第二個坑。

支持release版

debug版是好的,為什么release就不行了呢?准確的說,開啟了混淆的release包是不可以的,究其原因在於,openFileChooser 方法並不是WebChromeClient 的對外開放的方法,因此這個方法會被混淆,解決辦法也比較簡單,只需要在混淆文件里控制一下即可:

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

好了,我們的Webview可以作為應用內的一個部分對外發布了,等等,有5.0以上用戶反映用不了?納尼????

別回心,來看看這第三個坑。

支持5.0

在5.0發布后,Android人家說了,這次我們回調的不是openFileChooser方法,而是onShowFileChooser方法,並且上文提到的ValueCallback參數里包裹着不再是Uri,而是Uri數組,因此我們必須為5.0+的機器做適配,大致思路如下:

  1. webview.setWebChromeClient(new WebChromeClient(){
  2. public void openFileChooser(ValueCallback<Uri> uploadMsg) {
  3. ...
  4. }
  5. public void openFileChooser( ValueCallback uploadMsg, String acceptType ) {
  6. ...
  7. }
  8. public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){
  9. ...
  10. }
  11. // For Android 5.0+
  12. public boolean onShowFileChooser (WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
  13. mUploadCallbackAboveL = filePathCallback;
  14. Intent i = new Intent(Intent.ACTION_GET_CONTENT);
  15. i.addCategory(Intent.CATEGORY_OPENABLE);
  16. i.setType("*/*");
  17. MainActivity.this.startActivityForResult(
  18. Intent.createChooser(i, "File Browser"),
  19. FILECHOOSER_RESULTCODE);
  20. return true;
  21. }
  22. });
  23. @Override
  24. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  25. super.onActivityResult(requestCode, resultCode, data);
  26. if(requestCode==FILECHOOSER_RESULTCODE)
  27. {
  28. if (null == mUploadMessage && null == mUploadCallbackAboveL) return;
  29. Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
  30. if (mUploadCallbackAboveL != null) {
  31. onActivityResultAboveL(requestCode, resultCode, data);
  32. }
  33. else if (mUploadMessage != null) {
  34. mUploadMessage.onReceiveValue(result);
  35. mUploadMessage = null;
  36. }
  37. }
  38. }
  39. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  40. private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) {
  41. if (requestCode != FILECHOOSER_RESULTCODE
  42. || mUploadCallbackAboveL == null) {
  43. return;
  44. }
  45. Uri[] results = null;
  46. if (resultCode == Activity.RESULT_OK) {
  47. if (data == null) {
  48. } else {
  49. String dataString = data.getDataString();
  50. ClipData clipData = data.getClipData();
  51. if (clipData != null) {
  52. results = new Uri[clipData.getItemCount()];
  53. for (int i = 0; i < clipData.getItemCount(); i++) {
  54. ClipData.Item item = clipData.getItemAt(i);
  55. results[i] = item.getUri();
  56. }
  57. }
  58. if (dataString != null)
  59. results = new Uri[]{Uri.parse(dataString)};
  60. }
  61. }
  62. mUploadCallbackAboveL.onReceiveValue(results);
  63. mUploadCallbackAboveL = null;
  64. return;
  65. }

如上,我們的Webview應該就可以適應5.0+的機器了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM