bitmap memory management with bitmap recycle - android

This is quite a tricky one. I thought I had it resolved as it was working with my S2. But the problem has come back during testing HTC One M7.
I have a welcome screen kind of app, which keeps displaying random (or same selected) images from a storage folder whenever user switches phone on.
I'm preparing image view in OnResume, where I'm calling my image drawing method.
protected void onResume() {
super.onResume();
if (isCallActive(this) == false) {
changeImage(false);
} else {
}
}
ChangeImage method code is:
public void changeImage(Boolean vForce) {
String pathV = null;
SharedPreferences pref = getApplicationContext().getSharedPreferences("MyPref", 0); // 0 - for private mode
Boolean vPicChosen;
vPicChosen = pref.getBoolean("PicChosen", false);
if (vPicChosen == true) {
pathV = pref.getString("PicURL", "NOPIC");
} else pathV = "NOPIC";
if (pathV == "NOPIC" || vForce == true) {
pathV = randomPic();
}
imgFile = new File(pathV);
if(imgFile.exists()) {
img1 = (ImageView)findViewById(R.id.imageView);
Display display = getWindowManager().getDefaultDisplay();
float width = display.getWidth();
float height = display.getHeight();
if (reusedBitmap != null && !reusedBitmap.isRecycled()) {
//reusedBitmap.recycle();
}
reusedBitmap = decodeSampledBitmapFromPath(pathV, (int)width, (int)height);
img1.setImageBitmap(reusedBitmap);
}
else
Toast.makeText(this, "no Image present", Toast.LENGTH_SHORT).show();
}
Now with this code, every time my app was presenting an image (same or different), the memory heap was growing by 3 to 6MB, depending upon the image size. After 5-6 screen switchons, the memory would go beyond 50Mb, and eventually result in out of memory, or app being killed.
So I tried many things, and eventually succeeded in keeping a tab on memory by recycling the bitmap in onStop method:
#Override
protected void onStop() {
Log.i("event","onStop");
if (reusedBitmap != null && !reusedBitmap.isRecycled()) {
reusedBitmap.recycle();
}
}
As I mentioned earlier, this used to work fine on my S2.
But on my HTC One M7, due to this call, image drawing is throwing error
12-03 16:21:12.944: E/AndroidRuntime(25881): java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap#41af3468
If I remove this line from onStop, the memory growing problem comes back.
Any suggestion please?

Related

Mediaprojection issues on Android 9+

I made an OCR application that makes a screenshot using Android mediaprojection and processes the text in this image. This is working fine, except on Android 9+. When mediaprojeciton is starting there is always a window popping up warning about sensitive data that could be recorded, and a button to cancel or start recording. How can I achieve that this window will only be showed once?
I tried preventing it from popping up by creating two extra private static variables to store intent and resultdata of mediaprojection, and reusing it if its not null. But it did not work (read about this method in another post).
// initializing MP
mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
// Starting MediaProjection
private void startProjection() {
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
}
// OnActivityResult
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (requestCode == 100) {
if(mProjectionManager == null) {
cancelEverything();
return;
}
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if(mProjectionManager != null)
sMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
else
cancelEverything();
if (sMediaProjection != null) {
File externalFilesDir = getExternalFilesDir(null);
if (externalFilesDir != null) {
STORE_DIRECTORY = externalFilesDir.getAbsolutePath() + "/screenshots/";
File storeDirectory = new File(STORE_DIRECTORY);
if (!storeDirectory.exists()) {
boolean success = storeDirectory.mkdirs();
if (!success) {
Log.e(TAG, "failed to create file storage directory.");
return;
}
}
} else {
Log.e(TAG, "failed to create file storage directory, getExternalFilesDir is null.");
return;
}
// display metrics
DisplayMetrics metrics = getResources().getDisplayMetrics();
mDensity = metrics.densityDpi;
mDisplay = getWindowManager().getDefaultDisplay();
// create virtual display depending on device width / height
createVirtualDisplay();
// register orientation change callback
mOrientationChangeCallback = new OrientationChangeCallback(getApplicationContext());
if (mOrientationChangeCallback.canDetectOrientation()) {
mOrientationChangeCallback.enable();
}
// register media projection stop callback
sMediaProjection.registerCallback(new MediaProjectionStopCallback(), mHandler);
}
}
}, 2000);
}
}
My code is working fine on Android versions below Android 9. On older android versions I can choose to keep that decision to grant recording permission, and it will never show up again. So what can I do in Android 9?
Thanks in advance, I'm happy for every idea you have :)
Well the problem was that I was calling
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
every time, which is not necessary (createScreenCaptureIntent() leads to the dialog window which requests user interaction)
My solution makes the dialog appear only once (if application was closed it will ask for permission one time again).
All I had to do was making addiotional private static variables of type Intent and int.
private static Intent staticIntentData;
private static int staticResultCode;
On Activity result I assign those variables with the passed result code and intent:
if(staticResultCode == 0 && staticIntentData == null) {
sMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
staticIntentData = data;
staticResultCode = resultCode;
} else {
sMediaProjection = mProjectionManager.getMediaProjection(staticResultCode, staticIntentData)};
}
Every time I call my startprojection method, I will check if they are null:
if(staticIntentData == null)
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
else
captureScreen();
If null it will request permission, if not it will start the projection with the static intent data and static int resultcode, so it is not needed to ask for that permission again, just reuse what you get in activity result.
sMediaProjection = mProjectionManager.getMediaProjection(staticResultCode, staticIntentData);
Simple as that! Now it will only showing one single time each time you use the app. I guess thats what Google wants, because theres no keep decision checkbox in that dialog like in previous android versions.

Prevent MediaProjection object from being lost on killing the Application

I am using MediaProjection to take screenshot.This is what I am doing.I created an overlay icon using service.On clicking the overlay icon a screenshot is taken.The problem is that whenever the Application is killed either by pressing back button or manually by swiping it the MediaProjection object is lost.Is there a way to maintain the MediaProjection and avoiding requesting for MediaProjection each time application is killed. I have already seen this but I am still unable to do it.
In my Mainactivity Onclick contains startActivityForResult and the resulting onActivityResult is as follows:
#Override
protected void onActivityResult(final int requestCode,final int resultCode,final Intent data)
{
if (requestCode == requestcode)
{
if (resultCode == RESULT_OK)
{
Singelton.setScreenshotPermission((Intent) data.clone());
Singelton.putmanger(mediaProjectionManager);
}else if (resultCode==RESULT_CANCELED) {
Singelton.setScreenshotPermission(null);
}
}
}
The Singelton class is as follows:
protected static void setScreenshotPermission(final Intent permissionIntent)
{
screenshotPermission = permissionIntent;
//screenshotPermission becomes null once the application is killed
}
public static MediaProjection getData()
{
return (mediaProjection);
}
public static void getScreenshotPermission()
{
if (screenshotPermission != null)
{
Log.d("screenshotpermisson", "screenshotPermission != null ");
if(mediaProjection!=null)
{
Log.d("mediaprojection", "mediaprojection != null ");
mediaProjection.stop();
mediaProjection = null;
}
mediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, (Intent) screenshotPermission.clone());
}
else
{
//Here I need to request for media projection again without starting activity
}
}
My Service class for overlay icon handles click as follows:
public void createDisplay()
{
if (mediaProjection == null)
{
Singelton.getScreenshotPermission();
mediaProjection = Singelton.getData();
}
mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1);
vd = mediaProjection.createVirtualDisplay("screen-mirror", width, height, mDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mImageReader.getSurface(), null, null);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener(){..}
}
I am new to android and having a hard time figuring this out.Any help will be appreciated.
Just re-create the media projection as fadden implied in his comment. Since every time your service is killed the associated overlay is also killed with it therefore you have to re-create the media projection anyway when restarting the service.
You already doing fine by not consuming the original intent and instead using an clone as answered here https://stackoverflow.com/a/33892817/3918978 .
However if the permission or the MediaProjection shouldn't be valid anymore just get a new one (start an activity asking for it).

Android Developement - If-Then-Else Statement. Switching images

So What I would like to do here is that the starting image is assigned with Tag 0. When I run the method, I want the image to change to gallows1 and get a tag of "1". Then when its run again, I want it to keep gallows1 with a tag of 1, and then change it to gallows2, and reassign it.... Basically, I want 1 image to change to the next, and so forth as the method runs 6 times.
Here is what I have so far.
public void switchImage(View view) {
ImageView img = (ImageView) findViewById(R.id.imageView1);
img.setTag("0");
if (img.getTag() == "0") {
img.setImageResource(R.drawable.gallows1);
img.setTag("1");
} else if (img.getTag() == "1") {
img.setImageResource(R.drawable.gallows2);
img.setTag("2");
} else if (img.getTag() == "2") {
img.setImageResource(R.drawable.gallows3);
img.setTag("3");
} else if (img.getTag() == "3") {
img.setImageResource(R.drawable.gallows4);
img.setTag("4");
} else if (img.getTag() == "4") {
img.setImageResource(R.drawable.gallows5);
img.setTag("5");;
} else if (img.getTag() == "5") {
img.setImageResource(R.drawable.gallows6);
img.setTag("6");
} else if (img.getTag() == "6") {
return;
}
}
The Game I am making is hangman, but instead of using graphics, I wanted to have a twist (its for a challenge proj) and have progressing images show up instead.
Put this outside the method:
ImageView img = (ImageView) findViewById(R.id.imageView1);
img.setTag("0")
and remove img.setTag("0") from inside the method
The way you have it now, img.setTag("0") gets called everytime so it will always just hit the first if block. If you move it outside, then the first time the method is called, if block 1 will match, then the next time, if block 2, etc.

handling device rotation with camera activity

I have an activity which the first thing it does is send a camera intent. I want the user to take a picture and then I will use it to do something. My problem is that If the user changes the rotation while taking the picture the app keeps on looping inside the camera until he finishes the entire process while staying in a single device orientation.
Here is the code:
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
_dateTextView = FindViewById<TextView> (Resource.Id.DateLabel);
ViewModel.RecomendDishViewModel.RecomendationSent += RecomendationSent;
if (IsThereAnAppToTakePictures())
{
CreateDirectoryForPictures();
var imageButton = FindViewById<ImageButton> (Resource.Id.TakeImageWaterMark);
_imageView = FindViewById<ImageView> (Resource.Id.UserDishImage);
imageButton.Click += TakePictureClicked;
if(_bitmap != null)
{
_imageView.RecycleBitmap ();
_imageView.SetImageBitmap(_bitmap);
}
}
_dateTextView.Click += DateLabelClicked;
TakePictureClicked ()
}
protected void TakePictureClicked ()
{
Intent intent = new Intent(MediaStore.ActionImageCapture);
_file = new Java.IO.File(_dir, String.Format("myPhoto_{0}.jpg", Guid.NewGuid()));
intent.PutExtra(MediaStore.ExtraOutput, Uri.FromFile(_file));
StartActivityForResult(intent, 0);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
// make it available in the gallery
Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
Uri contentUri = Uri.FromFile(_file);
mediaScanIntent.SetData(contentUri);
SendBroadcast(mediaScanIntent);
// display in ImageView. We will resize the bitmap to fit the display
// Loading the full sized image will consume to much memory
// and cause the application to crash.
_bitmap = _file.Path.LoadAndResizeBitmap (518, 388);
if(_bitmap != null)
{
MemoryStream stream = new MemoryStream();
_bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
byte[] bitmapData = stream.ToArray();
ViewModel.RecomendDishViewModel.ImageData = bitmapData;
if(_imageView == null)
{
return;
}
_imageView.RecycleBitmap ();
_imageView.SetImageBitmap(_bitmap);
}
_file.Dispose ();
_dir.Dispose ();
}
My problem is that the activity gets recreated and then launches the camera app again. I have tried many things (this is the clean version) but nothing worked perfectly...
Any ideas?
This might not be exactly the solution you were looking for, but you could try simply locking screen rotation during the activity.
You can set this in the Android Manifest like so:
<activity android:name="MyActivity"
android:screenOrientation="portrait">
...
</activity>
This will keep the activity from redrawing when the device is rotated. Obviously you can replace "portrait" with "landscape" if you feel that a landscape layout would be more appropriate for your activity.
I'd recommend saving the state of the Activity like a Question before that I gave advice on.
Sadly, I'd failed. : )
Save State
To add to Slack Shots answer your really need to understand what is going on within the activity life cycle you either need to lock the orientation of your app and prevent changes or the better way (save state) or handle the orientation changes directly.
http://developer.android.com/training/basics/activity-lifecycle/recreating.html

Taking a picture from the camera fails 20% of the time

var camera = {
settings : {
quality : 50,
targetWidth : 1024,
targetHeight : 1024,
correctOrientation : true
}
};
var error = function(message) {
alert("Error happened while trying to get a picture", message);
};
document.addEventListener("deviceready", function() {
camera.toFile = function() {
this.settings.destinationType = navigator.camera.DestinationType.FILE_URI;
return this;
},
camera.toBase64 = function() {
this.settings.destinationType = navigator.camera.DestinationType.DATA_URL;
return this;
},
camera.fromCamera = function() {
this.settings.sourceType = navigator.camera.PictureSourceType.CAMERA;
return this;
};
camera.fromLibrary = function() {
this.settings.sourceType = navigator.camera.PictureSourceType.PHOTOLIBRARY;
return this;
};
camera.fromPhotoAlbum = function() {
this.settings.sourceType = navigator.camera.PictureSourceType.SAVEDPHOTOALBUM;
return this;
}
camera.get = function(callback) {
navigator.camera.getPicture(function(data) {
alert("taking a picture successful");
callback(data);
}, error, camera.settings);
};
}, false);
This is my small wrapper for the camera. And I call it like this:
camera.fromPhotoAlbum().toBase64().get(function(base64){});
About 20% of the time, the "alert("taking a picture successful");" is not called, while no error is shown. If I cancel taking a picture, an alert with the message "Error happened while trying to get a picture" is shown, so the error callback works.
Basically nothing happens. I've tested it on a Samsung Galaxy S2 on CM9 and a brand new HTC One X.
There was another question recently about this same problem that I answered. We ran into this at my company and solved it. It has more to do with the Android system than Phonegap.
What's happening is when you start the camera, your app goes into onStop(). While there, the Android system has the right to kill your app if it needs memory. It just so happens that memory usually gets low when the camera takes a picture and dumps it into memory, so there's a good chance your app will get killed while your user takes a picture.
Now that your app is dead, when the camera finishes, it restarts your app. That's why it's acting so weird; the camera comes back into your app, but not the same instance that it had before, so your callback never gets called, since it doesn't exist anymore.
You can reduce the frequency at which this occurs by reducing the quality of the picture and passing it by URI instead of data to your app, but the problem won't go away completely.
To work around the callback never happening, we made a Java callback that starts the camera and saves the picture to the same location every time it takes one. Then, when the app starts back up from getting killed, it looks in that location for the picture.
It's a weird solution to a stupid problem, but if your app gets killed, that camera callback simply won't happen. If you need more information on how to make the Java callback to do this, let me know and I'll put our code up here. Otherwise, take a look at this SO answer for more info.
EDIT: Here's the code we use in our main DroidGap activity:
private static final String folderPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/appName";
private static final String filePath = "phonegapImage.jpg";
#Override
public void onCreate(Bundle savedState) {
//...The rest of onCreate, this makes the Java available in JavaScript
appView.addJavascriptInterface(this, "Camera");
}
public void takePhoto(final String callback) {
Log.v("Camera Plugin", "Starting takePhoto callback");
Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(folderPath, filePath)));
startActivityForResult(intent, TAKE_PICTURE);
}
public String getPhotoUri() {
return Uri.fromFile(new File(folderPath, filePath)).toString();
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case TAKE_PICTURE:
if (resultCode == Activity.RESULT_OK) {
//Do whatever you need to do when the camera returns
//This is after the picture is already saved, we return to the page
}
break;
default:
Log.v("Camera", "Something strange happened...");
break;
}
}
Then, in your JavaScript, you can invoke the camera with:
Camera.takePhoto("onPhotoURISuccess");
//Then, to get the location of the photo after you take it and load the page again
var imgPath = Camera.getPhotoUri();
So, that's about it. Just make sure to change all of the path/file/page/etc names to what you want to use in your app. This will overwrite that image every time a picture is taken, but you can probably figure something out to dynamically name them if you don't want that. You can use that URI just as you would any other path in your JavaScript.

Categories

Resources