• WebView加载html实现网页上传本地文件(图片,拍照,语音等)


    前言:

    这里有两个方案,第一个使用Andorid客户端和JavaScript互相调用方法来实现,这种方法极力不推荐,它会增加服务端和客户端的开发成本。

    第二种就是继承WebViewChromeClient了,WebChromeClient是Html/Js和Android客户端进行交互的一个中间件,其将webview中js所产生的事件封装,然后传递到Android客户端。Google这样做的其中一个很重要的原因就是安全问题。

    一,使用Android本地和JS方法互相调用完成文件上传与选择(会增客户端与服务端开发成本,不推荐)

    这里我仅仅演示了Andorid客户端和Javascript如何互相调用

    1.Html代码

    <!doctype html>
    <html>
    <head><meta charset="UTF-8"><title>Untitled Document</title></head> <script type="text/javascript"> function javaNoParam(){ Android.showToast(); } function javaWithParam(message){ Android.showToast(message); } function jsNoParam(){ alert("来自Java调用,无参") } function jsWithParam(message){ alert("来自Java调用,有参数:"+message) } </script> <body> <p> <input type="button" name="button" id="button" value="调用Java无参函数" onClick="javaNoParam()"></p> <p> <input type="button" name="button2" id="button2" value="调用Java有参函数" onClick="javaWithParam('有参数')"></p> <p>&nbsp;</p> </body> </html>

    2.启动WebView对JavaScript的支持 ,默认不支持。

    WebSettings setting = webview.getSettings();setting.setJavaScriptEnable(true);

    3.写一个客户端接口供JS端调用

        public class WebAppInterface {
    
            private Context context;
    
            public WebAppInterface(Context context) {
                this.context = context;
            }
    
            public void showToast() {
                Toast.makeText(context, "js端调用,无参数", Toast.LENGTH_SHORT).show();
            }
    
            public void showToast(String message) {
                Toast.makeText(context, "js端调用,有参数:" + message, Toast.LENGTH_SHORT).show();
            }
    
        }

    4.将WebAppInterface接口设置到WebView中

    webview.addJavascriptInterface(new WebAppInterface(this), "Android");

    第二个参数是个代号,供JS端调用,有点像JS和客户端碰头的接头暗号:

     function javaWithParam(message){    
           Android.showToast(message);   //需要在addJavascriptInterface(new WebAppInterface(this), "Android")中设定的保持一致

    }

    设置完以上的部分,就可到达js调用客户端代码的目的

    4.Android客户端远程调用JavaScript方法

     webview.loadUrl("javascript:jsNoParam()");   
     webview.loadUrl("javascript:jsWithParam('" + "Hello!" + "')");

    其中jsNoParam()和jsWithParam(param)都是javacript中的方法

    上面的全部步骤即可实现Andorid客户端和JavaScript的简单调,但是这样如果应用到实际开发中会增加服务端和客户端的开发成本,每个接口都需要服务端和客户端一起协商开发,这样的在开发模式中耦合性很差,有没有一种东西技能满足web端与客户端交互又能达到开发模式上解耦合?

    当然是有,要不然google那帮高帅富们岂不是废了。

    二,继承WebChromeCilent,重写WebChromeClient的onFileChooser方法:

    1.现提供一个简单的版本,仅仅实现选择文件上传功能

     1.Html

    <!doctype html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>WebView Test</title>
    </head>
    
    <script type="text/javascript">
    
      function  alertSomething(){
          alert("你好")
          }
          
    function delete_confirm() <!--调用方法-->
    {
        event.returnValue = confirm("确定 or 取消");
    }
    
    </script>
    <body>
    <input type="button" name="button" id="button" value="js提示对话框" onClick="alertSomething()"></p>
    <input type="button" name="button2" id="button2" value="js确定or取消对话框" onClick="delete_confirm()"></p>

    <input type="file" value="" class='zj-up-btn pa' name="uploadfile" id="uploadfile" onchange="form.submit()" /></p> </body> </html>

    Java:继承WebChromeClient重写onFileChooser方法

    public class FileSelectionWebActivity extends FragmentActivity {
    
        private static final int FILE_SELECT_CODE = 0;
    
        private WebView webView;
        private ValueCallback<Uri> mUploadMessage;
    
        @Override
        protected void onCreate(Bundle arg0) {
            super.onCreate(arg0);
            setContentView(R.layout.selection_file_web_activity);
            initWebView();
          
        }
    
        @SuppressLint("SetJavaScriptEnabled")
        private void initWebView() {
            webView = (WebView) findViewById(R.id.fileSelectionWebview);
    WebSettings webSettings
    = webView.getSettings(); webSettings.setJavaScriptEnabled(true); webSettings.setBuiltInZoomControls(true);

    webView.loadUrl("file:///android_asset/selectFileHtml/index.html"); webView.setWebViewClient(new MyWebViewClient(this)); webView.setWebChromeClient(new MyWebChromeClient()); } private class MyWebChromeClient extends WebChromeClient { // For Android 3.0+ public void openFileChooser(ValueCallback<Uri> uploadMsg) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_SELECT_CODE); } // For Android 3.0+ public void openFileChooser(ValueCallback uploadMsg, String acceptType) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); startActivityForResult(Intent.createChooser(i, "File Browser"), FILE_SELECT_CODE); } // 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("image/*"); startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_SELECT_CODE); } } private class MyWebViewClient extends WebViewClient { private Context context; public DuomiWebViewClient(Context context) { super(); this.context = context; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); } } // flipscreen not loading again @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != RESULT_OK) { return; } switch (requestCode) { case FILE_SELECT_CODE : { Uri uri = data.getData(); Log.e("Tag", "Path:" + uri.toString()); mUploadMessage.onReceiveValue(uri); mUploadMessage = null; } break; } } }

    上面的代码只是提供了上传文件的功能,有时候当你想上传图时可能需要拍照上传,或者你想上传各种多媒体类型的文件,怎么办?

    其实我们手机的浏览器已经有这些功能了,为何不Reading the fucking source code!

    下面提供一个复杂的功能,代码是从浏览器中移植过来的:

    拥有的功能:

    1.客户端弹出服务端JS对话框

    2.能够拍照上传

    3.支持主流媒体文件选择

     废话不多说,贴代码:

    public class WebViewActivity extends FragmentActivity {
    
        private WebView webview;
        private UploadHandler mUploadHandler;
    
        @Override
        protected void onCreate(Bundle arg0) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            super.onCreate(arg0);
            setContentView(R.layout.activity_webview);
    
            webview = (WebView) findViewById(R.id.webview);
            webview.setWebChromeClient(new MyChromeViewClient());
            webview.setWebViewClient(new MyWebViewClinet());
       //     webview.setDownloadListener(new MyDownloadListener());
            
            
            initWebViewSettings();
            initData();
    
        }
    
        @SuppressLint({ "SetJavaScriptEnabled", "NewApi" })
        private void initWebViewSettings() {
    
            WebSettings settings = webview.getSettings();
            settings.setDefaultFontSize(50);
            settings.setDefaultFixedFontSize(30);
    
            settings.setJavaScriptEnabled(true);
            settings.setAllowFileAccess(true);
            settings.setDomStorageEnabled(true);
            settings.setLoadWithOverviewMode(true);
            settings.setUseWideViewPort(true);
            settings.setSupportZoom(true);
         
            // WebView inside Browser doesn't want initial focus to be set.
            settings.setNeedInitialFocus(false);
            // Browser supports multiple windows
            settings.setSupportMultipleWindows(true);
            // enable smooth transition for better performance during panning or
    
        }
    
        private void initData() {
            Intent intent = getIntent();
            String url = intent.getStringExtra("url");
            webview.loadUrl(url);
    
        }
    
        @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);
                }
            }
            super.onActivityResult(requestCode, resultCode, intent);
        }
    
    
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
    
            if ((keyCode == KeyEvent.KEYCODE_BACK) && webview.canGoBack()) {
                webview.goBack();
                return true;
            }
           
            return super.onKeyDown(keyCode, event);
        }
        
        
        
        class  MyDownloadListener implements DownloadListener{
    
    
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype,
                    long contentLength) {
                // TODO Auto-generated method stub
                
            }
            
        }
        
        class MyChromeViewClient extends WebChromeClient {
    
            @Override
            public void onCloseWindow(WebView window) {
                WebViewActivity.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(WebViewActivity.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(WebViewActivity.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);
            }
    
        }
    
        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 WebViewActivity.this;
            }
        }
    
    }

    有几个类要说明下:

    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开始干活了,砖头已经运回来了,你可以盖房子了。

         
  • 相关阅读:
    通用类 GetCPU 返回系统CPU占用百分比
    通用类 NVCSpecialized 序列和反序列化
    通用类 Logger 日志类
    第07组 Alpha冲刺 (1/6)(组长)
    第07组 Alpha冲刺 (5/6)(组长)
    第07组 Alpha冲刺 (6/6)(组长)
    第07组 Alpha冲刺 (2/6)(组长)
    第07组 Alpha冲刺 总结(组长)
    第07组 Alpha冲刺 (4/6)(组长)
    第07组 Alpha冲刺 (3/6)(组长)
  • 原文地址:https://www.cnblogs.com/ufreedom/p/4158081.html
Copyright © 2020-2023  润新知