I'm trying to pinpoint this memory leak.
I have two SurfaceViews, A and B. I start A, then navigate to B, then press the back button to go back to A, and then I navigate to B again.
I can see my allocated memory rise each time I do this, and eventually I'll get an out of memory error.
Here is how I navigate to B, from inside the SurfaceView connected to A
Context context = this.getContext();
Intent i =new Intent(context, StartCareer.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(i);
In both views, I have a lot of Bitmaps drawing. In B, I can't find any references to A, and the only reference outside the context that I can think of is a reference to a Global class that I have. I also have some analytics stuff going on in the background. It could be a million different things, I'd imagine
I have the DDMS view on Eclipse up, but I'm not sure what I'm looking at, or how to find the exact object that keeps getting repeated.
I'd accept either a crash-course/tutorial on the DDMS Allocation Tracker, or someone to point out what I'm doing wrong.
Additional information:
I have some bitmaps being drawn on a SurfaceView. Examples of such from B are:
////At class level
Bitmap rightB,leftB;
////In the constructor
rightB = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.right), 100,75, true);
////In doDraw
canvas.drawBitmap(rightB, rbX, rbY, null);
And my onDestroys
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mThread.isAlive()){
mThread.setMenuRunning(false);
}
}
So I've run MAT and found one leak, at least. My Thread Keeps getting recreated.
Here's what's doing it.
#Override
public void surfaceCreated(SurfaceHolder holder) {
loading=false;
if (!mThread.isAlive()){
mThread = new ViewThread(this);
mThread.setMenuRunning(true);
mThread.start();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mThread.isAlive()){
mThread.setMenuRunning(false);
}
}
Assuming that these methods get called every time the view loses or gains focus, this seems obviously wrong. How can I re-organize this so that it's not?
Call this method in onDestroy() and onstop() of your app.
private void unbindDrawables(View view) {
Log.d(TAG,"in unbindDrawables");
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
((ViewGroup) view).removeAllViews();
view.setBackgroundResource(0);
Log.d(TAG,"removed views");
//finish();
}
}
A few hints:
Recycle bitmaps when done with an activity (onDestroy for example)
Use the application context rather than the activity itself as context whenever possible
Try to recycle() your bitmaps in onDestroy() of your activities.
What you need to do is explained in detail here. For your specific issue, you need to do this
// resize to desired dimensions
int height = b.getHeight();
int width = b.getWidth();
Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
height: " + height);
double y = Math.sqrt(IMAGE_MAX_SIZE
/ (((double) width) / height));
double x = (y / height) * width;
Bitmap scaledBitmap = Bitmap.createScaledBitmap(b, (int) x,
(int) y, true);
b.recycle();
Related
I'm writing a simple Whack a Mole clone, and I've got my UI elements declared in a GridLayout in a layout.xml, then assigned to ImageView variables in an array programmatically. I've got a startGame() method that simply takes a random int, pulls it from the array and causes it to go visible for a second, then repeats. For some reason, when I put this code in a while() loop, it causes my UI to go blank as soon as it's launched.
I know it's the while() loop because I tried taking the code out of the while() loop, and it ran correctly (once), but turns everything white when placed in a while loop.
Here's the method causing the problem:
public void startGame() {
gameStarted = true;
while(gameStarted) {
randomInt = rand.nextInt(11);
mole[randomInt].setVisibility(View.VISIBLE);
handler.postDelayed(new Runnable() {
#Override
public void run() {
mole[randomInt].setVisibility(View.INVISIBLE);
}
}, 5000);
}
}
All the other relevant code is in onCreate, it's otherwise just a skeleton Activity subclass.
public class WAM_Activity extends Activity {
private ImageView[] mole = new ImageView[11];
private int[] moleId = {R.id.mole1, R.id.mole3, R.id.mole4, R.id.mole5, R.id.mole6, R.id.mole7, R.id.mole8, R.id.mole9, R.id.mole10, R.id.mole11, R.id.mole12};
private boolean gameStarted;
private int randomInt = 0;
private Random rand = new Random();
Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.wam_view_layout);
for (int i = 0; i < 11; i++) {
mole[i] = (ImageView) findViewById(moleId[i]);
mole[i].setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//do stuff eventually
}
});
}
gameStarted = true;
startGame();
}
Any idea why this isn't working? I've been staring at it for hours and I'm quite stumped.
Android doesn't work that way, when onCreate is called, it need to be finished in order for the app to keep responding, I'm surprised you are not getting any "App not respopnding" error.
If you want to create a "game loop" you can simply by creating a new Thread and putting the while in there.
Activity's lifecycle must be executed without blocking them for the app to operate correctly, for more info check here.
Do you know about threads? if you want i can post an example of how to do that with threads but it might be long and if you don't know what a Thread is it will be too confusing for you.
Edit: Ok I'll make an example of a Thread
When I create my games I usually have only one Activity that the only thing it does is creating a custom SurfaceView and nothing else.
public class GameActivity extends Activity
{
//This is a custom class that extends SurfaceView - I will write it below
private GameSurface game;
#Override
protected void onCreate(Bundle bundle)
{
super.onCreate(bundle);
//Create a new instance of the game
game = new GameSurface(this);
//Set the View of the game to the activity
setContentView(game);
}
}
You can also add extra stuff like onSaveInstanceState to save game data and restore them later but I don't want to add them now so the code looks simple.
This class was very simple, let's move on to our SurfaceView. The reason I picked a SurfaceView to do that it's because it is made to allow custom graphics to be drawn on it - exactly what we want on a video game. I will try to make the class as simple as possible:
/*SurfaceHolder.Callback will run some functions in our class when
our surface is completed - at that point we can initialize data
that have to do with the View's width/height.
I don't know if you've noticed that on a View's onCreate()
when you call getWidth() or getHeight() you get 0, that's because
the surface is not initialized yet, this is a way to fix that.
Also we need a Runnable to run the Thread inside this class,
no need to make more classes and make it more complicated*/
public class GameSurface extends SurfaceView
implements SurfaceHolder.Callback, Runnable
{
//This is our thread - we need the "running" variable to be
//able to stop the Thread manually, this will go inside our "while" loop
private Thread thread;
private boolean running;
//Right here you can add more variables that draw graphics
//For example you can create a new class that has a function that
//takes Canvas as a parameter and draws stuff into it, I will add
//a Rect in this case which is a class already made by android
//but you can create your own class that draws images or more
//complicated stuff
private Rect myRect;
//Rect needs a paint to give it color
private Paint myPaint;
//Constructor
public GameSurface(Context context)
{
super(context);
//This is the callback to let us know when surface is completed
getHolder().addCallback(this);
}
//When a class implements SurfaceHolder.Callback you are forced to
//create three functions "surfaceCreated", "surfaceChanged" and
//"surfaceDestroyed" these are called when the surface is created,
//when some settings are changed (like the orientation) and when
//it is about to be destroyed
#Override
public void surfaceCreated(Surface holder)
{
//Let's initialize our Rect, lets assume we want it to have 40
//pixels height and fill the screen's width
myRect = new Rect(0, 0, getWidth(), 40);
//Give color to the rect
myPaint = new Paint();
myPaint.setARGB(0, 255, 0, 0);
//In case you are not familiar with the Rect class, as
//parameters it gets Rect(left, top, right, bottom)
//Time to start our Thread - nothing much to explain here if
//you know how threads work, remember this class implements
//Runnable so the Thread's constructor gets "this" as parameter
running = true;
thread = new Thread(this);
thread.start();
}
//We won't use this one for now, but we are forced to type it
//Even if we leave it empty
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
//When the surface is destroyed we just want the Thread to
//terminate - we don't want threads running when our app is not visible!
#Override
public void surfaceDestroyed(SurfaceHolder holder)
//We will type this function later
{destroyThread();}
//Time for the interesting stuff! let's start with input
#Override
public boolean onTouchEvent(MotionEvent event)
{
//The logic is as follows: when our Rect is touched, we want
//it to become smaller
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (myRect.contains((int) event.getX(), (int) event.getY())
{
myRect.right -= 5;
//Return true - we did something with the input
return true;
}
}
return super.onTouchEvent(event);
}
//This is our update, it will run once per frame
private void update()
{
//Let's assume we want our rect to move 1 pixel downwards
//on every frame
myRect.offset(0, 1);
}
//Now for our draw function
public void draw(Canvas canvas)
{
//Here we want to draw a background and our rect
canvas.drawARGB(0, 0, 0, 255);
canvas.drawRect(myRect, myPaint);
}
//The only thing left is our run() function for the Thread
#Override
public void run()
{
//Screen
Canvas canvas;
//Our game cycle (the famous while)
while(running)
{
//Count start time so we can calculate frames
int startTime = System.currentTimeMillis();
//Update our game
update();
//Empty screen so it can obtain new instance
canvas = null;
//Try locking the canvas for pixel editing on surface
try
{
//Try getting screen
canvas = getHolder().lockCanvas();
//Succeeded
if (canvas != null) synchronized (getHolder())
{
//Actual drawing - our draw function
draw(canvas);
}
} finally
{
//Draw changes
if (canvas != null) getHolder().unlockCanvasAndPost(canvas);
}
//End Frame - 1000/30 means 30 frames per second
int frameTime = System.currentTimeMillis() -startTime;
if (frameTime < 1000/30)
try { Thread.sleep(1000/30 -frameTime); } catch (InterruptedException e){}
}
}
//Last but not least, our function for closing the thread
private void destroyThread()
{
//Stop thread's loop
running = false;
//Try to join thread with UI thread
boolean retry = true;
while (retry)
{
try {thread.join(); retry = false;}
catch (InterruptedException e) {}
}
}
}
I may have made some minor mistakes (probably with case sensitive letters) so feel free to correct these, I wrote the code at once so I didn't have time to test it, it should work flawlessly though.
If you have any more questions, need more explanation or something is not working right let me know!
I'm using animationdrawables in many activities of my app. This is the code to inicializate them:
public void prepareVideo (int resourceBall, String animation, int width, int height ){
imgBall = (ImageView)findViewById(resourceBall);
imgBall.setVisibility(ImageView.VISIBLE);
String resourceNameAnimation = animation;
int id_res = getResources().getIdentifier(resourceNameAnimation, "drawable", getPackageName());
imgBall.setBackgroundResource(id_res);
LayoutParams latoutFrame = imgBall.getLayoutParams();
latoutFrame.width = width;
latoutFrame.height = height;
imgBall.setLayoutParams(latoutFrame);
frame = (AnimationDrawable) imgBall.getBackground();
}
Then i call:
imgBall.post(new StartAnimation());
that call a runnable:
class StartAnimation implements Runnable {
public void run() {
frame.start();
}
}
The problem is i use many animations that are the uses the same xml (frame by frame). I have to call this method in many activities with the same animation. Finally i get a outofmemoryerror. I'm trying to free memory between screens:
for (int i = 0; i < frame.getNumberOfFrames(); ++i){
Drawable frame_rescue = frame.getFrame(i);
if (frame_rescue instanceof BitmapDrawable) {
((BitmapDrawable)frame_rescue).getBitmap().recycle();
}
frame_rescue.setCallback(null);
The problem is that when i try to use a resourde again y get other error:
"Trying to use recycled bitmap"
Anyone know other way of free memory?
I had the same issue, the problem was the runnable.
Making an independent thread to each animation made animationDrawables persist after destroying the activity and clearing views and callbacks.
So starting animations without independent threads lets GC collect unused resources.
My view is a bunch of normal widget and a surfaceview. I don't know why after I get surfaceholder of SurfaceView and use getSurface() again on holder, I will always return null.
Here is my example code :
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view);
}
#Override
public void onResume() {
super.onResume();
surface = (SurfaceView) findViewById(R.id.surfaceView);
this.holder = surface.getHolder();
if (holder.getSurface().isValid()){ // get surface again
Log.i("Notice","Surface holder is valid");
}
else
Log.i("Notice","Surface holder ISNOT valid"); //Always receive this
}
When I see Android document for getSurface() method. This is how it say:
Direct access to the surface object. The Surface may not always be
available -- for example when using a SurfaceView the holder's Surface
is not created until the view has been attached to the window manager
and performed a layout in order to determine the dimensions and screen
position of the Surface. You will thus usually need to implement
Callback.surfaceCreated to find out when the Surface is available for
use.
I don't understand this so much but I know I have miss something. Please explain for me, and tell me about Callback.surfaceCreated means, and how to implement it ?
Thanks :)
You are trying to use surface when it is not available yet. That's alright it is not available at your Activity.onCreate or Activity.onResume methods because actually surface is placed in separate window behind your Activity window and has own lifecycle.
You need to implement SurfaceHolder.Callback to receive events about Surface availability and do drawing from a separate thread. Look at LunarLander project in Android SDK samples folder, it is shown there how to use SurfaceView properly.
Your callback will look something like that:
public class MyCallback implements SurfaceHolder.Callback {
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// you need to start your drawing thread here
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// and here you need to stop it
}
}
And than you need to set this callback to SurfaceHolder:
surface.getHolder().addCallback(new MyCallback());
i found solution, this worked for me, infect error is in choosing correct size , so while using MediaRecorder.setVideoSize() use this method to choose optimal size
private static Size chooseOptimalSize(Size[] choices, int width, int height) {
Size bigEnough = null;
int minAreaDiff = Integer.MAX_VALUE;
for (Size option : choices) {
int diff = (width*height)-(option.getWidth()*option.getHeight()) ;
if (diff >=0 && diff < minAreaDiff &&
option.getWidth() <= width &&
option.getHeight() <= height) {
minAreaDiff = diff;
bigEnough = option;
}
}
if (bigEnough != null) {
return bigEnough;
} else {
Arrays.sort(choices,new CompareSizeByArea());
return choices[0];
}
}
To use a SurfaceView for drawing a 2D game in Android, I use this in the main activity's onCreate():
setContentView(new GameView(this));
Which is a reference to this class:
public class GameView extends SurfaceView implements SurfaceHolder.Callback
Additionally, I have a thread with its run() function:
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_panel.updatePhysics();
_panel.onDraw(c);
}
}
finally { // when exception is thrown above we may not leave the surface in an inconsistent state
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
In updatePhysics() I do some calculations. They are more complex than this simple example, of course, but work the same way:
public void updatePhysics() {
GraphicObject.Coordinates coord;
GraphicObject.Speed speed;
for (GraphicObject graphic : _allElements) {
coord = graphic.getCoordinates();
speed = graphic.getSpeed();
coord.setX(coord.getX() + speed.getX());
coord.setY(coord.getY() + speed.getY());
...
}
}
And in onDraw(), I draw everything to the canvas:
#Override
public void onDraw(Canvas canvas) {
canvas.drawBitmap(BITMAP, xPos, yPos, null);
...
}
This works fine - everything. And when I tested it on my device, it looked pretty good. But when I gave it to someone else and he did a test game, the objects were moving much faster! Why is this so? Because the thread calls updatePhysics() as often as possible which means that fast devices call this function more often?
How can I prevent this and make the game equally fast on all devices? Something like this?
private long lastRun = System.currentTimeMillis();
public void updatePhysics() {
long millisPassed = System.currentTimeMillis()-lastRun;
...
float newCoord = (coord.getX() + speed.getX()) * millisPassed / 33;
coord.setX(newCoord);
...
}
Thanks for your help!
If you can, use the time directly to calculate all your physics. That would usually work best.
If you have no way to calculate based on time because what you are doing that is just step based and you know that generating the next step does not take much time then you have another option.
You create two threads. The first one advances the state at a fixed rate (and you have to be sure that this works on slow devices at that rate too). The second one takes the current state is sees and draws that. Now the second thread can be as slow as it wants because it simply skips some states (or draws the same state several times if it is faster).
Small example below has one thread that advances some state object and replaces the reference each time so the consuming thread does not need to worry that it's state object gets modified
class GameState {
private int state = 0;
public GameState advanceState() {
GameState result = new GameState();
result.state = this.state + 1;
return result;
}
}
class SurfaceViewImplementation extends SurfaceView {
// the current state
volatile GameState mState = new GameState();
void somewhere() {
Thread fastProducer = new Thread(new Runnable() {
private static final long MAX_WAIT = 1000 / 60;
#Override
public void run() {
while (!Thread.interrupted()) {
long timeBefore = SystemClock.currentThreadTimeMillis();
GameState newState = mState.advanceState();
mState = newState;
long timeAfter = SystemClock.currentThreadTimeMillis();
long timeSpent = timeAfter - timeBefore;
SystemClock.sleep(Math.max(0, MAX_WAIT - timeSpent));
}
}
});
fastProducer.start();
Thread slowConsumer = new Thread(new Runnable() {
#Override
public void run() {
while (!Thread.interrupted()) {
GameState currentState = mState;
longRunningDraw(currentState);
}
}
});
slowConsumer.start();
}
}
That will still fail to give you a speed independant result if the producing thread can't run at the desired rate.
I would save the time when I start rendering the frame(in your case is updatePhysics()), and then next time I would got to this point, I know how much time pass, if it's to fast you can use Thread.Sleep();
I've got 30+ single bitmaps (320x240 pixels) that I would like to display one after another in full screen on Android devices resulting in an animation. Currently I implemented the animation using an ImageView and a Timer that sets the next frame and then sends a message that will apply the next frame. The resulting frame rate is very low: < 2 fps.
The timer:
animationTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Drawable frame = getNextFrame();
if (frame != null) {
Message message = animationFrameHandler.obtainMessage(1, frame);
animationFrameHandler.sendMessage(message);
}
}
}, 0, (int) (1000.0d / fps));
The handler:
final Handler animationFrameHandler = new Handler() {
#Override
public void handleMessage(Message message) {
setImageDrawable((Drawable) message.obj);
}
};
Since I want to achieve frame rates up to 30 fps I have to make use of another mechanism and heard of Canvas.drawBitmapMesh() and OpenGL.
If possible I would like to avoid using OpenGL.
Thank you very sharing your experiences!
My now working approach is the following:
Before starting the animation, load every frame into a List<Bitmap>. Important: Call System.gc() if you're getting OutOfMemoryErrors – that really helps loading more bitmaps into the memory. Then have a thread running that posts the next frame to a View instance that then update it's canvas.
Loading the frames and starting the animation
// Loading the frames before starting the animation
List<Bitmap> frames = new ArrayList<Bitmap>();
for (int i = 0; i < 30; i++) {
// Load next frame (e. g. from drawable or assets folder)
frames.add(...);
// Do garbage collection every 3rd frame; really helps loading all frames into memory
if (i %% 3 == 0) {
System.gc();
}
}
// Start animation
frameIndex = 0;
animationThread.start();
Thread that applies the next frame
private final class AnimationThread extends Thread {
#Override
public void run() {
while (!isInterrupted()) {
// Post next frame to be displayed
animationView.postFrame(frames.get(frameIndex));
// Apply next frame (restart if last frame has reached)
frameIndex++;
if (frameIndex >= frames.size()) {
frameIndex = 0;
}
try {
sleep(33); // delay between frames in msec (33 msec mean 30 fps)
} catch (InterruptedException e) {
break;
}
}
}
}
The animation view
class AnimationView extends View {
Bitmap frame = null;
public void postFrame(Bitmap frame) {
Message message = frameHandler.obtainMessage(0, frame);
frameHandler.sendMessage(message);
}
protected final Handler frameHandler = new Handler() {
#Override
public void handleMessage(Message message) {
if (message.obj != null) {
frame = (Bitmap) message.obj;
} else {
frame = null;
}
invalidate();
}
}
#Override
protected void onDraw(Canvas canvas) {
if (frame == null) return;
canvas.drawARGB(0, 0, 0, 0);
canvas.drawBitmap(frame, null, null, null);
}
}
You should look at the FrameAnimation class; http://developer.android.com/guide/topics/graphics/2d-graphics.html#frame-animation to do frame animation with Androids animation.
Though that might still be too slow.
The other alternative if you don't want to use OpenGL ES is to draw to the Canvas as you've mentioned. But just use .drawBitmap, not the drawBitmapMesh. Create a SurfaceView, which has a thread, that thread should draw on your Canvas at whatever interval you want.
It's pretty straightforward, just read the Android docs, the information is all there.
I'll let someone else go into the best way of doing this but one thing that immediately jumps to mind from your post that isn't helping is using TimerTask is a terrible way to do this and is not meant for animation.
Probably won't help with performance, but if those bitmaps are resources you might want to consider using an AnimationDrawable. If not, try to extend Drawable and implement the Animatable interface. Views already have built-in support for animating drawables, no need to use a handler for that.
One way to improve performance might be to match the bit-depth of the drawables to those of your current window. Romain Guy did a keynote on this and animations in general once: http://www.youtube.com/watch?v=duefsFTJXzc