Android 에서 흔히 볼 수 있 는 몇 가지 메모리 누 출 소결

배경
최근 프로젝트 의 버 전 교체 과정 에서 메모리 문제 에 대한 에피소드 가 생 긴 다음 에 자신 이 앱 이 실 행 될 때 메모리 크기 에 대한 문 제 를 최적화 하 는 데 시간 을 들 였 습 니 다.이에 정 리 를 해서 여러분 과 공유 하 겠 습 니 다.
소개
안 드 로 이 드 프로그램 개발 에 서 는 한 대상 이 더 이상 사용 할 필요 가 없습니다.회수 되 어야 할 때 다른 대상 이 인용 을 가지 고 있어 서 회수 되 지 못 합 니 다.이 로 인해 회수 되 어야 할 대상 이 회수 되 지 못 하고 메모리 에 머 물 러 메모리 누 출 이 발생 합 니 다.메모리 누 출 은 어떤 영향 이 있 습 니까?그것 은 응용 프로그램 OOM 을 만 드 는 주요 원인 중의 하나 이다.안 드 로 이 드 시스템 이 모든 프로그램 에 분 배 된 메모리 에 한계 가 있 기 때문에 한 응용 프로그램 에서 발생 하 는 메모리 누 출 이 비교적 많 을 때 응용 에 필요 한 메모리 가 이 시스템 에서 분 배 된 메모리 한 도 를 초과 하 는 것 을 피하 기 어렵다.이 로 인해 메모리 가 넘 쳐 서 Crash 를 사용 하 게 된다.메모리 누 출 의 원인 과 영향 을 알 게 된 후에 우리 가 해 야 할 일 은 흔히 볼 수 있 는 메모리 누 출 을 파악 하고 앞으로 안 드 로 이 드 프로그램 개발 에서 가능 한 한 피 하 는 것 이다.
1.단일 사례 로 인 한 메모리 유출
안 드 로 이 드 의 단일 모드 는 개발 자 들 의 사랑 을 받 지만 잘못 사용 하면 메모리 가 새 어 나 갈 수 있 습 니 다.단일 사례 의 정적 특성 으로 인해 단일 사례 의 생명 주기 와 응용 수명 주기 가 똑 같이 길 기 때문에 한 대상 이 사용 할 필요 가 없고 단일 대상 이 이 대상 의 인용 을 가지 고 있다 면 이 대상 은 정상적으로 회수 되 지 못 해 메모리 가 새 게 된다 는 것 을 의미한다.

public class SingleInstance {

  private static SingleInstance instance;
  private Context context;

  private SingleInstance(Context context) {
    this.context = context;
  }

  public synchronized static SingleInstance getInstance(Context context) {
    if (instance != null) {
      instance = new SingleInstance(context);
    }
    return instance;
  }
}

이것 은 일반적인 단일 모드 입 니 다.정적 변수의 가장 큰 특징 이 무엇 인지 모두 가 알 고 있 습 니 다.상주 메모리 입 니 다.즉,앱 의 프로 세 스 가 죽 이지 않 았 다 면 메모리 에 있 었 습 니 다.이 사례 를 만 들 때 하나의 Context 가 들 어 와 야 하기 때문에 이 Context 의 생명주기 의 길 이 는 매우 중요 하 다.만약 에 들 어 오 는 것 이 Activity 의 Context 라면 이 Context 에 대응 하 는 Activity 가 물 러 날 때 이 Context 는 Activity 의 생명주기 와 똑 같이 길 기 때문이다(Activity 가 간접 적 으로 Context 에 계승 된다).따라서 현재 Activity 가 종료 되 었 을 때 메모리 가 회수 되 지 않 습 니 다.단일 대상 이 이 Activity 의 인용 을 가지 고 있 기 때 문 입 니 다.
그래서 정확 한 단 례 는 이런 자 세 를 취해 야 한다.

public class SingleInstance {

  private static SingleInstance instance;
  private Context context;

  private SingleInstance(Context context) {
    this.context = context.getApplicationContext();
  }

  public synchronized static SingleInstance getInstance(Context context) {
    if (instance != null) {
      instance = new SingleInstance(context);
    }
    return instance;
  }
}

이렇게 하면 어떤 Context 가 들 어 오 든 최종 적 으로 애플 리 케 이 션 의 Context 를 사용 할 것 이 며,하나의 수명 주기 가 응용 과 똑 같이 길 어서 메모리 누 출 을 방지 할 수 있다.
2.비 정적 내부 클래스(예 를 들 어 내부 클래스,익명 내부 클래스)가 정적 인 스 턴 스 로 인 한 메모리 누 출 을 만 듭 니 다.
때때로 우 리 는 잦 은 Activity 를 시작 할 때 같은 데이터 자원 을 반복 적 으로 만 드 는 것 을 피하 기 위해 다음 과 같은 쓰기 가 나타 날 수 있 습 니 다.

public class MainActivity extends AppCompatActivity {

  private InnerClass innerClass;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if(innerClass == null){
      innerClass = new InnerClass();
    }
  }

  class InnerClass{

  }
}
이렇게 해서 Activity 내부 에 비정 상 내부 클래스 의 단일 예 를 만 들 었 습 니 다.Activity 를 시작 할 때마다 이 단일 사례 의 데 이 터 를 사용 합 니 다.그러면 자원 의 중복 생 성 을 피 할 수 있 지만 이런 쓰기 방법 은 메모리 누 출 을 초래 할 수 있 습 니 다.비정 상 내부 클래스 는 기본적으로 외부 클래스 의 인용 이 있 고 이 비정 상 내부 클래스 를 사용 하여 정적 인 인 스 턴 스 를 만 들 었 기 때 문 입 니 다.이 인 스 턴 스 의 수명 주기 가 응용 과 같이 길 기 때문에 이 정적 인 스 턴 스 는 이 Activity 의 인용 을 가지 고 있어 Activity 의 메모리 자원 을 정상적으로 회수 하지 못 합 니 다.
정확 한 방법 은 이 내부 클래스 를 정적 내부 클래스 로 설정 하거나 이 내부 클래스 를 추출 하여 하나의 예 로 밀봉 하 는 것 입 니 다.Context 를 사용 하려 면 applicationContext 를 사용 하 십시오.
3.Handler 로 인 한 메모리 유출
Handler 의 사용 으로 인 한 메모리 누 출 문 제 는 가장 흔히 볼 수 있 습 니 다.평소에 네트워크 작업 을 처리 하거나 리 셋 을 요청 하 는 등 api 는 Handler 를 통 해 처리 해 야 합 니 다.Handler 의 사용 코드 에 대해 규범 에 맞지 않 으 면 메모리 누 출 이 발생 할 수 있 습 니 다.다음 과 같은 예 입 니 다.

public class MainActivity extends AppCompatActivity {

  private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      //...

    }
  };

  @Override

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    loadData();

  }

  private void loadData(){
    //...request
    Message message = Message.obtain();
    mHandler.sendMessage(message);
  }
}

이러한 Handler 를 만 드 는 방식 은 메모리 누 출 을 초래 할 수 있 습 니 다.mHandler 는 Handler 의 비정 상 익명 내부 클래스 의 인 스 턴 스 이기 때문에 외부 클래스 Activity 의 인용 을 가지 고 있 습 니 다.우 리 는 메시지 대기 열 이 Looper 스 레 드 에서 메 시 지 를 계속 문의 하고 처리 하 는 것 을 알 고 있 습 니 다.그러면 이 Activity 가 종료 되 었 을 때 메시지 대기 열 에 처리 되 지 않 은 메시지 가 있 거나 메 시 지 를 처리 하고 있 습 니 다.한편,메시지 대기 열 에 있 는 Message 는 mHandler 인 스 턴 스 의 인용 을 가지 고 있 고 mHandler 는 Activity 의 인용 을 가지 고 있 기 때문에 이 Activity 의 메모리 자원 을 제때에 회수 하지 못 하고 메모리 누 출 을 야기 하기 때문에 다른 방법 은'소프트 참조'입 니 다.

public class MainActivity extends AppCompatActivity {

  private MyHandler mHandler = new MyHandler(this);
  private TextView mTextView ;

  private static class MyHandler extends Handler {

    private WeakReference<Context> reference;
    public MyHandler(Context context) {
      reference = new WeakReference<>(context);
    }

    @Override
    public void handleMessage(Message msg) {

      MainActivity activity = (MainActivity) reference.get();

      if(activity != null){
        activity.mTextView.setText("");
      }
    }
  }



  @Override

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTextView = (TextView)findViewById(R.id.textview);
    loadData();
  }



  private void loadData() {

    //...request
    Message message = Message.obtain();
    mHandler.sendMessage(message);
  }
}

정적 Handler 내부 클래스 를 만 든 다음 Handler 가 가지 고 있 는 대상 에 대해 약 한 인용 을 사용 합 니 다.그러면 회수 할 때 Handler 가 가지 고 있 는 대상 을 회수 할 수 있 습 니 다.그러면 Activity 누 출 을 피 할 수 있 지만 Looper 스 레 드 의 메시지 큐 에서 처리 해 야 할 메시지 가 있 을 수 있 습 니 다.따라서 저 희 는 Activity 의 Destroy 나 Stop 에서 메시지 큐 에 있 는 메 시 지 를 제거 해 야 합 니 다.더 정확 한 방법 은 다음 과 같다.

public class MainActivity extends AppCompatActivity {

  private MyHandler mHandler = new MyHandler(this);
  private TextView mTextView ;
  private static class MyHandler extends Handler {
    private WeakReference<Context> reference;
    public MyHandler(Context context) {
      reference = new WeakReference<>(context);
    }

    @Override
    public void handleMessage(Message msg) {
      MainActivity activity = (MainActivity) reference.get();
      if(activity != null){
        activity.mTextView.setText("");
      }
    }
  }



  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTextView = (TextView)findViewById(R.id.textview);
    loadData();
  }


//         
  private void loadData() {
    //...request
    Message message = Message.obtain();
    mHandler.sendMessage(message);
  }


  @Override
  protected void onDestroy() {
    super.onDestroy();
    //         
    mHandler.removeCallbacksAndMessages(null);
  }
}

mHandler.removeCallbacksAndMessages(null)사용 하기;메시지 큐 의 모든 메시지 와 모든 Runnable 을 제거 합 니 다.물론 mHandler.removeCallbacks()도 사용 할 수 있 습 니 다.또는 mHandler.removeMessages();지정 한 Runnable 과 Message 를 제거 합 니 다.
물론 간단 하 죠.그냥 그 럴 수도 있어 요.

 @Override
  protected void onDestroy() {
    super.onDestroy();
    //         
    mHandler.removeCallbacksAndMessages(null);
  }
테스트
4.스 레 드 로 인 한 메모리 유출
스 레 드 로 인 한 메모리 누 출 도 평소에 흔히 볼 수 있 습 니 다.다음 과 같은 두 가지 예 는 모든 사람 이 이렇게 썼 을 수 있 습 니 다.

//――――――test1
    new AsyncTask<Void, Void, Void>() {
      @Override
      protected Void doInBackground(Void... params) {
        SystemClock.sleep(10000);
        return null;
      }
    }.execute();

//――――――test2
    new Thread(new Runnable() {
      @Override
      public void run() {
        SystemClock.sleep(10000);
      }
    }).start();
위의 비동기 작업 과 Runnable 은 모두 익명 의 내부 클래스 이기 때문에 현재 Activity 에 대해 암시 적 인 참조 가 있 습 니 다.만약 에 Activity 가 소각 하기 전에 작업 이 완성 되 지 않 으 면 Activity 의 메모리 자원 을 회수 하지 못 하고 메모리 가 누 출 될 것 입 니 다.정확 한 방법 은 정적 내부 클래스 를 사용 하 는 것 입 니 다.다음 과 같 습 니 다.

static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
    private WeakReference<Context> weakReference;
    public MyAsyncTask(Context context) {
      weakReference = new WeakReference<>(context);
    }


    @Override
    protected Void doInBackground(Void... params) {
      SystemClock.sleep(10000);
      return null;
    }



    @Override
    protected void onPostExecute(Void aVoid) {
      super.onPostExecute(aVoid);
      MainActivity activity = (MainActivity) weakReference.get();
      if (activity != null) {
        //...
      }
    }
  }

  static class MyRunnable implements Runnable{
    @Override
    public void run() {
      SystemClock.sleep(10000);
    }
  }
//――――――

  new Thread(new MyRunnable()).start();

  new MyAsyncTask(this).execute();

이렇게 하면 Activity 의 메모리 자원 유출 을 피 할 수 있 습 니 다.물론 Activity 가 소각 할 때 도 해당 하 는 작업 인 AsyncTask:cancel()을 취소 하고 작업 이 배경 에서 자원 을 낭비 하지 않도록 해 야 합 니 다.
5.자원 이 닫 히 지 않 아 발생 하 는 메모리 유출
BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap 등 자원 을 사용 한 경우 Activity 가 소각 할 때 즉시 닫 거나 로그아웃 해 야 합 니 다.그렇지 않 으 면 이 자원 들 이 회수 되 지 않 아 메모리 가 새 지 않 습 니 다.
6.WebView,또는 바 이 두,고 덕 지도의 MapView 로 인 한 메모리 누 출 은 제 글 을 참고 할 수 있 습 니 다.
WebView 로 인 한 메모리 누 출:https://www.jb51.net/article/79372.htm
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기