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%
}
}
I have two Image-View in a layout, one as at background and another is above that(at foreground) and I want to save both the images. So can anyone help me out that how can I save both the images into the device storage as single image.
Thank you.
A simple solution I found here is to put both of your imageView in a single Layout and then save your layout as a Bitmap. I will retype the solution code here
private Bitmap getBitmap(View v) {
v.clearFocus();
v.setPressed(false);
boolean willNotCache = v.willNotCacheDrawing();
v.setWillNotCacheDrawing(false);
// Reset the drawing cache background color to fully transparent
// for the duration of this operation
int color = v.getDrawingCacheBackgroundColor();
v.setDrawingCacheBackgroundColor(0);
if (color != 0) {
v.destroyDrawingCache();
}
v.buildDrawingCache();
Bitmap cacheBitmap = v.getDrawingCache();
if (cacheBitmap == null) {
Toast.makeText(StopWarApp.getContext(), "Something went wrong",
Toast.LENGTH_SHORT).show();
return null;
}
Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
// Restore the view
v.destroyDrawingCache();
v.setWillNotCacheDrawing(willNotCache);
v.setDrawingCacheBackgroundColor(color);
return bitmap;
}
Now that you have your Bitmap, you can save it to your storage like this
private void saveImage(Bitmap finalBitmap, String image_name) {
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root);
myDir.mkdirs();
String fname = "Image-" + image_name+ ".jpg";
File file = new File(myDir, fname);
if (file.exists()) file.delete();
try {
FileOutputStream out = new FileOutputStream(file);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Don't forget to add your permissions in the manifest file
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
In my MainActivity onCreate() I want to populate a listview. This listview contains Strings and Bitmaps.
What's the best way to save these data to get them back when the application is restarted ?
There is my own cache solution:
public class MyCache {
private String DiretoryName;
public void OpenOrCreateCache(Context _context, String _directoryName){
File file = new File(_context.getCacheDir().getAbsolutePath() + "/" + _directoryName);
if(!file.exists()){
file.mkdir();
}
DiretoryName = file.getAbsolutePath();
}
public void SaveBitmap(String fileName, Bitmap bmp){
try {
File file = new File(DiretoryName+ "/" + fileName);
if(file.exists()){
file.delete();
}
FileOutputStream Filestream = new FileOutputStream(DiretoryName + "/" + fileName);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
Filestream.write(byteArray);
Filestream.close();
bmp.recycle();
}
catch (Exception e){
e.printStackTrace();
}
}
public Bitmap OpenBitmap(String name){
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
File file = new File(DiretoryName+ "/" + name);
if(file.exists()) {
Bitmap bitmap = BitmapFactory.decodeFile(DiretoryName+ "/" + name, options);
return bitmap;
}
else{
return null;
}
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
public void CleanCacheBitMap(){
File file = new File(Diretorio);
if(file.exists()){
file.delete();
}
}
}
And onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
cache = new MyCache();
cache.OpenOrCreateCache(this, "TheFolderNameForOpenOrSaveInAppCache");
}
And for save on runtime:
cache.SaveBitmap("BitMapName", YourBitmap);
And openning on runtime:
Bitmap bmp = cache.OpenBitmap("BitMapName");
This solution save any bitmap, in particular folder in your app cache (internal storage).
There are different possibilities depending on how tightly you want to hang on to these files. The most reliable is the method getFilesDir(). Each application has its own private "files directory".
All the details are here:
https://developer.android.com/training/basics/data-storage/files.html
The cache directory (getCacheDir()) is similar but the user can erase the cache contents so that's more for temporary files you don't want to hang onto.
Strings and bitmaps can both be saved to files. You can create your own name value mapping file to link them. Or use JSON objects, or many other things to organize your list of strings/bitmaps.
I have an image to the right and a button "download" to the left. the image is from my drawable. now,when i try to click the download i want to put the image to my sdcard downloads. Please help me i only see about download in url. is there other solution for this . Thanks
public class ImageDownloader {
public void download(String url, ImageView imageView) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
task.execute(url);
}
}
/* class BitmapDownloaderTask, see below */
}
First, you need to get your Bitmap. You can already have it as an object Bitmap, or you can try to get it from the ImageView such as:
BitmapDrawable drawable = (BitmapDrawable) ImageView.getDrawable();
Bitmap bitmap = drawable.getBitmap();
Then you must get to directory (a File object) from SD Card such as:
File sdCardDirectory = Environment.getExternalStorageDirectory();
Next, create your specific file for image storage:
File image = new File(sdCardDirectory, "test.png");
After that, you just have to write the Bitmap such as:
boolean success = false;
// Encode the file as a PNG image.
FileOutputStream outStream;
try {
outStream = new FileOutputStream(image);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
/* 100 to keep full quality of the image */
outStream.flush();
outStream.close();
success = true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Finally, just deal with the boolean result if needed. Such as:
if (success) {
Toast.makeText(getApplicationContext(), "Image saved with success",
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(),
"Error during image saving", Toast.LENGTH_LONG).show();
}
Don't forget to add the following permission in your Manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
So I'm trying to restore an image from a file in my app's private files directory.
InputStream inputStream;
BitMapDrawable result;
try {
inputStream = new FileInputStream(file.getAbsolutePath());
result = BitmapDrawable.createFromStream(inputStream, null);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return result;
But obviously I'm doing something wrong because result is always null.
new BitmapDrawable( BitmapFactory.decodeFile(file.getAbsolutePath()) );
Isn't It what you want?
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
Drawable d = new BitmapDrawable(getResources(),bitmap);
imageview.setImageDrawable(d);