2011-08-29 133 views
8

在Android中創建一個遊戲,我注意到遊戲有內存泄漏。 Iv設法將內存泄漏隔離到一個更小的應用程序中,這樣我就可以很好地嘗試和解決問題,以及如何解決它。Android Surfaceview線程和內存泄漏

該應用程序爲其視圖使用了一個面視圖,並且附加了一個線程以執行所有繪圖到屏幕。內存泄漏發生在我開始一個新的活動並關閉當前正在使用的一個。當我在測試應用程序上執行內存轉儲時,我可以看到這一點,因爲它只是打開並關閉活動(活動a - >活動b - >活動a)。 Iv種類的想法,我怎麼能解決這個問題,因爲我試圖將我創建的所有引用視爲null(我在線程內創建),iv當我銷燬視圖時嘗試從surfaceview中移除回調,還在活動內部,似乎沒有任何區別。

MemoryLeakActivity.java

package memory.leak; 

import memory.leak.view.MemoryLeak; 
import android.app.Activity; 
import android.os.Bundle; 

public class MemoryLeakActivity extends Activity { 
    /** Called when the activity is first created. */ 
    @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 { 
    /** Called when the activity is first created. */ 
    @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; 
    } 
} 

- 編輯 - 我修改了視圖類到它surfaceDestroyed(SurfaceHolder保持器),使得它將設置視圖當線程被告知停止時線程必須爲空。我所做的修改將

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; 
    } 

你也需要改變surfaceCreated(SurfaceHolder持有人)的方法來

public void surfaceCreated(SurfaceHolder holder) { 
     if(!this.vThread.isAlive()) { 
      this.vThread =new MemoryLeakViewThread(); 
      this.vThread.setRunning(true, this); 
      this.vThread.start(); 
     } 
    } 

然後在線程類我們需要改變以下

public MemoryLeakViewThread() { 
    } 

    public void setRunning(boolean run) { 
     this.run =run; 
    } 

    public void setRunning(boolean run, MemoryLeak view) { 
     this.run =run; 
     this.view =view; 
    } 

通過這樣做似乎可以解決問題,現在唯一的問題是由於線程類和線程組,線程似乎留在內存中。但我想這可能是由於調試器。

+0

添加異常和堆棧跟蹤將有助於瞭解問題。 – Arslan

+0

Iv設法通過在設置線程的運行狀態時傳遞視圖來解決大部分內存泄漏問題,然後在將運行狀態設置爲false時將其設置爲空。這已經從內存中移除了活動和視圖,現在唯一停留的是似乎被阻塞在線程組類中的線程。我記得讀過關於線程的東西會卡在那裏,如果沒有開始,我現在就無法找到它的鏈接。 – Spider

+0

http://code.google.com/p/android/issues/detail?id=7979這就是它。至於堆棧跟蹤,你想在哪裏添加異常?它不會拋出任何東西,它最終會在內存耗盡時發生,但由於我沒有在測試應用程序中使用太多的內存,所以需要一段時間。我使用MAT來分析一個堆轉儲,所以我可以看到什麼是卡在內存 – Spider

回答

5

當你在onSurfaceCreated中創建它時,你不應該在構造函數中創建新的線程。比較你的代碼和我的例子:How can I use the animation framework inside the canvas?

+0

嘗試了一下你所說的話,並將創建的線程從構造函數方法中移出,並將其放在了Surfacecreated方法中。這阻止了線程卡在內存中。 :D我所有的記憶問題現在都已修復。 – Spider

-1

正如你可以在這裏看到:

http://developer.android.com/resources/articles/avoiding-memory-leaks.html

開始在Android的內存泄漏最簡單的方法是改爲通過視圖的構造整個活動的應用程序上下文。你有沒有嘗試改變這一行:

setContentView(new MemoryLeak(this)); 

到這一個:

setContentView(new MemoryLeak(Context.getApplicationContext())); 

希望它有幫助。

+1

好吧,你顯然沒有嘗試過,或者你會看到當你嘗試編譯靜態引用到非靜態方法會發生什麼。 – NickT