webview之加载H5界面无法调用手机本地图库

webview加载H5页面,如果H5界面需要调用手机的本地图库

首先在此祝各位大佬远离BUG

  • 比如我们在开发中会遇到这样的场景,需要加载一个H5界面,这个界面里面可能有用户上传头像这个功能,但是当你怎么点击上传图片的时候它都无响应。但是你把这个H5用手机浏览器打开,会发现他可以正常调用手机本地的图库,对于此类问题,我分两种情况讲
  • Acvtivity里面用webview去加载 H5界面。
  • fragment里面用webview去加载 H5界面。

Acvtivity里面用webview去加载 H5界面。首先要重新设置myChromeViewClient和myWebViewClinet,会根据不用的系统调用不用的回调,2.0 3.0 4.0 5.0+,需要注意的是回调在onActivityResult方法里面。

解决方案上代码

首先需要定义成员变量:

private UploadHandler mUploadHandler;
private ValueCallback<Uri[]> mUploadMessageForAndroid5;
public final static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;
private MyChromeViewClient myChromeViewClient =new MyChromeViewClient();

然后拿到webview控件对其设置:

webview.setWebChromeClient(myChromeViewClient);
webview.setWebViewClient(myWebViewClinet);

重写onActivityResult方法,因为设置myChromeViewClient和myWebViewClinet,会根据不用的系统调用不用的回调,2.0
3.0 4.0 5.0+,而各种回调的执行都在onActivityResult里面:

@Override
    protected void onActivityResult(int requestCode, int resultCode,
            Intent intent) {

        if (requestCode == Controller.FILE_SELECTED) {
            // Chose a file from the file picker.
            if (mUploadHandler != null) {
                mUploadHandler.onResult(resultCode, intent);
            }
        } else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
            if (null == mUploadMessageForAndroid5)
                return;
            Uri result = (intent == null || resultCode != Activity.RESULT_OK) ? null
                    : intent.getData();
            System.out.println("-----------界面执行了回调"+(result == null));
            if (result != null) {
                mUploadMessageForAndroid5.onReceiveValue(new Uri[] { result });
            } else {
                mUploadMessageForAndroid5.onReceiveValue(new Uri[] {});
            }
            mUploadMessageForAndroid5 = null;
        }
        super.onActivityResult(requestCode, resultCode, intent);
    }

然后将一下代码复制到你的activity里面

class MyDownloadListener implements DownloadListener {

        @Override
        public void onDownloadStart(String url, String userAgent,
                String contentDisposition, String mimetype, long contentLength) {
            // TODO Auto-generated method stub

        }

    }

    /**
     * 有几个类要说明下:
     * 
     * MyChromeViewClient
     * 继承WebChromeClient重写了几个关键方法。其中有三个重载方法openFileChooser,用来兼容不同的Andorid版本
     * ,以防出现NoSuchMethodError异常。
     * 另外一个类UploadHandler,起到一个解耦合作用,它相当于WebChromeClient和Web网页端的一个搬运工兼职翻译
     * ,解析网页端传递给WebChromeClient的动作
     * ,然后将onActivityResult接收用户选择的文件传递给司机ValueCallback
     * 。WebChromeClient提供了一个Web网页端和客户端交互的通道,而UploadHandler就是用来搬砖的~。
     * UploadHandler有个很重要的成员变量:ValueCallback<Uri>
     * mUploadMessage。ValueCallback是WebView留下来的一个回调
     * ,就像是WebView的司机一样,当WebChromeClient和UploadHandler合作将文件选择后
     * ,ValueCallback开始将文件给WebView,告诉WebView开始干活了,砖头已经运回来了,你可以盖房子了。
     */
    class MyChromeViewClient extends WebChromeClient {

        @Override
        public void onCloseWindow(WebView window) {
            WebViewCustomer.this.finish();
            super.onCloseWindow(window);
        }

        public void onProgressChanged(WebView view, final int progress) {

        }

        @Override
        public boolean onJsAlert(WebView view, String url, String message,
                final JsResult result) {

            new AlertDialog.Builder(WebViewCustomer.this)
                    .setTitle("提示信息")
                    .setMessage(message)
                    .setPositiveButton(android.R.string.ok,
                            new DialogInterface.OnClickListener() {

                                @Override
                                public void onClick(DialogInterface dialog,
                                        int which) {
                                    result.confirm();
                                }
                            }).setCancelable(false).create().show();
            return true;
        }

        @Override
        public boolean onJsConfirm(WebView view, String url, String message,
                final JsResult result) {

            new AlertDialog.Builder(WebViewCustomer.this)
                    .setTitle("提示信息")
                    .setMessage(message)
                    .setPositiveButton(android.R.string.ok,
                            new DialogInterface.OnClickListener() {

                                @Override
                                public void onClick(DialogInterface dialog,
                                        int which) {
                                    result.confirm();
                                }
                            })
                    .setNegativeButton(android.R.string.cancel,
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog,
                                        int which) {
                                    result.cancel();
                                }
                            }).setCancelable(false).create().show();
            return true;

        }

        // Android 2.x
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            openFileChooser(uploadMsg, "");
        }

        // Android 3.0
        public void openFileChooser(ValueCallback<Uri> uploadMsg,
                String acceptType) {
            openFileChooser(uploadMsg, "", "filesystem");
        }

        // Android 4.1
        public void openFileChooser(ValueCallback<Uri> uploadMsg,
                String acceptType, String capture) {

            mUploadHandler = new UploadHandler(new Controller());
            mUploadHandler.openFileChooser(uploadMsg, acceptType, capture);
        }

        // For Android 5.0+
        public boolean onShowFileChooser(WebView webView,
                ValueCallback<Uri[]> filePathCallback,
                WebChromeClient.FileChooserParams fileChooserParams) {

            openFileChooserImplForAndroid5(filePathCallback);
            return true;
        }

    }

    private void openFileChooserImplForAndroid5(ValueCallback<Uri[]> uploadMsg) {
        mUploadMessageForAndroid5 = uploadMsg;
        Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
        contentSelectionIntent.setType("image/*");

        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
        chooserIntent.putExtra(Intent.EXTRA_TITLE, "选择图片");
        System.out.println("-----------调用");
        startActivityForResult(chooserIntent,
                FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
    }

    class MyWebViewClinet extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {

            return true;
        }

    }

    // copied from android-4.4.3_r1/src/com/android/browser/UploadHandler.java

    class UploadHandler {
        /*
         * The Object used to inform the WebView of the file to upload.
         */
        private ValueCallback<Uri> mUploadMessage;
        private String mCameraFilePath;
        private boolean mHandled;
        private boolean mCaughtActivityNotFoundException;
        private Controller mController;

        public UploadHandler(Controller controller) {
            mController = controller;
        }

        public String getFilePath() {
            return mCameraFilePath;
        }

        boolean handled() {
            return mHandled;
        }

        public void onResult(int resultCode, Intent intent) {
            if (resultCode == Activity.RESULT_CANCELED
                    && mCaughtActivityNotFoundException) {
                // Couldn't resolve an activity, we are going to try again so
                // skip
                // this result.
                mCaughtActivityNotFoundException = false;
                return;
            }
            Uri result = (intent == null || resultCode != Activity.RESULT_OK) ? null
                    : intent.getData();

            // As we ask the camera to save the result of the user taking
            // a picture, the camera application does not return anything other
            // than RESULT_OK. So we need to check whether the file we expected
            // was written to disk in the in the case that we
            // did not get an intent returned but did get a RESULT_OK. If it
            // was,
            // we assume that this result has came back from the camera.
            if (result == null && intent == null
                    && resultCode == Activity.RESULT_OK) {
                File cameraFile = new File(mCameraFilePath);
                if (cameraFile.exists()) {
                    result = Uri.fromFile(cameraFile);
                    // Broadcast to the media scanner that we have a new photo
                    // so it will be added into the gallery for the user.
                    mController.getActivity().sendBroadcast(
                            new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
                                    result));
                }
            }
            mUploadMessage.onReceiveValue(result);
            mHandled = true;
            mCaughtActivityNotFoundException = false;
        }

        public void openFileChooser(ValueCallback<Uri> uploadMsg,
                String acceptType, String capture) {
            final String imageMimeType = "image/*";
            final String videoMimeType = "video/*";
            final String audioMimeType = "audio/*";
            final String mediaSourceKey = "capture";
            final String mediaSourceValueCamera = "camera";
            final String mediaSourceValueFileSystem = "filesystem";
            final String mediaSourceValueCamcorder = "camcorder";
            final String mediaSourceValueMicrophone = "microphone";
            // According to the spec, media source can be 'filesystem' or
            // 'camera' or 'camcorder'
            // or 'microphone' and the default value should be 'filesystem'.
            String mediaSource = mediaSourceValueFileSystem;
            if (mUploadMessage != null) {
                // Already a file picker operation in progress.
                return;
            }
            mUploadMessage = uploadMsg;
            // Parse the accept type.
            String params[] = acceptType.split(";");
            String mimeType = params[0];
            if (capture.length() > 0) {
                mediaSource = capture;
            }
            if (capture.equals(mediaSourceValueFileSystem)) {
                // To maintain backwards compatibility with the previous
                // implementation
                // of the media capture API, if the value of the 'capture'
                // attribute is
                // "filesystem", we should examine the accept-type for a MIME
                // type that
                // may specify a different capture value.
                for (String p : params) {
                    String[] keyValue = p.split("=");
                    if (keyValue.length == 2) {
                        // Process key=value parameters.
                        if (mediaSourceKey.equals(keyValue[0])) {
                            mediaSource = keyValue[1];
                        }
                    }
                }
            }
            // Ensure it is not still set from a previous upload.
            mCameraFilePath = null;
            if (mimeType.equals(imageMimeType)) {
                if (mediaSource.equals(mediaSourceValueCamera)) {
                    // Specified 'image/*' and requested the camera, so go ahead
                    // and launch the
                    // camera directly.
                    startActivity(createCameraIntent());
                    return;
                } else {
                    // Specified just 'image/*', capture=filesystem, or an
                    // invalid capture parameter.
                    // In all these cases we show a traditional picker filetered
                    // on accept type
                    // so launch an intent for both the Camera and image/*
                    // OPENABLE.
                    Intent chooser = createChooserIntent(createCameraIntent());
                    chooser.putExtra(Intent.EXTRA_INTENT,
                            createOpenableIntent(imageMimeType));
                    startActivity(chooser);
                    return;
                }
            } else if (mimeType.equals(videoMimeType)) {
                if (mediaSource.equals(mediaSourceValueCamcorder)) {
                    // Specified 'video/*' and requested the camcorder, so go
                    // ahead and launch the
                    // camcorder directly.
                    startActivity(createCamcorderIntent());
                    return;
                } else {
                    // Specified just 'video/*', capture=filesystem or an
                    // invalid capture parameter.
                    // In all these cases we show an intent for the traditional
                    // file picker, filtered
                    // on accept type so launch an intent for both camcorder and
                    // video/* OPENABLE.
                    Intent chooser = createChooserIntent(createCamcorderIntent());
                    chooser.putExtra(Intent.EXTRA_INTENT,
                            createOpenableIntent(videoMimeType));
                    startActivity(chooser);
                    return;
                }
            } else if (mimeType.equals(audioMimeType)) {
                if (mediaSource.equals(mediaSourceValueMicrophone)) {
                    // Specified 'audio/*' and requested microphone, so go ahead
                    // and launch the sound
                    // recorder.
                    startActivity(createSoundRecorderIntent());
                    return;
                } else {
                    // Specified just 'audio/*', capture=filesystem of an
                    // invalid capture parameter.
                    // In all these cases so go ahead and launch an intent for
                    // both the sound
                    // recorder and audio/* OPENABLE.
                    Intent chooser = createChooserIntent(createSoundRecorderIntent());
                    chooser.putExtra(Intent.EXTRA_INTENT,
                            createOpenableIntent(audioMimeType));
                    startActivity(chooser);
                    return;
                }
            }
            // No special handling based on the accept type was necessary, so
            // trigger the default
            // file upload chooser.
            startActivity(createDefaultOpenableIntent());
        }

        private void startActivity(Intent intent) {
            try {
                mController.getActivity().startActivityForResult(intent,
                        Controller.FILE_SELECTED);
            } catch (ActivityNotFoundException e) {
                // No installed app was able to handle the intent that
                // we sent, so fallback to the default file upload control.
                try {
                    mCaughtActivityNotFoundException = true;
                    mController.getActivity().startActivityForResult(
                            createDefaultOpenableIntent(),
                            Controller.FILE_SELECTED);
                } catch (ActivityNotFoundException e2) {
                    // Nothing can return us a file, so file upload is
                    // effectively disabled.
                    Toast.makeText(mController.getActivity(),
                            "File uploads are disabled.", Toast.LENGTH_LONG)
                            .show();
                }
            }
        }

        private Intent createDefaultOpenableIntent() {
            // Create and return a chooser with the default OPENABLE
            // actions including the camera, camcorder and sound
            // recorder where available.
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            Intent chooser = createChooserIntent(createCameraIntent(),
                    createCamcorderIntent(), createSoundRecorderIntent());
            chooser.putExtra(Intent.EXTRA_INTENT, i);
            return chooser;
        }

        private Intent createChooserIntent(Intent... intents) {
            Intent chooser = new Intent(Intent.ACTION_CHOOSER);
            chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents);
            chooser.putExtra(Intent.EXTRA_TITLE, "Choose file for upload");
            return chooser;
        }

        private Intent createOpenableIntent(String type) {
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType(type);
            return i;
        }

        private Intent createCameraIntent() {
            Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            File externalDataDir = Environment
                    .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
            File cameraDataDir = new File(externalDataDir.getAbsolutePath()
                    + File.separator + "browser-photos");
            cameraDataDir.mkdirs();
            mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator
                    + System.currentTimeMillis() + ".jpg";
            cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                    Uri.fromFile(new File(mCameraFilePath)));
            return cameraIntent;
        }

        private Intent createCamcorderIntent() {
            return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
        }

        private Intent createSoundRecorderIntent() {
            return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
        }
    }

    class Controller {

        final static int FILE_SELECTED = 4;

        Activity getActivity() {
            return WebViewCustomer.this;
        }
    }

以上代码就可以解决我们在webview加载H5界面的时候无法调用手机本地图库的问题。网上也有很多,和这个类似或者差不多的,我只能说我亲测这个是没有问题的。

如果你的webview是在fragment里面,那你用了我以上的方法就很难受,会骂我在网上随便百度一个就发播客,我也曾经这样过。

我们现在说一下在fragment是怎么回事。在activity里面的时候说过,解决这个问题需要我们设置myChromeViewClient和myWebViewClinet,会根据不用的系统调用不用的回调,2.0 3.0 4.0 5.0+,需要注意的是回调在onActivityResult方法里面,但是fragment的回调是在fragment所在的activity的onActivityResult方法里面,所以fragment里面需要实现的话,需要在他所在的activity的onActivityResult方法里面设置监听或者发送广播,然后调用fragment里面onActivityResult这个方法执行。(可以将这个方法重命名,只要执行就可以)。也可以将fragment里面的这个方法做成静态方法,直接去调用,然后将数据传递进行执行就可以。这里可能有些新手或者刚入门的小伙伴在开发中遇到这个问题,还是希望我不要这么多废话,直接给代码和解决方案:(在这我先说一下根本原因:就是图库选择的图片再回传的时候,传给了fragment所在的activity)

那么首先:
在fragment所在的activity里面重写

@Override
    protected void onActivityResult(int arg0, int arg1, Intent arg2) {
        super.onActivityResult(arg0, arg1, arg2);
        if(arg0 == 2 || arg0 == 4){
            Intent intent = new Intent("pictureCallback"); 
            intent.putExtra("requestCode", arg0);  
            intent.putExtra("resultCode", arg1);  
            if(arg2 == null){
                intent.putExtra("webviewintent", ""); 
            }else{
                intent.putExtra("webviewintent", arg2.getData().toString()); 
            }
            sendBroadcast(intent);
        }

    }

有人会问了,你这个2和4是什么鬼。我这样告诉你,我也不知道是什么鬼。但是我在fragment所在的activity里面的onActivityResult方法里面监听,每当我点击打开图片这个功能,都会在activity的onActivityResult方法里面获取到这两个数字,我觉得应该是每次activity去打开其他activity都有标识的,可能这个2和4就是。我试了很多次都是这样。如果你们用着不行,那建议你们在这个方法里面打印一下log日志,看一下你们的arg0是多少。但是我觉得应该是我这个没错。你们现在应该很清楚,我用的是广播这种方法。有人可能会吐槽,但是我觉得还不错。
我们继续,后面只需要在fragment里面去接收一下这个广播就可以了。就能执行上面的那些方法

在fragment里面定义变量

WebViewPictureCallback myBroadcastReceiver = null;

注册广播

myBroadcastReceiver = new WebViewPictureCallback();
IntentFilter filter = new IntentFilter("pictureCallback"); 
((MainActivity)getActivity()).registerReceiver(myBroadcastReceiver, filter);

自定义广播接收者

    public class WebViewPictureCallback extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent != null
                    && intent.getAction().equals("pictureCallback")
                    && getResultCode() == Activity.RESULT_OK){
                Uri uri = null;
                if("".equals(intent.getStringExtra("webviewintent"))){
                    uri = null;
                }else{
                    uri = Uri.parse((intent.getStringExtra("webviewintent")));
                }
                gallery(intent.getIntExtra("requestCode", 0),intent.getIntExtra("resultCode", 0)
                        ,uri);
            }

        }

    }

大家看到了gallery方法,其实就是前面activity里面说的onActivityResult方法,只是重新起了个方法名,然后去调用

    public void gallery(int requestCode, int resultCode, Uri data) {
        if (requestCode == Controller.FILE_SELECTED) {
            // Chose a file from the file picker.
            if (mUploadHandler != null) {
                mUploadHandler.onResult(resultCode, data);
            }
        } else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
            if (null == mUploadMessageForAndroid5){
                return;
            }
            Uri result = (data == null || resultCode != Activity.RESULT_OK) ? null
                    : data;
            if (result != null) {
                mUploadMessageForAndroid5.onReceiveValue(new Uri[] { result });
            } else {
                mUploadMessageForAndroid5.onReceiveValue(new Uri[] {});
            }
            mUploadMessageForAndroid5 = null;
        }

    }

所以总结一下,在activity里面使用web view调用不了本地图库的方法我已经给出了,在fragment里面只是fragmnet没法响应回调,即打开图库,之后在图库里面选择的图片值传回来,fragment本身无法接收到,所以就多了一个中间环节,在fragment所在的activity里面先接受,然后把这个值不管你是想用我前面说的方法,还是我用的广播。在fragment里面去接收一下图库返回的值,然后其他的就还是按照上面的方法执行的。
以上是webview加载H5无法打开手机本地图库的问题。本来还有很多webview开发过程中的坑,发现一篇博客没法给大家说完,所以我之后还会写一些其他的webview开发遇到的坑。如果各位大佬有什么问题欢迎吐槽,如果对你有帮助,解决了问题。请帮忙顶一下。
谢谢

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值