Im creating a game in android, and I noticed that the game has a memory leak. Iv was able to isolate a memory leak in a smaller application so that I can test well and decide how to fix it.
The application uses the surface view for its view and has a stream attached to it to make the entire drawing on the screen. A memory leak occurs when I start a new action and close the one that is currently in use. I see this when I do a memory dump in my test application, since everything that it does is open and closes activity (activity a → activity b → activity a). Iv didn’t have enough ideas on how I can fix this, because I tried to nullify all my links that I create for the view (inside the stream), iv tried to remove the callback from the viewview when I destroy the view, and also inside this activity, it seems, does not matter.
MemoryLeakActivity.java
package memory.leak; import memory.leak.view.MemoryLeak; import android.app.Activity; import android.os.Bundle; public class MemoryLeakActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new MemoryLeak(this)); } }
MemoryLeakViewThread.java
package memory.leak.thread; import memory.leak.view.MemoryLeak; import android.view.SurfaceHolder; import android.graphics.Canvas; public class MemoryLeakViewThread extends Thread { private MemoryLeak view; private boolean run =false; public MemoryLeakViewThread(MemoryLeak view) { this.view =view; } public void setRunning(boolean run) { this.run =run; } @Override public void run() { Canvas canvas =null; SurfaceHolder holder =this.view.getHolder(); while(this.run) { canvas =holder.lockCanvas(); if(canvas !=null) { this.view.onDraw(canvas); holder.unlockCanvasAndPost(canvas); } } holder =null; this.view =null; } }
Memoryleak.java
package memory.leak.view; import memory.leak.TestActivity; import memory.leak.thread.MemoryLeakViewThread; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Color; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.GestureDetector.OnGestureListener; public class MemoryLeak extends SurfaceView implements SurfaceHolder.Callback, OnGestureListener { private GestureDetector gesture; private MemoryLeakViewThread vThread; private Context context; public MemoryLeak(Context context) { super(context); this.getHolder().addCallback(this); this.vThread =new MemoryLeakViewThread(this); this.gesture =new GestureDetector(this); this.context =context; } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} public void surfaceCreated(SurfaceHolder holder) { if(!this.vThread.isAlive()) { this.vThread =new MemoryLeakViewThread(this); this.vThread.setRunning(true); this.vThread.start(); } } public void surfaceDestroyed(SurfaceHolder holder) { boolean retry = true; if(this.vThread.isAlive()) { this.vThread.setRunning(false); while(retry) { try { this.vThread.join(); retry =false; } catch(Exception ee) {} } } this.vThread =null; this.context =null; } public boolean onTouchEvent(MotionEvent event) { return this.gesture.onTouchEvent(event); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { } @Override public void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); } @Override public boolean onDown(MotionEvent e) { return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } @Override public void onLongPress(MotionEvent e) {} @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public void onShowPress(MotionEvent e) {} @Override public boolean onSingleTapUp(MotionEvent e) { Intent helpScreenIntent =new Intent(this.context, TestActivity.class); this.context.startActivity(helpScreenIntent); if (this.context instanceof Activity) ((Activity) this.context).finish(); return true; } }
TestActivity.java
package memory.leak; import memory.leak.view.Test; import android.app.Activity; import android.os.Bundle; public class TestActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new Test(this)); } }
TestViewThread.java
package memory.leak.thread; import memory.leak.view.Test; import android.view.SurfaceHolder; import android.graphics.Canvas; public class TestViewThread extends Thread { private Test panel; private boolean run =false; public TestViewThread(Test panel) { this.panel =panel; } public void setRunning(boolean run) { this.run =run; } @Override public void run() { Canvas canvas =null; SurfaceHolder holder =this.panel.getHolder(); while(this.run) { canvas =holder.lockCanvas(); if(canvas !=null) { this.panel.onDraw(canvas); holder.unlockCanvasAndPost(canvas); } } holder =null; this.panel =null; } }
Test.java
package memory.leak.view; import memory.leak.MemoryLeakActivity; import memory.leak.thread.TestViewThread; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Color; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.GestureDetector.OnGestureListener; public class Test extends SurfaceView implements SurfaceHolder.Callback, OnGestureListener { private GestureDetector gesture; private TestViewThread vThread; private Context context; public Test(Context context) { super(context); this.getHolder().addCallback(this); this.vThread =new TestViewThread(this); this.gesture =new GestureDetector(this); this.context =context; } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} public void surfaceCreated(SurfaceHolder holder) { if(!this.vThread.isAlive()) { this.vThread =new TestViewThread(this); this.vThread.setRunning(true); this.vThread.start(); } } public void surfaceDestroyed(SurfaceHolder holder) { boolean retry = true; if(this.vThread.isAlive()) { this.vThread.setRunning(false); while(retry) { try { this.vThread.join(); retry =false; } catch(Exception ee) {} } } this.vThread =null; this.context =null; } public boolean onTouchEvent(MotionEvent event) { return this.gesture.onTouchEvent(event); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { } @Override public void onDraw(Canvas canvas) { canvas.drawColor(Color.RED); } @Override public boolean onDown(MotionEvent e) { return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } @Override public void onLongPress(MotionEvent e) {} @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public void onShowPress(MotionEvent e) {} @Override public boolean onSingleTapUp(MotionEvent e) { Intent helpScreenIntent =new Intent(this.context, MemoryLeakActivity.class); this.context.startActivity(helpScreenIntent); if (this.context instanceof Activity) ((Activity) this.context).finish(); return true; } }
- Edit-- I made changes to the Destroyed view class (SurfaceHolder holder) so that it sets the view that the stream should be null when the stream is told to stop. The changes I made
public void surfaceDestroyed(SurfaceHolder holder) { boolean retry = true; if(this.vThread.isAlive()) { this.vThread.setRunning(false); while(retry) { try { this.vThread.join(); retry =false; } catch(Exception ee) {} } this.vThread.setRunning(false, null); } this.vThread =null; this.context =null; this.gesture =null; }
you also need to change the surfaceCreated (SurfaceHolder holder) method to
public void surfaceCreated(SurfaceHolder holder) { if(!this.vThread.isAlive()) { this.vThread =new MemoryLeakViewThread(); this.vThread.setRunning(true, this); this.vThread.start(); } }
then in the thread class we need to change the following
public MemoryLeakViewThread() { } public void setRunning(boolean run) { this.run =run; } public void setRunning(boolean run, MemoryLeak view) { this.run =run; this.view =view; }
So the problem seemed to be fixed, the only problem is that the thread seems to remain in memory due to the thread class and thread group. But I think this may be due to the debugger.