简体   繁体   中英

More efficient method to take picture from Android in-app-cam and upload it to server

I'm building an app that require method to take picture from in-app camera, but for some Android devices (old device or low ram), it's quite freeze when taking picture triggered. Is there any code i can modify or optimize to make user experience feels better?

//this function trigger to take picture (or screenshot) from user screen
    private void captureImage() {
        mPreview.setDrawingCacheEnabled(true);
        final Bitmap[] drawingCache = {Bitmap.createBitmap(mPreview.getDrawingCache())};
        mPreview.setDrawingCacheEnabled(false);

     mCameraSource.takePicture(null, bytes -> {
            int orientation = Exif.getOrientation(bytes);
            Bitmap temp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            Bitmap picture = rotateImage(temp, orientation);
            assert picture != null;
            Bitmap overlay = Bitmap.createBitmap(mGraphicOverlay.getWidth(), mGraphicOverlay.getHeight(), picture.getConfig());
            Canvas canvas = new Canvas(overlay);

            Matrix matrix = new Matrix();
            matrix.setScale((float) overlay.getWidth() / (float) picture.getWidth(), (float) overlay.getHeight() / (float) picture.getHeight());

            // mirror by inverting scale and translating
            matrix.preScale(-1, 1);
            matrix.postTranslate(canvas.getWidth(), 0);

            Paint paint = new Paint();
            canvas.drawBitmap(picture, matrix, paint);
            canvas.drawBitmap(drawingCache[0], 0, 0, paint);

//this function to save picture taken and put it on app storage cache
            try {
                String mainpath = getApplicationContext().getFilesDir().getPath() + separator + "e-Presensi" + separator;
                File basePath = new File(mainpath);
                if (!basePath.exists())
                    Log.d("CAPTURE_BASE_PATH", basePath.mkdirs() ? "Success" : "Failed");

//this function to get directory path of saved photo
                String path = mainpath + "photo_" + getPhotoTime() + ".jpg";
                String namafotoo = "photo_" + getPhotoTime() + ".jpg";
                filePath = path;

                namafoto = namafotoo;
                SessionManager.createNamaFoto(namafoto);

                File captureFile = new File(path);
                boolean sucess = captureFile.createNewFile();
                if (!captureFile.exists())
                    Log.d("CAPTURE_FILE_PATH", sucess ? "Success" : "Failed");
                FileOutputStream stream = new FileOutputStream(captureFile);
                overlay.compress(Bitmap.CompressFormat.WEBP, 60, stream);
                stream.flush();
                stream.close();
                if (!picture.isRecycled()) {
                    picture.recycle();
                }

                if (drawingCache[0] != null && !drawingCache[0].isRecycled()) {
                    drawingCache[0].recycle();
                    drawingCache[0] = null;
                }
                mPreview.setDrawingCacheEnabled(false);
                uploadPicture();
                finish();

            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

Thanks for your help.

In general, I would advise you to step through your code and look at large memory resources you're generating on each line and consider setting those to null aggressively as you move throughout the method if you're done.

For example, you have a variable called temp which is size "Y" bytes that you appear to rotate and then never use temp after that. If picture is a rotated copy of temp then you have now used 2Y bytes of memory to keep temp and picture . I suspect if you simply set temp to null after creating picture , you might free up half that memory that your older/slower phones are going to badly need.

Take that same concept and follow through with the rest of your method to see if you can find other optimizations. Basically anywhere you're creating a copy of the image data you're not going to use, you should immediately set it to null so the garbage collector can throw it away aggressively.

Firt you need some variables:

byte[] byteArray_IMG;
String currentPhotoPath;
static final int REQUEST_TAKE_PHOTO = 1;
ImageView imageView; // maybe you need show the photo before send it

Then define the method to take the photo:

  private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    // Ensure that there's a camera activity to handle the intent
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // Create the File where the photo should go
        File photoFile = null;

        try {
            photoFile = createImageFile();
        } catch (IOException ex) {
            Toast.makeText(getApplicationContext(),Error takin the photo",Toast.LENGTH_LONG).show();
        }

        // Continue only if the File was successfully created
        if (photoFile != null) {
            path_img = photoFile.toString();
            Uri photoURI = FileProvider.getUriForFile(this,"com.app.yournmamepackage.fileprovider" , photoFile);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
        }
    }
}

Create the method to create the file image

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 = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
            imageFileName,  /* prefix */
            ".jpg",         /* suffix */
            storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    currentPhotoPath = image.getAbsolutePath();
    return image;
}

Override the activity result to:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode,resultCode,data);

    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {

 /*            Bundle extras =  data.getExtras();
        Bitmap imageBitmap = (Bitmap)  extras.get("data");

        imageView.setImageBitmap(imageBitmap);

        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);

        byteArray_IMG = stream.toByteArray();*/

        MediaScannerConnection.scanFile(this, new String[]{path_img}, null,
                new MediaScannerConnection.OnScanCompletedListener() {
                    @Override
                    public void onScanCompleted(String path, Uri uri) {
                        Log.i("path",""+path);
                    }
                });

        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        Bitmap imageBitmap = BitmapFactory.decodeFile(path_img);
        imageBitmap.compress(Bitmap.CompressFormat.JPEG, 14, stream);
        imageView.setImageBitmap(imageBitmap);
        byteArray_IMG = stream.toByteArray();
    }
}

Remember this is very important

imageBitmap.compress(Bitmap.CompressFormat.JPEG, 25, stream)

 // 25 is photo quality  0-100

Then you can upload the picture usin an asynchronous process

Firstly Initialize these variables above onCreate() method in your activity/fragment

val FILE_NAME:String="photo.jpg"     //give any name with.jpg extension
private var imageuri: Uri?=null
val REQUEST_IMAGE=111

Now open camera

val intent: Intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

        photofile = getphotofile(FILE_NAME)
        imageuri = activity?.let { it1 -> FileProvider.getUriForFile(it1, "//your package name here//.fileprovider", photofile) }     //put your package name
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageuri)
        startActivityForResult(int, REQUEST_IMAGE)

onActivityResult() method

if(requestCode==REQUEST_IMAGE && resultCode == Activity.RESULT_OK){
        val progressdialog: ProgressDialog = ProgressDialog(activity)
        progressdialog.setTitle("Sending Image....")         
        progressdialog.show()                   //start your progressdialog

        val ref= FirebaseDatabase.getInstance().reference
        val messagekey=ref.push().key          //create a key to store image in firebase

        var bmp: Bitmap?=null
        try {
            bmp=MediaStore.Images.Media.getBitmap(activity?.contentResolver,imageuri)   //get image in bitmap
        } catch (e: IOException) {
            e.printStackTrace();
        }
        val baos= ByteArrayOutputStream()
        bmp!!.compress(Bitmap.CompressFormat.JPEG,50,baos)   //compress the quality of image to 50 from 100
        val fileinBytes: ByteArray =baos.toByteArray()

        val store: StorageReference = FirebaseStorage.getInstance().reference.child("Chat Images/")    //create a child reference in firebase
        val path=store.child("$messagekey.jpg")    //store a message in above reference with the unique key created above with .jpg extension
        val uploadTask: StorageTask<*>
        uploadTask=path.putBytes(fileinBytes)    //it will upload the image to firebase at given path

        uploadTask.continueWithTask(Continuation<UploadTask.TaskSnapshot, Task<Uri>> { task ->
            if (!task.isSuccessful) {
                task.exception?.let {
                    throw it
                }
                Toast.makeText(activity, "Upload Failed", Toast.LENGTH_SHORT).show()
            }
            return@Continuation path.downloadUrl
        }).addOnCompleteListener { task->
            if(task.isSuccessful){

                url=task.result.toString()   //get the url of uploaded image
                
                //Do what you want with the url of your image                     

                progressdialog.dismiss()
                Toast.makeText(activity, "Image Uploaded", Toast.LENGTH_SHORT).show()
            }
        }.addOnFailureListener { e->
            progressdialog.dismiss()
            Toast.makeText(activity, "Failed " + e.message, Toast.LENGTH_SHORT)
                    .show();
        }
        uploadTask.addOnProgressListener { task->
            var progress:Double=(100.0*task.bytesTransferred)/task.totalByteCount
            progressdialog.setMessage("Sending ${progress.toInt()}%")   //this will show the progress in progress bar 0-100%
        }

    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM