android webview图片文件上传——上传控件点击无效的解决办法

android webview图片文件上传兼容性问题——上传控件点击无效的解决办法

现象描述:

在android的webview中,如果web页面中带有< input type=”file” …>的控件,在webview中虽然能正常显示这个上传控件,但是在部分手机上点击却没有任何反应。

查找原因

经过大量机型验证发现5.0以下的可以正常显示出来,5.0及以上的手机点击无反应。初步可以判定是Webview的兼容性问题。 
正常情况下在有上传控件的webview点击上传按钮的流程应该是:WebView加载包含上传文件的表单按钮,HTML定义了input标签,同时input的type类型为file,手指点击该按钮,回调openFileChooser这个方法(5.0及以上系统回调onShowFileChooser),然后打开文件选择器选择照片或者文件。 
通过查阅sdk对比分析了解到,Android 5.0以下的系统回调openFileChooser方法,而5.0及以上的系统回调onShowFileChooser方法,而由于系统并没有实现onShowFileChooser方法,因而造成了在5.0及以上系统没有反应的现象。

解决方案

因此我们需要重写webchromeClient中的onShowFileChooser,实现5.0及以上系统的兼容。 
首先查看一下openFileChooser和onShowFileChooser的系统源码 
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);  
}  onShowFileChooser
/ 
 1. Tell the client to show a file chooser. 
 2. 
 3. This is called to handle HTML forms with 'file' input type, in response to the 
 4. user pressing the "Select File" button. 
 5. To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and 
 6. return true. 
 7. 
 8. @param webView The WebView instance that is initiating the request. 
 9. @param filePathCallback Invoke this callback to supply the list of paths to files to upload, 
 10.                         or NULL to cancel. Must only be called if the 
 11.                         <code>showFileChooser</code> implementations returns true. 
 12. @param fileChooserParams Describes the mode of file chooser to be opened, and options to be 
 13.                          used with it. 
 14. @return true if filePathCallback will be invoked, false to use default handling. 
 15. 
 16. @see FileChooserParams 
 */  
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,  
        FileChooserParams fileChooserParams) {  
    return false;  
}  

通过对比这两个方法可以看出它们的区别,openFileChooser中传入的参数ValueCallback接口回传一个Uri的对象,而onShowFileChooser回传一个Uri[]数组,因此在onActivityResult回调方法中调用ValueCallback接口方法onReceiveValue传入参数需特别注意对于这两种方法的回调要区别对待。

/** 
 *回调onShowFileChooser方法,onReceiveValue传入Uri对象数组 
 */  
mFilePathCallback.onReceiveValue(new Uri[]{uri});  
/** 
 *回调openFileChooser方法,onReceiveValue传入一个Uri对象 
 */  
mFilePathCallback4.onReceiveValue(uri);  

代码实例

1. 重写openFileChooser和onShowFileChooser方法,兼容各版本
webview.setWebChromeClient(new WebChromeClient() {
            // For Android < 3.0
            public void openFileChooser(ValueCallback<Uri> uploadMsg) {
                this.openFileChooser(uploadMsg, "*/*");
            }

            // For Android >= 3.0
            public void openFileChooser(ValueCallback<Uri> uploadMsg,
                    String acceptType) {
                this.openFileChooser(uploadMsg, acceptType, null);
            }

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

            // For Lollipop 5.0+ Devices
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            public boolean onShowFileChooser(WebView mWebView,
                    ValueCallback<Uri[]> filePathCallback,
                    WebChromeClient.FileChooserParams fileChooserParams) {
                if (mUploadMessage5 != null) {
                    mUploadMessage5.onReceiveValue(null);
                    mUploadMessage5 = null;
                }
                mUploadMessage5 = filePathCallback;
                Intent intent = fileChooserParams.createIntent();
                try {
                    startActivityForResult(intent,
                            FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
                } catch (ActivityNotFoundException e) {
                    mUploadMessage5 = null;
                    return false;
                }
                return true;
            }
        });
2. 在onActivityResult()中将选择的图片内容通过ValueCallback的onReceiveValue方法返回给WebView,后续通过js上传
 protected void onActivityResult(int requestCode, int resultCode,
            Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage) {
                return;
            }
            Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
                    : intent.getData();
            mUploadMessage.onReceiveValue(result);
            mUploadMessage = null;
        } else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
            if (null == mUploadMessage5) {
                return;
            }
            mUploadMessage5.onReceiveValue(WebChromeClient.FileChooserParams
                    .parseResult(resultCode, intent));
            mUploadMessage5 = null;
        }
    }
3. 完整代码实例
package com.example.test;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;

public class MainActivity extends Activity {
    private ValueCallback<Uri> mUploadMessage;
    private ValueCallback<Uri[]> mUploadMessage5;
    public static final int FILECHOOSER_RESULTCODE = 5173;
    public static final int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 5174;

    @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> uploadMsg){
                this.openFileChooser(uploadMsg, "*/*");
            }

            // For Android >= 3.0
            public void openFileChooser(ValueCallback<Uri> uploadMsg,
                    String acceptType) {
                this.openFileChooser(uploadMsg, acceptType, null);
            }

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

            // For Lollipop 5.0+ Devices
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            public boolean onShowFileChooser(WebView mWebView,
                    ValueCallback<Uri[]> filePathCallback,
                    WebChromeClient.FileChooserParams fileChooserParams) {
                if (mUploadMessage5 != null) {
                    mUploadMessage5.onReceiveValue(null);
                    mUploadMessage5 = null;
                }
                mUploadMessage5 = filePathCallback;
                Intent intent = fileChooserParams.createIntent();
                try {
                    startActivityForResult(intent,
                            FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
                } catch (ActivityNotFoundException e) {
                    mUploadMessage5 = null;
                    return false;
                }
                return true;
            }
        });
        String targetUrl = "file:///android_asset/up.html";
        webview.loadUrl(targetUrl);
    }

    @SuppressLint("NewApi")
    @Override
    protected void onActivityResult(int requestCode, int resultCode,
            Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage) {
                return;
            }
            Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
                    : intent.getData();
            mUploadMessage.onReceiveValue(result);
            mUploadMessage = null;
        } else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
            if (null == mUploadMessage5) {
                return;
            }
            mUploadMessage5.onReceiveValue(WebChromeClient.FileChooserParams
                    .parseResult(resultCode, intent));
            mUploadMessage5 = null;
        }
    }
}

代码的git地址,内附简易的web的上传按钮页面,在assets文件夹下 
git地址 
csdn下载地址

后记

由于公司项目中是使用的cordova3.0.0, 同样也有这个兼容性问题,可以同上重写CordovaChromeClient里的方法。另外通过查看cordova最新版本发现它已经修复了这个问题,所以也可以通过升级cordova来解决此问题。最新版的cordova的SystemWebChromeClient中重写了onShowFileChooser。具体如下:

 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
        Intent intent = fileChooserParams.createIntent();
        try {
            parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
                @Override
                public void onActivityResult(int requestCode, int resultCode, Intent intent) {
                    Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
                    LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
                    filePathsCallback.onReceiveValue(result);
                }
            }, intent, FILECHOOSER_RESULTCODE);
        } catch (ActivityNotFoundException e) {
            LOG.w("No activity found to handle file chooser intent.", e);
            filePathsCallback.onReceiveValue(null);
        }
        return true;
    }

从源码中可以看出它进一步将onActivityResult里的操作也封装在onShowFileChooser,所以升级到最新版的cordova的话我们就不需要自己去写onActivityResult里的操作,更加方便省事。

代码的git地址,内附简易的web的上传按钮页面,在assets文件夹下 
git地址 
csdn下载地址

我自己的代码示例子:

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import android.webkit.*;
import android.widget.Toast;
import com.example.administrator.demo.R;
import com.github.lzyzsd.jsbridge.*;

/**
 * Created by Administrator on 2017/9/12.
 */
public class Register extends AppCompatActivity {
    //文件上传
    private ValueCallback<Uri> mUploadMessage;
    private ValueCallback<Uri[]> mUploadMessage5;
    public static final int FILECHOOSER_RESULTCODE = 5173;
    public static final int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 5174;

    private BridgeWebView webView;
    private ProgressDialog mProgressDialog;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.register);
        getSupportActionBar().hide();
        webView = (BridgeWebView)findViewById(R.id.webViewRegister);

        mProgressDialog = new ProgressDialog(this);
        initWebView();
    }
    private void initWebView() {
        //文件上传
        assert webView != null;
        webView.getSettings().setUseWideViewPort(true);
        webView.getSettings().setLoadWithOverviewMode(true);
        webView.getSettings().setJavaScriptEnabled(true);

        //设置具体的webViewClient
        webView.setWebViewClient(new MyWebViewClient(webView));
        //set HandlerCallback
        webView.setDefaultHandler(new myHandlerCallBack());
        webView.setWebChromeClient(new WebChromeClient() {
            //捕获网页中的弹框信息
            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                if (message != null) {
                    Toast.makeText(getApplicationContext(),message,Toast.LENGTH_SHORT).show();
                }
                result.cancel();
                return true;
            }
            //捕获网页的确认框
            public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                //throw new RuntimeException("Stub!");
                return super.onJsConfirm(view,url,message,result);
            }
            //捕获网页的输入框
            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                //throw new RuntimeException("Stub!");
                return super.onJsPrompt(view,url,message,defaultValue,result);
            }

            //文件上传
            // For Android < 3.0
            public void openFileChooser(ValueCallback<Uri> uploadMsg) {
                this.openFileChooser(uploadMsg, "*/*");
            }

            // For Android >= 3.0
            public void openFileChooser(ValueCallback<Uri> uploadMsg,
                                        String acceptType) {
                this.openFileChooser(uploadMsg, acceptType, null);
            }

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

            // For Lollipop 5.0+ Devices
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            public boolean onShowFileChooser(WebView mWebView,
                                             ValueCallback<Uri[]> filePathCallback,
                                             WebChromeClient.FileChooserParams fileChooserParams) {
                if (mUploadMessage5 != null) {
                    mUploadMessage5.onReceiveValue(null);
                    mUploadMessage5 = null;
                }
                mUploadMessage5 = filePathCallback;
                Intent intent = fileChooserParams.createIntent();
                try {
                    startActivityForResult(intent,
                            FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
                } catch (ActivityNotFoundException e) {
                    mUploadMessage5 = null;
                    return false;
                }
                return true;
            }
        });

        webView.loadUrl("http://192.168.1.136:8989/app/register.html");
        webView.registerHandler("jumpLogin", new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                Intent jumpMainLogin = new Intent(Register.this,MainActivity.class);
                startActivity(jumpMainLogin);
                function.onCallBack("跳转成功!");
            }
        });
    }
    //监听手机的返回事件,然后浏览器也是返回。
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
            webView.goBack();
            return true;
        }
        finish();
        return super.onKeyDown(keyCode,event);
    }
    //自定义BridgeWebViewClient
    class MyWebViewClient extends BridgeWebViewClient {
        public MyWebViewClient(BridgeWebView webView) {
            super(webView);
        }
        @Override
        public void  onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view,url,favicon);
            mProgressDialog.show();
        }
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view,url);
            mProgressDialog.hide();
        }
    }
    //自定义回调
    class myHandlerCallBack extends DefaultHandler {
        @Override
        public void handler(String data, CallBackFunction function) {
            if (function != null) {
                Toast.makeText(Register.this, data, Toast.LENGTH_SHORT).show();
            }
        }
    }
    @SuppressLint("NewApi")
    @Override
    protected void onActivityResult(int requestCode, int resultCode,Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage) {
                return;
            }
            Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
                    : intent.getData();
            mUploadMessage.onReceiveValue(result);
            mUploadMessage = null;
        } else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
            if (null == mUploadMessage5) {
                return;
            }
            mUploadMessage5.onReceiveValue(WebChromeClient.FileChooserParams
                    .parseResult(resultCode, intent));
            mUploadMessage5 = null;
        }
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Android WebView 中实现点击上传图片的功能,需要做以下几个步骤: 1. 在 WebView 中启用 JavaScript: ```java webView.getSettings().setJavaScriptEnabled(true); ``` 2. 为 WebView 设置 WebChromeClient: ```java webView.setWebChromeClient(new WebChromeClient() { // For Android 5.0+ @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { if (mFilePathCallback != null) { mFilePathCallback.onReceiveValue(null); } mFilePathCallback = filePathCallback; Intent intent = fileChooserParams.createIntent(); try { startActivityForResult(intent, REQUEST_SELECT_FILE); } catch (ActivityNotFoundException e) { mFilePathCallback = null; Toast.makeText(MainActivity.this, "Cannot open file chooser", Toast.LENGTH_SHORT).show(); return false; } return true; } // For Android < 5.0 public void openFileChooser(ValueCallback<Uri> uploadMsg) { openFileChooser(uploadMsg, null); } // For Android 3.0+ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { mUploadMessage = uploadMsg; Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); startActivityForResult(Intent.createChooser(intent, "File Chooser"), FILECHOOSER_RESULTCODE); } }); ``` 这里的 `onShowFileChooser()` 方法是为了支持 Android 5.0 及以上版本的文件上传, `openFileChooser()` 方法是为了支持 Android 3.0 至 Android 4.4 版本的文件上传。在 `onActivityResult()` 方法中处理选择的文件并返回给 WebView: ```java @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == FILECHOOSER_RESULTCODE) { if (null == mUploadMessage) return; Uri result = data == null || resultCode != RESULT_OK ? null : data.getData(); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } else if (requestCode == REQUEST_SELECT_FILE) { if (mFilePathCallback == null) return; Uri[] results = null; // Check that the response is a good one if (resultCode == Activity.RESULT_OK) { if (data == null) { // If there is not data, then we may have taken a photo if (mCameraPhotoPath != null) { results = new Uri[]{Uri.parse(mCameraPhotoPath)}; } } else { String dataString = data.getDataString(); ClipData clipData = data.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)}; } } } mFilePathCallback.onReceiveValue(results); mFilePathCallback = null; } } ``` 3. 在 HTML 中添加上传文件的代码: ```html <input type="file" id="fileInput" name="fileInput" multiple> ``` 这里的 `id` 和 `name` 两个属性都必须为 `fileInput`,否则在 WebView无法触发文件上传事件。同时,为了支持多选文件,可以将 `multiple` 属性设置为 `true`。 4. 在 JavaScript 中触发文件上传事件: ```javascript document.getElementById("fileInput").click(); ``` 这里的 `click()` 方法是为了模拟用户点击上传文件按钮。当用户点击上传文件按钮时,WebView 就会弹出文件选择框。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值