最近公司项目需要在WebView上调用手机系统相册来上传图片,开发过程中发现在很多机器上无法正常唤起系统相册来选择图片。

解决问题之前我们先来说说WebView上传文件的逻辑:当我们在Web页面上点击选择文件的控件()时,会回调WebChromeClient下的openFileChooser()(5.0及以上系统回调onShowFileChooser())。这个时候我们在openFileChooser方法中通过Intent打开系统相册或者支持该Intent的第三方应用来选择图片。like this:

public void openFileChooser(ValueCallback 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()中将选择的图片内容通过ValueCallbackonReceiveValue方法返回给WebView,然后通过js上传。代码如下:

@Overrideprotected 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:ValueCallbacksWebView组件通过openFileChooser()或者onShowFileChooser()提供给我们的,它里面包含了一个或者一组Uri,然后我们在onActivityResult()里将Uri传给ValueCallbacksonReceiveValue()方法,这样WebView就知道我们选择了什么文件。

到这里你可能要问了,说了这么多还是没解释为什么在很多机型上无法唤起系统相册或者第三方app来选择图片啊?!这是因为为了最求完美的Google攻城狮们对openFileChooser做了多次修改,在5.0上更是将回调方法该为了onShowFileChooser。所以为了解决这一问题,兼容各个版本,我们需要对openFileChooser()进行重载,同时针对5.0及以上系统提供onShowFileChooser()方法:

webview.setWebChromeClient(new WebChromeClient() {        // For Android < 3.0        public void openFileChooser(ValueCallback valueCallback) {            ***        }        // For Android  >= 3.0        public void openFileChooser(ValueCallback valueCallback, String acceptType) {            ***        }        //For Android  >= 4.1        public void openFileChooser(ValueCallback valueCallback,                 String acceptType, String capture) {            ***        }        // For Android >= 5.0        @Override        public boolean onShowFileChooser(WebView webView,                 ValueCallback 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@Deprecatedpublic void openFileChooser(ValueCallback 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 uploadMessage;    private ValueCallback 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 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 valueCallback, String acceptType, String capture) {                uploadMessage = valueCallback;                openImageChooserActivity();            }            // For Android >= 5.0            @Override            public boolean onShowFileChooser(WebView webView, ValueCallback 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

如果你喜欢我的文章,就关注下我的知乎专栏或者在 GitHub 上添个 Star 吧!

  • 知乎专栏:https://zhuanlan.zhihu.com/baron
  • GitHub:https://github.com/BaronZ88

更多相关文章

  1. Android(安卓)进阶 教你打造 Android(安卓)中的 IOC 框架 【View
  2. 条件数据库Android:sqllite使用
  3. android中ListView异步加载图片时的图片错位问题解决方案
  4. 如何在MainActivity中使用ReactApplicationContext,让android 可
  5. 浅谈Android游戏开发基础和经验
  6. Android(安卓)View绘制回调方法流程
  7. Android(安卓)时区设置以及设置系统属性的分析
  8. windows中下载android源码的方法 附下载脚本
  9. Android日记之2012\01\13

随机推荐

  1. Android线程
  2. Android彻底组件化—UI跳转升级改造
  3. Android的八种对话框的实现
  4. Android(安卓)文件的保存与读取之SDCard(S
  5. android与javascript交互调用
  6. 解决 Android(安卓)在Eclipse 开发中 Cla
  7. android设置横屏和竖屏的方法
  8. Android(安卓)使用Vitamio打造自己的万能
  9. Android不是一个商业成功的产品?
  10. Android(安卓)应用框架 —— 组件