[Cordova] 改进InAppBrowser插件(WebView),让其<input type="file">支持选择文件

来源:互联网 发布:清华镜像python 编辑:程序博客网 时间:2024/05/07 05:03

原文链接:http://blog.csdn.net/lovelyelfpop/article/details/52815700



默认安卓的WebView显示的网页,其中的文件选择控件<input type="file">是不能选择文件的,点击弹不出文件或图片选择。

其实Google给WebView提供了接口,只要实现这些接口,就可以选择文件了。


搜索了很多,最终发现StackOverflow上的这个答案,是我找到最能满足需求的一个解决方案了。

http://stackoverflow.com/questions/5907369/file-upload-in-webview#24280517



1、支持几乎所有安卓版本,从安卓2.x~7.0

安卓4.4、4.4.1和4.4.2是无法支持的,因为当时Google说WebView上传文件不安全,就去掉了。所以这3个版本没有解决办法。不过现在基本没有这3个系统版本的设备了吧。

4.4.3和4.4.4是支持的


2、支持指定accept="",限制选择文件的类型

如果指定了accept="image/*",那么就会支持选择图片;如果不指定,那就可以选择任何文件。等等。


3、支持拍照上传

指定了accept="image/*",除了支持选择图片,还可以拍照



下面提供InAppBrowser插件的改进:

先上效果图(文件选择控件指定了accept="image/*")

下图是安卓4.4.4的效果

下图是安卓6.0的效果


下图是安卓7.0的效果



1、修改cordova-plugin-inappbrowser\src\Android\InAppChromeClient.java

增加成员变量

[java] view plain copy
  1. //added  
  2. private CordovaPlugin plugin;  
  3. public UploadHandler mUploadHandler;  
增加构造函数

[java] view plain copy
  1. //added  
  2. public InAppChromeClient(CordovaWebView webView, CordovaPlugin plugin) {  
  3.     super();  
  4.     this.webView = webView;  
  5.     this.plugin = plugin;  
  6. }  
增加成员函数和内部类

[java] view plain copy
  1. //added all below  
  2. // Android 2.x  
  3. public void openFileChooser(ValueCallback<Uri> uploadMsg) {  
  4.     openFileChooser(uploadMsg, "");  
  5. }  
  6.   
  7. // Android 3.0  
  8. public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {  
  9.     openFileChooser(uploadMsg, """filesystem");  
  10. }  
  11.   
  12. // Android 4.1  
  13. public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {  
  14.     mUploadHandler = new UploadHandler(new Controller());  
  15.     mUploadHandler.openFileChooser(uploadMsg, acceptType, capture);  
  16. }  
  17.   
  18. // Android 4.4, 4.4.1, 4.4.2  
  19. // openFileChooser function is not called on Android 4.4, 4.4.1, 4.4.2,  
  20. // you may use your own java script interface or other hybrid framework.  
  21.   
  22. // Android 5.0.1  
  23. @SuppressLint("NewApi")  
  24. public boolean onShowFileChooser(  
  25.         WebView webView, ValueCallback<Uri[]> filePathCallback,  
  26.         FileChooserParams fileChooserParams) {  
  27.   
  28.     String acceptTypes[] = fileChooserParams.getAcceptTypes();  
  29.   
  30.     String acceptType = "";  
  31.     for (int i = 0; i < acceptTypes.length; ++ i) {  
  32.         if (acceptTypes[i] != null && acceptTypes[i].length() != 0)  
  33.             acceptType += acceptTypes[i] + ";";  
  34.     }  
  35.     if (acceptType.length() == 0)  
  36.         acceptType = "*/*";  
  37.   
  38.     final ValueCallback<Uri[]> finalFilePathCallback = filePathCallback;  
  39.   
  40.     ValueCallback<Uri> vc = new ValueCallback<Uri>() {  
  41.   
  42.         @Override  
  43.         public void onReceiveValue(Uri value) {  
  44.   
  45.             Uri[] result;  
  46.             if (value != null)  
  47.                 result = new Uri[]{value};  
  48.             else  
  49.                 result = null;  
  50.   
  51.             finalFilePathCallback.onReceiveValue(result);  
  52.   
  53.         }  
  54.     };  
  55.   
  56.     openFileChooser(vc, acceptType, "filesystem");  
  57.   
  58.   
  59.     return true;  
  60. }  
  61.   
  62. public class Controller {  
  63.     final static int FILE_SELECTED = 4;  
  64.   
  65.     CordovaInterface getCordova(){ return plugin.cordova; }  
  66. }  
  67.   
  68. // copied from android-4.4.3_r1/src/com/android/browser/UploadHandler.java  
  69. //////////////////////////////////////////////////////////////////////  
  70.   
  71. /*  
  72.  * Copyright (C) 2010 The Android Open Source Project  
  73.  *  
  74.  * Licensed under the Apache License, Version 2.0 (the "License");  
  75.  * you may not use this file except in compliance with the License.  
  76.  * You may obtain a copy of the License at  
  77.  *  
  78.  *      http://www.apache.org/licenses/LICENSE-2.0  
  79.  *  
  80.  * Unless required by applicable law or agreed to in writing, software  
  81.  * distributed under the License is distributed on an "AS IS" BASIS,  
  82.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  83.  * See the License for the specific language governing permissions and  
  84.  * limitations under the License.  
  85.  */  
  86. class UploadHandler {  
  87.     /* 
  88.      * The Object used to inform the WebView of the file to upload. 
  89.      */  
  90.     private ValueCallback<Uri> mUploadMessage;  
  91.     private String mCameraFilePath;  
  92.     private boolean mHandled;  
  93.     private boolean mCaughtActivityNotFoundException;  
  94.     private Controller mController;  
  95.     public UploadHandler(Controller controller) {  
  96.         mController = controller;  
  97.     }  
  98.     String getFilePath() {  
  99.         return mCameraFilePath;  
  100.     }  
  101.     boolean handled() {  
  102.         return mHandled;  
  103.     }  
  104.     void onResult(int resultCode, Intent intent) {  
  105.         if (resultCode == Activity.RESULT_CANCELED && mCaughtActivityNotFoundException) {  
  106.             // Couldn't resolve an activity, we are going to try again so skip  
  107.             // this result.  
  108.             mCaughtActivityNotFoundException = false;  
  109.             return;  
  110.         }  
  111.         Uri result = intent == null || resultCode != Activity.RESULT_OK ? null  
  112.                 : intent.getData();  
  113.         // As we ask the camera to save the result of the user taking  
  114.         // a picture, the camera application does not return anything other  
  115.         // than RESULT_OK. So we need to check whether the file we expected  
  116.         // was written to disk in the in the case that we  
  117.         // did not get an intent returned but did get a RESULT_OK. If it was,  
  118.         // we assume that this result has came back from the camera.  
  119.         if (result == null && intent == null && resultCode == Activity.RESULT_OK) {  
  120.             File cameraFile = new File(mCameraFilePath);  
  121.             if (cameraFile.exists()) {  
  122.                 result = Uri.fromFile(cameraFile);  
  123.                 // Broadcast to the media scanner that we have a new photo  
  124.                 // so it will be added into the gallery for the user.  
  125.                 mController.getCordova().getActivity().sendBroadcast(  
  126.                         new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));  
  127.             }  
  128.         }  
  129.         mUploadMessage.onReceiveValue(result);  
  130.         mHandled = true;  
  131.         mCaughtActivityNotFoundException = false;  
  132.     }  
  133.     void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {  
  134.         final String imageMimeType = "image/*";  
  135.         final String videoMimeType = "video/*";  
  136.         final String audioMimeType = "audio/*";  
  137.         final String mediaSourceKey = "capture";  
  138.         final String mediaSourceValueCamera = "camera";  
  139.         final String mediaSourceValueFileSystem = "filesystem";  
  140.         final String mediaSourceValueCamcorder = "camcorder";  
  141.         final String mediaSourceValueMicrophone = "microphone";  
  142.         // According to the spec, media source can be 'filesystem' or 'camera' or 'camcorder'  
  143.         // or 'microphone' and the default value should be 'filesystem'.  
  144.         String mediaSource = mediaSourceValueFileSystem;  
  145.         if (mUploadMessage != null) {  
  146.             // Already a file picker operation in progress.  
  147.             return;  
  148.         }  
  149.         mUploadMessage = uploadMsg;  
  150.         // Parse the accept type.  
  151.         String params[] = acceptType.split(";");  
  152.         String mimeType = params[0];  
  153.         if (capture.length() > 0) {  
  154.             mediaSource = capture;  
  155.         }  
  156.         if (capture.equals(mediaSourceValueFileSystem)) {  
  157.             // To maintain backwards compatibility with the previous implementation  
  158.             // of the media capture API, if the value of the 'capture' attribute is  
  159.             // "filesystem", we should examine the accept-type for a MIME type that  
  160.             // may specify a different capture value.  
  161.             for (String p : params) {  
  162.                 String[] keyValue = p.split("=");  
  163.                 if (keyValue.length == 2) {  
  164.                     // Process key=value parameters.  
  165.                     if (mediaSourceKey.equals(keyValue[0])) {  
  166.                         mediaSource = keyValue[1];  
  167.                     }  
  168.                 }  
  169.             }  
  170.         }  
  171.         //Ensure it is not still set from a previous upload.  
  172.         mCameraFilePath = null;  
  173.         if (mimeType.equals(imageMimeType)) {  
  174.             if (mediaSource.equals(mediaSourceValueCamera)) {  
  175.                 // Specified 'image/*' and requested the camera, so go ahead and launch the  
  176.                 // camera directly.  
  177.                 startActivity(createCameraIntent());  
  178.                 return;  
  179.             } else {  
  180.                 // Specified just 'image/*', capture=filesystem, or an invalid capture parameter.  
  181.                 // In all these cases we show a traditional picker filetered on accept type  
  182.                 // so launch an intent for both the Camera and image/* OPENABLE.  
  183.                 Intent chooser = createChooserIntent(createCameraIntent());  
  184.                 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(imageMimeType));  
  185.                 startActivity(chooser);  
  186.                 return;  
  187.             }  
  188.         } else if (mimeType.equals(videoMimeType)) {  
  189.             if (mediaSource.equals(mediaSourceValueCamcorder)) {  
  190.                 // Specified 'video/*' and requested the camcorder, so go ahead and launch the  
  191.                 // camcorder directly.  
  192.                 startActivity(createCamcorderIntent());  
  193.                 return;  
  194.             } else {  
  195.                 // Specified just 'video/*', capture=filesystem or an invalid capture parameter.  
  196.                 // In all these cases we show an intent for the traditional file picker, filtered  
  197.                 // on accept type so launch an intent for both camcorder and video/* OPENABLE.  
  198.                 Intent chooser = createChooserIntent(createCamcorderIntent());  
  199.                 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(videoMimeType));  
  200.                 startActivity(chooser);  
  201.                 return;  
  202.             }  
  203.         } else if (mimeType.equals(audioMimeType)) {  
  204.             if (mediaSource.equals(mediaSourceValueMicrophone)) {  
  205.                 // Specified 'audio/*' and requested microphone, so go ahead and launch the sound  
  206.                 // recorder.  
  207.                 startActivity(createSoundRecorderIntent());  
  208.                 return;  
  209.             } else {  
  210.                 // Specified just 'audio/*',  capture=filesystem of an invalid capture parameter.  
  211.                 // In all these cases so go ahead and launch an intent for both the sound  
  212.                 // recorder and audio/* OPENABLE.  
  213.                 Intent chooser = createChooserIntent(createSoundRecorderIntent());  
  214.                 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(audioMimeType));  
  215.                 startActivity(chooser);  
  216.                 return;  
  217.             }  
  218.         }  
  219.         // No special handling based on the accept type was necessary, so trigger the default  
  220.         // file upload chooser.  
  221.         startActivity(createDefaultOpenableIntent());  
  222.     }  
  223.     private void startActivity(Intent intent) {  
  224.         try {  
  225.             mController.getCordova().startActivityForResult(plugin, intent, Controller.FILE_SELECTED);  
  226.         } catch (ActivityNotFoundException e) {  
  227.             // No installed app was able to handle the intent that  
  228.             // we sent, so fallback to the default file upload control.  
  229.             try {  
  230.                 mCaughtActivityNotFoundException = true;  
  231.                 mController.getCordova().startActivityForResult(plugin, createDefaultOpenableIntent(),  
  232.                         Controller.FILE_SELECTED);  
  233.             } catch (ActivityNotFoundException e2) {  
  234.                 // Nothing can return us a file, so file upload is effectively disabled.  
  235.                 Toast.makeText(mController.getCordova().getActivity(), "File uploads are disabled.",  
  236.                         Toast.LENGTH_LONG).show();  
  237.             }  
  238.         }  
  239.     }  
  240.     private Intent createDefaultOpenableIntent() {  
  241.         // Create and return a chooser with the default OPENABLE  
  242.         // actions including the camera, camcorder and sound  
  243.         // recorder where available.  
  244.         Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
  245.         i.addCategory(Intent.CATEGORY_OPENABLE);  
  246.         i.setType("*/*");  
  247.         Intent chooser = createChooserIntent(createCameraIntent(), createCamcorderIntent(),  
  248.                 createSoundRecorderIntent());  
  249.         chooser.putExtra(Intent.EXTRA_INTENT, i);  
  250.         return chooser;  
  251.     }  
  252.     private Intent createChooserIntent(Intent... intents) {  
  253.         Intent chooser = new Intent(Intent.ACTION_CHOOSER);  
  254.         chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents);  
  255.         chooser.putExtra(Intent.EXTRA_TITLE,  
  256.                 "Choose file for upload");  
  257.         return chooser;  
  258.     }  
  259.     private Intent createOpenableIntent(String type) {  
  260.         Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
  261.         i.addCategory(Intent.CATEGORY_OPENABLE);  
  262.         i.setType(type);  
  263.         return i;  
  264.     }  
  265.     private Intent createCameraIntent() {  
  266.         Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);  
  267.         File externalDataDir = Environment.getExternalStoragePublicDirectory(  
  268.                 Environment.DIRECTORY_DCIM);  
  269.         File cameraDataDir = new File(externalDataDir.getAbsolutePath() +  
  270.                 File.separator + "browser-photos");  
  271.         cameraDataDir.mkdirs();  
  272.         mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator +  
  273.                 System.currentTimeMillis() + ".jpg";  
  274.         cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath)));  
  275.         return cameraIntent;  
  276.     }  
  277.     private Intent createCamcorderIntent() {  
  278.         return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);  
  279.     }  
  280.     private Intent createSoundRecorderIntent() {  
  281.         return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);  
  282.     }  
  283. }  

别忘了顶部Import

[java] view plain copy
  1. //added  
  2. import org.apache.cordova.CordovaPlugin;  
  3. import org.apache.cordova.CordovaInterface;  
  4. import android.annotation.SuppressLint;  
  5. import android.app.Activity;  
  6. import android.content.ActivityNotFoundException;  
  7. import android.content.Intent;  
  8. import android.net.Uri;  
  9. import android.os.Environment;  
  10. import android.provider.MediaStore;  
  11. import android.webkit.ValueCallback;  
  12. import android.widget.Toast;  
  13. import java.io.File;  


2、修改cordova-plugin-inappbrowser\src\android\InAppBrowser.java

增加成员变量

[java] view plain copy
  1. //added  
  2. private InAppChromeClient chromeClient;  
修改showWebPage方法里

[java] view plain copy
  1. // WebView  
  2. inAppWebView = new WebView(cordova.getActivity());  
  3. inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));  
  4. inAppWebView.setId(Integer.valueOf(6));  
  5. //inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView));//注释掉这一句  
  6. //加上下面2句  
  7. chromeClient = new InAppChromeClient(thatWebView, InAppBrowser.this);  
  8. inAppWebView.setWebChromeClient(chromeClient);  
  9. WebViewClient client = new InAppBrowserClient(thatWebView, edittext);  
增加一个Override方法

[java] view plain copy
  1. @Override  
  2. public void onActivityResult(int requestCode, int resultCode, Intent intent) {  
  3.     if (requestCode == InAppChromeClient.Controller.FILE_SELECTED) {  
  4.         // Chose a file from the file picker.  
  5.         if (chromeClient != null && chromeClient.mUploadHandler != null) {  
  6.             chromeClient.mUploadHandler.onResult(resultCode, intent);  
  7.         }  
  8.     }  
  9.     super.onActivityResult(requestCode, resultCode, intent);  
  10. }  



至于iOS版本的,最新的InAppBrowser插件1.5.0+已经支持选择文件了(1.5.0以前的版本不行,有bug)
0 0
原创粉丝点击