Upload an Image from camera or gallery in WebView

前端 未结 5 1751
南笙
南笙 2020-11-27 14:36

WebView in this app opens a page with upload button.

\"Page

Below is th

相关标签:
5条回答
  • 2020-11-27 15:05

    Update 6/18: This doesn't seem to work on Samsung Galaxy S2 with Android 4.2.1. This worked fine on HTC One X+ with 4.1.2. Be cautioned.


    I faced the same problem. Here's the problem and how I solved it.

    Problem:

    When openFileChooser is called, the call back object ValueCallback<Uri> is being passed. This is the actual call back to web view when we have a file ready for it. We save this object to mUploadMessage and use mUploadMessage.onReceiveValue() function in onActivityResult to return the file to Webview. While you choose camera, click a picture, save it and return to the webview activity, our activity might get recycled, which means we actually lose the call back object mUploadMessage. And hence, the file cannot be passed back to webview for upload.

    Fix:

    Fix involves executing some javascript, so enable javascript on webview. Basically, we will get another call back object if we lost the previous one.

    We need to create a boolean field 'mUploadFileOnLoad' and three fields.

        private int mReturnCode;
        private int mResultCode;
        private Intent mResultIntent;
        private boolean mUploadFileOnLoad = false;
    

    When we return to our activity from camera, onActivityResult will be called. If activity is reconstructed, mUploadMessage is null. So, we will save the parameters to fields and set mUploadFileOnLoad to true and return. The else part is very important.

        @Override
        protected void onActivityResult(int requestCode, 
                                        int resultCode,
                                        Intent intent) 
        {  
          //if the callback object has been recycled      
          if(null==mUploadMessage)
          {
            //Save the result
            mReturnCode = requestCode;
            mResultCode = resultCode;
            mResultIntent = intent;
            //remember to invoke file upload using Javascript
            mUploadFileOnLoad = true;
            return;
          }else
            mUploadFileOnLoad = false;
          //rest of the code
        }
    

    The important parts of this solution are in WebViewClient and WebChromeClient

        new WebChromeClient() {
    
            //Other overloaded functions
    
            //See http://stackoverflow.com/a/15423907/375093 for full explanation
            //The undocumented magic method override
            //Eclipse will swear at you if you try to put @Override here
            // For Android < 3.0
            public void openFileChooser(ValueCallback<Uri> uploadMsg) {
                //If we lost the callback object
                if(mUploadFileOnLoad)
                {
                    mUploadMessage = uploadMsg;
                    //use the saved result objects to invoke onActivityResult
                    onActivityResult(mReturnCode, mResultCode, mResultIntent);
                    return;
                }
             //Rest of the code....
             }
    

    and

            new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                if(mUploadFileOnLoad)
                {
                   webview.loadUrl("javascript:document.getElementById('my_file').click()");
                }
            }
    

    In the above, my_file is the id of the <input> element in the web page.

    <input type="file" id="my_file">
    

    So, in summary, what we did is - When we don't have a callback object, we save the data received from other activity and set mUploadFileOnLoad to true and wait for page to load. When page loads, we use Javascript to invoke the file chooser so we get a callback object. Since we already have results, we invoke onActivityResult and return. onActivityResult now has a callback from the webview.

    0 讨论(0)
  • 2020-11-27 15:11

    Make sure you do NOT have android:launchMode="singleInstance" in manifest file

    0 讨论(0)
  • 2020-11-27 15:17

    I am sorry for my English.

    This is a solution.

    The first, you define file members like this.

    public File mTempFile;

    your's open file chooser is ok.

    onActivityResult method is so important.

    the camera app does not return URL, but ValueCallback must have URL.

    Get URI from mTempFile.

    this is work.

    I use to like this.

    if ( mTempFile.exists() ) {
    
        mUploadMessage.onReceiveValue(Uri.fromFile(mTempFile));
        mUploadMessage = null;
    
    } else {
    
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;
    }
    

    If mTempFile is exist that was called camera, other case from gallery.

    0 讨论(0)
  • I suppose that the onActivityResult method is actually called, but the 3rd parameter Intent intent is null. It seems that it is a bug of Nexus phones.

    But you can save the output image uri to the private variable and use it instead of the intent:

    private Uri imageUri;
    
    private void showAttachmentDialog(ValueCallback<Uri> uploadMsg) {
        this.mUploadMessage = uploadMsg;
    
        File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "TestApp");
        if (!imageStorageDir.exists()) {
            imageStorageDir.mkdirs();
        }
        File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg");
        this.imageUri = Uri.fromFile(file); // save to the private variable
    
        final Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
        captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("image/*");
    
        Intent chooserIntent = Intent.createChooser(i, "Image Chooser");
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[] { captureIntent });
    
        this.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == this.mUploadMessage) {
                return;
            }
    
            Uri result;
            if (resultCode != RESULT_OK) {
                result = null;
            } else {
                result = intent == null ? this.imageUri : intent.getData(); // retrieve from the private variable if the intent is null
            }
    
            this.mUploadMessage.onReceiveValue(result);
            this.mUploadMessage = null;
        }
    }
    

    In this code I added the imageUri variable to the activity and used it in both methods.

    0 讨论(0)
  • 2020-11-27 15:20

    After struggling a lot I found a code that works for taking files from galley and camera from 5.0+ devices

        private ValueCallback<Uri> mUploadMessage;
    private Uri mCapturedImageURI = null;
    private ValueCallback<Uri[]> mFilePathCallback;
    private String mCameraPhotoPath;
    private static final int INPUT_FILE_REQUEST_CODE = 1;
    private static final int FILECHOOSER_RESULTCODE = 1;
    
    
    
    private File createImageFile() throws IOException {
        // Create an image file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES);
        File imageFile = File.createTempFile(
                imageFileName,  /* prefix */
                ".jpg",         /* suffix */
                storageDir      /* directory */
        );
        return imageFile;
    }
    

    this is initialization and setting webview

         mWebView= (WebView) findViewById(R.id.webview);
            mWebView.getSettings().setJavaScriptEnabled(true);
            mWebView.getSettings().setPluginState(WebSettings.PluginState.OFF);
            mWebView.getSettings().setLoadWithOverviewMode(true);
            mWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
            mWebView.getSettings().setUseWideViewPort(true);
            mWebView.getSettings().setUserAgentString("Android Mozilla/5.0 AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30");
            mWebView.getSettings().setAllowFileAccess(true);
            mWebView.getSettings().setAllowFileAccess(true);
            mWebView.getSettings().setAllowContentAccess(true);
            mWebView.getSettings().supportZoom();
            mWebView.loadUrl(Common.adPostUrl);
    
            mWebView.setWebViewClient(new WebViewClient() {
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                    // do your handling codes here, which url is the requested url
                    // probably you need to open that url rather than redirect:
                    if ( url.contains(".pdf")){
                        Intent intent = new Intent(Intent.ACTION_VIEW);
                        intent.setDataAndType(Uri.parse(url), "application/pdf");
                        try{
                            view.getContext().startActivity(intent);
                        } catch (ActivityNotFoundException e) {
                            //user does not have a pdf viewer installed
                        }
                    } else {
                        mWebView.loadUrl(url);
                    }
                    return false; // then it is not handled by default action
                }
    
    
                @Override
                public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
    
    Log.e("error",description);
                }
    
    
                @Override
                public void onPageStarted(WebView view, String url, Bitmap favicon) {        //show progressbar here
    
                    super.onPageStarted(view, url, favicon);
                }
    
                @Override
                public void onPageFinished(WebView view, String url) {
              //hide progressbar here
    
                }
    
            });
            mWebView.setWebChromeClient(new ChromeClient());
    

    and here is my ChomeClient() method

    public class ChromeClient extends WebChromeClient {
    
        // For Android 5.0
        public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> filePath, WebChromeClient.FileChooserParams fileChooserParams) {
            // Double check that we don't have any existing callbacks
            if (mFilePathCallback != null) {
                mFilePathCallback.onReceiveValue(null);
            }
            mFilePathCallback = filePath;
    
            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                // Create the File where the photo should go
                File photoFile = null;
                try {
                    photoFile = createImageFile();
                    takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
                } catch (IOException ex) {
                    // Error occurred while creating the File
                    Log.e(Common.TAG, "Unable to create Image File", ex);
                }
    
                // Continue only if the File was successfully created
                if (photoFile != null) {
                    mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                            Uri.fromFile(photoFile));
                } else {
                    takePictureIntent = null;
                }
            }
    
            Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
            contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
            contentSelectionIntent.setType("image/*");
    
            Intent[] intentArray;
            if (takePictureIntent != null) {
                intentArray = new Intent[]{takePictureIntent};
            } else {
                intentArray = new Intent[0];
            }
    
            Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
            chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
            chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
    
            startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
    
            return true;
    
        }
    
        // openFileChooser for Android 3.0+
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
    
            mUploadMessage = uploadMsg;
            // Create AndroidExampleFolder at sdcard
            // Create AndroidExampleFolder at sdcard
    
            File imageStorageDir = new File(
                    Environment.getExternalStoragePublicDirectory(
                            Environment.DIRECTORY_PICTURES)
                    , "AndroidExampleFolder");
    
            if (!imageStorageDir.exists()) {
                // Create AndroidExampleFolder at sdcard
                imageStorageDir.mkdirs();
            }
    
            // Create camera captured image file path and name
            File file = new File(
                    imageStorageDir + File.separator + "IMG_"
                            + String.valueOf(System.currentTimeMillis())
                            + ".jpg");
    
            mCapturedImageURI = Uri.fromFile(file);
    
            // Camera capture image intent
            final Intent captureIntent = new Intent(
                    android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
    
            captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedImageURI);
    
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
    
            // Create file chooser intent
            Intent chooserIntent = Intent.createChooser(i, "Image Chooser");
    
            // Set camera intent to file chooser
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS
                    , new Parcelable[] { captureIntent });
    
            // On select image call onActivityResult method of activity
            startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
    
    
        }
    
        // openFileChooser for Android < 3.0
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            openFileChooser(uploadMsg, "");
        }
    
        //openFileChooser for other Android versions
        public void openFileChooser(ValueCallback<Uri> uploadMsg,
                                    String acceptType,
                                    String capture) {
    
            openFileChooser(uploadMsg, acceptType);
        }
    
    }
    

    //here is my onActivityResult method to handle data from gallery or camera intent

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    
            if (requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) {
                super.onActivityResult(requestCode, resultCode, data);
                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();
                    if (dataString != null) {
                        results = new Uri[]{Uri.parse(dataString)};
                    }
                }
            }
    
            mFilePathCallback.onReceiveValue(results);
            mFilePathCallback = null;
    
        } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
            if (requestCode != FILECHOOSER_RESULTCODE || mUploadMessage == null) {
                super.onActivityResult(requestCode, resultCode, data);
                return;
            }
    
            if (requestCode == FILECHOOSER_RESULTCODE) {
    
                if (null == this.mUploadMessage) {
                    return;
    
                }
    
                Uri result = null;
    
                try {
                    if (resultCode != RESULT_OK) {
    
                        result = null;
    
                    } else {
    
                        // retrieve from the private variable if the intent is null
                        result = data == null ? mCapturedImageURI : data.getData();
                    }
                } catch (Exception e) {
                    Toast.makeText(getApplicationContext(), "activity :" + e,
                            Toast.LENGTH_LONG).show();
                }
    
                mUploadMessage.onReceiveValue(result);
                mUploadMessage = null;
    
            }
        }
    
        return;
    }
    

    and here is the permissions required to open camera

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.CAMERA2" /> // for new versions api 21+
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    

    Note: My code also contains the code for devices running 3.0+ also but I never tested them , the above code worked on Lolipop, Marshmallow and Nougat emulators. One more thing , if you see and icon of Android System in place of Camera it means you have many apps available in your device to handle camera.

    0 讨论(0)
提交回复
热议问题