美文网首页AndroidAndroid知识Android开发
WebView上传文件的深坑与研究

WebView上传文件的深坑与研究

作者: 影响身边的人 | 来源:发表于2016-12-22 14:29 被阅读236次

    直接切入主题了

    最近公司项目里线上用户反馈出一个bug,介入的第三方平台中有个添加图片的功能,当点击H5中的按钮的时候,调不起本地文件管理器,在很多手机上都出现这种情况

    原因如下

    刨除定制化的webview来说,原生webview是支持上传文件的。但是众多版本的迭代扩展,api参数也不一样。一般拿到上传文件的需求时,大家都会照搬android brower的代码(聪明),api如下:

            // Android > 4.1.1 调用这个方法
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
                LogUtils.d("openFileChooser 4.1.1 = ");
            }
    
            // 3.0 + 调用这个方法
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
                LogUtils.d("openFileChooser 3.0 = ");
            }
    
            // Android < 3.0 调用这个方法
            public void openFileChooser(ValueCallback<Uri> uploadMsg) {
                LogUtils.d("openFileChooser < 3.0 = ");
            }
    

    之前项目里的代码当然也是这样写的,后来还怀疑过是否需要和前端联调,后来查了一下发现了第一个坑:

    5.0之后,系统提供了onShowFileChooser来让我们实现选择文件的方法。

    看了Sam的文章知道了这点:Sam


    之后,本以为没有问题了,测试的时候发现,坑又来了。

    我们可以看出,在这个功能上,有很明显的api升级痕迹,在试过众多手机之后,发现,4.4.0到4.4.2的系统无法调用这个api,当然也无从上传文件了,是怎么回事?

    在这篇博客找到了详细的答案:穿衣助手技术博客

    原来google在4.4更改webkit内核为chromium之后,把这个api删除了!一般升级api,都会保留对原有api的支持,@deprecated掉旧的api,加入新的api,但是google却2个都没做,开发团队解释,我们正在开发一个新的共有的api,这个api会更好,我们会在正式版本上推出。结果呢,在4.4.3版本,他又把这个api加回来了

    于是怎么解决呢,网络上大多都放弃或是没有解决方案了,太过于麻烦,有这么三个方式:
    参考这个,不过并没有给出具体的代码,只有思路,并且只有第三个思路是较靠谱的

    • 第一种,H5直接使用新的video标签通过获取navigator的getUserMedia来获取视频流stream中截取一张图的方式来实现,不过这种方式在mobile上的支持比较晚。其中,android是从Android5.0才开始支持,ios未知。所以这种实现思路不做考虑。

    • 第二种,H5直接用以前Html旧有的input标签来实现。其中这种方式在ios支持上还不错,在android上的支持则不怎么令人满意。因为它直接涉及到了webkit在android各个平台不同的实现方式,有很大的风险性。但是,出于方便让ios能直接调用的原因这个方式的可行性还是很大的。当然,问题并非不能解决,这个下面再讲。

    • 第三种,H5直接利用js和本地进行交互来实现。这种方式的采用很成功的例子就是微信公众账号的实现,它通过实现一套js库来支持网页的各种调用

    关于第三种方式的解决方案如下:

    点我下载

    用js调用的本地方法如下:

     final class ModuleJavaScriptInterface {
    
            ModuleJavaScriptInterface() {
            }
    
            @JavascriptInterface
            public void finishWebview(String json) {
                if (!TextUtils.isEmpty(json)) {
                    Toast.makeText(BrowserActivity.this, "json= " + json, Toast.LENGTH_SHORT).show();
                }
            }
    
            @JavascriptInterface
            public void uploadImage() {
                Toast.makeText(BrowserActivity.this, "upload ", Toast.LENGTH_SHORT).show();
    
    
                //打开相册
                Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                i.addCategory(Intent.CATEGORY_OPENABLE);
                i.setType("*/*");
                startActivityForResult(Intent.createChooser(i, "File Chooser"), KITKAT_RESULTCODE);
    
                /*
                在onActivityResult中  KITKAT_RESULTCODE
    
                1 上传图片
                2 上传成功后 将服务器返回的URL 返回给 js  : UploadedFileName
    
    
                String UploadedFileName = "";
                mWebView.loadUrl("javascript:CheckImage('" + UploadedFileName + "')");
                 */
    
            }
        }
    
    

    当然还有一个小坑

    之前项目里面已经处理好了,所以并没有显现出来,如下:

    调用系统app选择文件的时候,若弹出选择框,cancel掉选择框之后,发现webview无响应了,无法刷新,加载,点击,甚至退出这个activity也无法加载!后果很严重~
    原因是当你选择上传文件的时候,webview的ValueCallback对象(就是选择图片的回调)会持有这个webview,在没有收到回调之前,你无法对这个webview做任何的操作!
    知道原因之后,就很好解决了,如果cancel了,那么直接调用该对象的onReceiveValue()方法,传入null即可,webview就可以正常操作了


    最后别忘了取消混淆的问题呦


    最后给出剩下的代码事例:

    public class SafeWebViewClient extends WebViewClient {  
        @Override  
        public void onProgressChanged(WebView view, int newProgress) {  
            super.onProgressChanged(view, newProgress);  
            activity.mWebLoadingProgressBar.setProgress(newProgress);  
            if (newProgress >= 90 && activity.mWebLoadingProgressBar.getVisibility() == View.VISIBLE) {  
                activity.mWebLoadingProgressBar.setVisibility(View.GONE);  
            }  
        }  
     
        // For Android < 3.0
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {  
      
            activity.mUploadMessage = uploadMsg;  
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
            i.addCategory(Intent.CATEGORY_OPENABLE);  
            i.setType("image/*");  
            activity.startActivityForResult(Intent.createChooser(i, "File Chooser"), activity.FILECHOOSER_RESULTCODE);  
      
        }  
      
        // For Android 3.0+  
        public void openFileChooser(ValueCallback uploadMsg, String acceptType) {  
            activity.mUploadMessage = uploadMsg;  
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
            i.addCategory(Intent.CATEGORY_OPENABLE);  
            i.setType("*/*");  
            activity.startActivityForResult(Intent.createChooser(i, "File Browser"), activity.FILECHOOSER_RESULTCODE);  
        }  
      
        //For Android 4.1  
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {  
            activity.mUploadMessage = uploadMsg;  
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);  
            i.addCategory(Intent.CATEGORY_OPENABLE);  
            i.setType("image/*");  
            activity.startActivityForResult(Intent.createChooser(i, "File Chooser"), activity.FILECHOOSER_RESULTCODE);  
      
        }  
      
        //For Android 5.0  
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)  
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {  
            // make sure there is no existing message  
            if (activity.uploadMessage != null) {  
                activity.uploadMessage.onReceiveValue(null);  
                activity.uploadMessage = null;  
            }  
            activity.uploadMessage = filePathCallback;  
            Intent intent = fileChooserParams.createIntent();  
            try {  
                activity.startActivityForResult(intent, activity.REQUEST_SELECT_FILE);  
            } catch (ActivityNotFoundException e) {  
                activity.uploadMessage = null;  
                return false;  
            }  
            return true;  
        }  
      
    }  
    

    选中图片之后走:

    public void onActivityResult(int requestCode, int resultCode, Intent intent) {  
        if (requestCode == FILECHOOSER_RESULTCODE) {  
            if (null == mUploadMessage) return;  
            Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();  
            mUploadMessage.onReceiveValue(result);  
            mUploadMessage = null;  
        } else if (requestCode == REQUEST_SELECT_FILE) {  
            if (uploadMessage == null) return;  
            uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));  
            uploadMessage = null;  
        }  
    
    

    Thanks && END

    相关文章

      网友评论

        本文标题:WebView上传文件的深坑与研究

        本文链接:https://www.haomeiwen.com/subject/mnthvttx.html