수색…


일반적인 메모리 누수 및 해결 방법

1. 컨텍스트 수정 :

적절한 컨텍스트를 사용해보십시오 : 예를 들어 토스트가 하나의 getApplicationContext() 아닌 여러 액티비티에서 볼 수 있기 때문에 토스트를 위해 getApplicationContext() 를 사용하고, 액티비티가 종료 된 후에도 서비스가 계속 실행될 수 있으므로 다음을 사용하여 서비스를 시작하십시오.

Intent myService = new Intent(getApplicationContext(), MyService.class);

다음 표를 사용하여 적절한 컨텍스트에 대한 빠른 가이드로 사용하십시오. 여기에 이미지 설명을 입력하십시오.

문맥에 대한 원본 기사는 여기에 있습니다 .

2. Context에 대한 정적 참조

심각한 메모리 누수 실수는 View 대한 정적 참조를 유지하는 것입니다. 모든 View 에는 Context 대한 내부 참조가 있습니다. 즉, 전체 뷰 계층 구조가있는 이전 활동은 앱이 종료 될 때까지 가비지 수집되지 않습니다. 화면을 회전 할 때 앱이 두 번 메모리에 저장됩니다.

뷰, 컨텍스트 또는 그 자손에 대한 정적 참조가 절대적으로 없는지 확인하십시오.

3. 실제로 서비스를 끝내고 있는지 확인하십시오.

예를 들어 Google 위치 서비스 API를 사용하는 intentService가 있습니다. 그리고 googleApiClient.disconnect(); 를 호출하는 것을 잊었습니다 googleApiClient.disconnect(); :

//Disconnect from API onDestroy()
if (googleApiClient.isConnected()) {
    LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, GoogleLocationService.this);
    googleApiClient.disconnect();
}

4. 이미지 및 비트 맵 사용량 확인 :

Square의 라이브러리 Picasso를 사용하는 경우 .fit() 사용하지 않아 메모리가 누수되는 것으로 나타났습니다. 메모리 공간이 평균 50MB에서 19MB 미만으로 크게 줄어 들었습니다.

Picasso.with(ActivityExample.this)                   //Activity context
                .load(object.getImageUrl())           
                .fit()                                //This avoided the OutOfMemoryError
                .centerCrop()                         //makes image to not stretch
                .into(imageView);

5. 브로드 캐스트 리시버를 사용하는 경우 등록을 취소하십시오.

6. java.util.Observer (관찰자 패턴)를 사용하는 경우 다음을 수행하십시오.

deleteObserver(observer); 를 사용하십시오 deleteObserver(observer);

AsyncTask로 활동 유출을 피하십시오

주의 사항 : AsyncTask에는 여기에 설명 된 메모리 누수와 다른 점이 많이 있습니다. 따라서이 API를주의 깊게 읽거나, 그 의미를 완전히 이해하지 못하는 경우이 API를 사용하지 마십시오. 많은 대안 (Thread, EventBus, RxAndroid 등)이 있습니다.

AsyncTask 일반적인 실수 중 하나는 호스트 Activity (또는 Fragment )에 대한 강력한 참조를 포착하는 것입니다.

class MyActivity extends Activity {
  private AsyncTask<Void, Void, Void> myTask = new AsyncTask<Void, Void, Void>() {
    // Don't do this! Inner classes implicitly keep a pointer to their
    // parent, which in this case is the Activity!
  }
}

AsyncTask 는 상위 Activity 쉽게 유지할 수 있기 때문에 문제가됩니다. 예를 들어 작업이 실행되는 동안 구성이 변경되는 경우입니다.

이 작업을 수행하는 올바른 방법은 작업 a를하는 것입니다 static 부모를 캡처하지 않는 클래스 및 지주 약한 참조 호스트로 Activity :

class MyActivity extends Activity {
  static class MyTask extends AsyncTask<Void, Void, Void> {
    // Weak references will still allow the Activity to be garbage-collected
    private final WeakReference<MyActivity> weakActivity;

    MyTask(MyActivity myActivity) {
      this.weakActivity = new WeakReference<>(myActivity);
    }

    @Override
    public Void doInBackground(Void... params) {
      // do async stuff here
    }

    @Override
    public void onPostExecute(Void result) {
      // Re-acquire a strong reference to the activity, and verify
      // that it still exists and is active.
      MyActivity activity = weakActivity.get();
      if (activity == null
          || activity.isFinishing()
          || activity.isDestroyed()) {
        // activity is no longer valid, don't do anything!
        return;
      }

      // The activity is still valid, do main-thread stuff here
    }
  }
}

활동의 익명 콜백

익명 클래스를 만들 때마다 부모 클래스에 대한 암시 적 참조가 유지됩니다. 그래서 쓸 때 :

public class LeakyActivity extends Activity
{

...

    foo.registerCallback(new BarCallback() 
    {            
        @Override
        public void onBar() 
        {
            // do something                
        }            
    });
}

실제로 LeakyActivity 인스턴스에 대한 참조를 foo에 보내고 있습니다. 사용자가 LeakyActivity에서 멀리 이동할 때이 참조는 LeakyActivity 인스턴스가 가비지 수집되는 것을 막을 수 있습니다. 이것은 액티비티가 전체 뷰 계층 구조에 대한 참조를 보유하므로 메모리의 큰 객체입니다.

이 누출을 피하는 방법 :

물론 액티비티 전체에서 익명 콜백을 사용하지 않아도됩니다. 또한 활동주기와 관련하여 콜백을 모두 등록 취소 할 수 있습니다. 이렇게 :

public class NonLeakyActivity extends Activity
{
    private final BarCallback mBarCallback = new BarCallback() 
    {            
        @Override
        public void onBar() 
        {
            // do something                
        }            
    });

    @Override
    protected void onResume()
    {
        super.onResume();
        foo.registerCallback(mBarCallback);
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        foo.unregisterCallback(mBarCallback);
    }
}

정적 클래스의 Activity Context

종종 안드로이드의 클래스 중 일부를 유틸리티 클래스를 사용하기 쉽게 포장하기를 원할 것입니다. 이러한 유틸리티 클래스는 종종 Android OS 또는 앱의 리소스에 액세스하기위한 컨텍스트가 필요합니다. 이에 대한 일반적인 예는 SharedPreferences 클래스의 래퍼입니다. Androids 공유 환경 설정에 액세스하려면 다음을 작성해야합니다.

context.getSharedPreferences(prefsName, mode);

그래서 다음과 같은 클래스를 만들려고 유혹받을 수 있습니다 :

public class LeakySharedPrefsWrapper
{
    private static Context sContext;

    public static void init(Context context)
    {
        sContext = context;
    }

    public int getInt(String name,int defValue)
    {
        return sContext.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
    }
}

이제 활동 컨텍스트로 init() 을 호출하면 LeakySharedPrefsWrapper가 활동에 대한 참조를 유지하여 가비지 수집을 방지합니다.

피해야 할 방법 :

정적 도우미 함수를 호출 할 때 context.getApplicationContext(); 사용하여 응용 프로그램 컨텍스트를 보낼 수 있습니다 context.getApplicationContext();

정적 도우미 함수를 작성할 때, 주어진 컨텍스트에서 응용 프로그램 컨텍스트를 추출 할 수 있습니다 (응용 프로그램 컨텍스트에서 getApplicationContext () 호출은 응용 프로그램 컨텍스트를 리턴합니다). 래퍼에 대한 수정은 간단합니다.

public static void init(Context context)
{
    sContext = context.getApplicationContext();
}

응용 프로그램 컨텍스트가 사용 사례에 적절하지 않은 경우 각 유틸리티 함수에 Context 매개 변수를 포함 할 수 있으므로 이러한 컨텍스트 매개 변수에 대한 참조를 유지하지 않아야합니다. 이 경우 솔루션은 다음과 같이 보입니다.

public int getInt(Context context,String name,int defValue)
{
    // do not keep a reference of context to avoid potential leaks.
    return context.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}

LeakCanary 라이브러리로 메모리 누출 감지

LeakCanary 는 디버그 빌드에서 메모리 누출을 감지하는 오픈 소스 Java 라이브러리입니다.

build.gradle 종속성을 추가하기 build.gradle .

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
 }

그런 다음 Application 클래스에서 :

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }

    LeakCanary.install(this);
  }
}

이제 LeakCanary는 디버그 빌드에서 활동 메모리 누수가 감지되면 자동으로 알림을 표시합니다.

참고 : 릴리스 코드에는 leakcanary-android-no-op 종속성에있는 두 개의 빈 클래스 이외의 LeakCanary에 대한 참조가 포함되지 않습니다.

리스너와 함께 활동 유출 방지

활동에 리스너를 구현하거나 작성하는 경우 항상 리스너가 등록 된 객체의 수명주기에주의하십시오.

사용자가 로그인하거나 로그 아웃 할 때 관심있는 여러 가지 활동 / 단편이있는 응용 프로그램을 생각해보십시오. 이렇게하는 한 가지 방법은 사용자의 상태가 변경 될 때 알림을 받으려면 구독 할 수있는 UserController 의 싱글 톤 인스턴스를 갖는 것입니다.

public class UserController {
    private static UserController instance;
    private List<StateListener> listeners;

    public static synchronized UserController getInstance() {
        if (instance == null) {
            instance = new UserController();
        }
        return instance;
    }

    private UserController() {
        // Init
    }

    public void registerUserStateChangeListener(StateListener listener) {
        listeners.add(listener);
    }

    public void logout() {
        for (StateListener listener : listeners) {
            listener.userLoggedOut();
        }
    }

    public void login() {
        for (StateListener listener : listeners) {
            listener.userLoggedIn();
        }
    }

    public interface StateListener {
        void userLoggedIn();
        void userLoggedOut();
    }
}

다음 두 가지 활동 인 SignInActivity .

public class SignInActivity extends Activity implements UserController.StateListener{

    UserController userController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.userController = UserController.getInstance();
        this.userController.registerUserStateChangeListener(this);
    }

    @Override
    public void userLoggedIn() {
        startMainActivity();
    }

    @Override
    public void userLoggedOut() {
        showLoginForm();
    }

    ...

    public void onLoginClicked(View v) {
        userController.login();
    }
}

그리고 MainActivity :

public class MainActivity extends Activity implements UserController.StateListener{
    UserController userController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.userController = UserController.getInstance();
        this.userController.registerUserStateChangeListener(this);
    }

    @Override
    public void userLoggedIn() {
        showUserAccount();
    }

    @Override
    public void userLoggedOut() {
        finish();
    }

    ...

    public void onLogoutClicked(View v) {
        userController.logout();
    }
}

이 예제에서는 사용자가 로그인 한 다음 다시 로그 아웃 할 때마다 MainActivity 인스턴스가 유출된다는 점에 유의하십시오. UserController#listeners 에서 활동에 대한 참조가 있기 때문에 누수가 발생합니다.

참고 : 익명의 내부 클래스를 리스너로 사용하더라도 활동은 여전히 ​​누수됩니다.

...
this.userController.registerUserStateChangeListener(new UserController.StateListener() {
    @Override
    public void userLoggedIn() {
        showUserAccount();
    }

    @Override
    public void userLoggedOut() {
        finish();
    }
});    
...

익명의 내부 클래스에는 외부 클래스 (이 경우에는 활동)에 대한 암시 적 참조가 있으므로 활동이 계속 누출됩니다. 이것이 외부 클래스의 인스턴스 메소드를 내부 클래스에서 호출 할 수있는 이유입니다. 사실, 외부 클래스에 대한 참조가없는 내부 클래스의 유일한 유형은 정적 내부 클래스입니다.

간단히 말해서, 정적이 아닌 내부 클래스의 모든 인스턴스는이를 생성 한 외부 클래스의 인스턴스에 대한 암시 적 참조를 보유합니다.

UserController#listeners 에서 리스너를 제거하는 메서드를 추가하거나 WeakReference 를 사용하여 UserController#listeners 의 참조를 유지하는 두 가지 방법이 있습니다.

대안 1 : 청취자 제거

새로운 메소드 removeUserStateChangeListener(StateListener listener) 를 만들어 보겠습니다.

public class UserController {

    ...

    public void registerUserStateChangeListener(StateListener listener) {
        listeners.add(listener);
    }

    public void removeUserStateChangeListener(StateListener listener) {
        listeners.remove(listener);
    }

    ...
}

그런 다음 우리는 activity의 onDestroy 메서드에서이 메서드를 호출합니다.

public class MainActivity extends Activity implements UserController.StateListener{
    ...

    @Override
    protected void onDestroy() {
        super.onDestroy();
        userController.removeUserStateChangeListener(this);
    }
}

이 수정으로 사용자가 로그인 및 로그 아웃 할 때 MainActivity 의 인스턴스가 더 이상 유출되지 않습니다. 그러나 문서가 명확하지 않은 경우, UserController 사용하기 시작한 다음 개발자가 활동이 파손될 때 청취자를 등록 해제해야한다는 사실을 놓칠 수도 있습니다. 따라서 이러한 유형의 누출을 피할 수있는 두 번째 방법이 있습니다.

대안 2 : 약한 참조 사용

우선, 약한 참조가 무엇인지 설명하여 시작하겠습니다. 약한 참조는 이름에서 알 수 있듯이 객체에 대한 약한 참조를 유지합니다. 강력한 참조 인 일반 인스턴스 필드와 비교할 때 약한 참조는 가비지 수집기 GC가 개체를 제거하는 것을 중지하지 않습니다. 위 예제에서 UserController WeakReference 를 사용하여 Listener를 참조하면 MainActivity 가 파괴 된 후 MainActivity 가 가비지 수집 될 수 있습니다.

즉, 약한 참조는 GC에이 객체에 대한 강력한 참조가없는 경우 계속 진행하고 제거한다는 것을 GC에 알리는 것입니다.

WeakReference 의 목록을 사용하여 Listener를 추적하도록 UserController 를 수정합시다.

public class UserController {

    ...
    private List<WeakReference<StateListener>> listeners;
    ...

    public void registerUserStateChangeListener(StateListener listener) {
        listeners.add(new WeakReference<>(listener));
    }

    public void removeUserStateChangeListener(StateListener listenerToRemove) {
        WeakReference referencesToRemove = null;
        for (WeakReference<StateListener> listenerRef : listeners) {
            StateListener listener = listenerRef.get();
            if (listener != null && listener == listenerToRemove) {
                referencesToRemove = listenerRef;
                break;
            }
        }
        listeners.remove(referencesToRemove);
    }

    public void logout() {
        List referencesToRemove = new LinkedList();
        for (WeakReference<StateListener> listenerRef : listeners) {
            StateListener listener = listenerRef.get();
            if (listener != null) {
                listener.userLoggedOut();
            } else {
                referencesToRemove.add(listenerRef);
            }
        }
    }

    public void login() {
        List referencesToRemove = new LinkedList();
        for (WeakReference<StateListener> listenerRef : listeners) {
            StateListener listener = listenerRef.get();
            if (listener != null) {
                listener.userLoggedIn();
            } else {
                referencesToRemove.add(listenerRef);
            }
        }
    }
    ...    
}

이 수정을 통해 Listener가 제거되었는지 여부는 중요하지 않습니다. 왜냐하면 UserController 는 모든 리스너에 대한 강력한 참조를 보유하고 있지 않기 때문입니다. 그러나 매번이 상용구 코드를 작성하는 것은 번거로울 수 있습니다. 따라서 WeakCollection 이라는 제네릭 클래스를 만들어 WeakCollection .

public class WeakCollection<T> {
    private LinkedList<WeakReference<T>> list;

    public WeakCollection() {
        this.list = new LinkedList<>();
    }
    public void put(T item){
        //Make sure that we don't re add an item if we already have the reference.
        List<T> currentList = get();
        for(T oldItem : currentList){
            if(item == oldItem){
                return;
            }
        }
        list.add(new WeakReference<T>(item));
    }

    public List<T> get() {
        List<T> ret = new ArrayList<>(list.size());
        List<WeakReference<T>> itemsToRemove = new LinkedList<>();
        for (WeakReference<T> ref : list) {
            T item = ref.get();
            if (item == null) {
                itemsToRemove.add(ref);
            } else {
                ret.add(item);
            }
        }
        for (WeakReference ref : itemsToRemove) {
            this.list.remove(ref);
        }
        return ret;
    }

    public void remove(T listener) {
        WeakReference<T> refToRemove = null;
        for (WeakReference<T> ref : list) {
            T item = ref.get();
            if (item == listener) {
                refToRemove = ref;
            }
        }
        if(refToRemove != null){
            list.remove(refToRemove);
        }
    }
}

이제 WeakCollection<T> 을 사용하기 위해 UserController 를 다시 작성해 보겠습니다.

public class UserController {
    ...
    private WeakCollection<StateListener> listenerRefs;
    ...

    public void registerUserStateChangeListener(StateListener listener) {
        listenerRefs.put(listener);
    }

    public void removeUserStateChangeListener(StateListener listenerToRemove) {
        listenerRefs.remove(listenerToRemove);
    }

    public void logout() {
        for (StateListener listener : listenerRefs.get()) {
            listener.userLoggedOut();
        }
    }

    public void login() {
        for (StateListener listener : listenerRefs.get()) {
            listener.userLoggedIn();
        }
    }

    ...
}

위의 코드 예에서와 같이 WeakCollection<T> 은 일반 목록 대신 WeakReference 를 사용하는 데 필요한 상용구 코드를 모두 제거합니다. 가장 우선하여 : UserController#removeUserStateChangeListener(StateListener) 대한 호출을 놓친 경우, 리스너와 참조하는 모든 객체가 누출되지 않습니다.

익명 클래스, 처리기, 타이머 작업, 스레드로 메모리 누수 피하기


안드로이드에서는 모든 개발자가 프로젝트에서 Anonymous Class (Runnable)를 적어도 한 번 사용합니다. 모든 Anonymous Class 는 부모 (활동)에 대한 참조 Anonymous Class 집니다. 장기 실행 태스크를 수행하는 경우, 태스크가 종료 될 때까지 상위 활동은 파기되지 않습니다.
예제는 핸들러와 Anonymous Runnable 클래스를 사용합니다. Runnable 이 끝나기 전에 액티비티를 종료하면 메모리가 누출됩니다.

  new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            // do abc long 5s or so
        }
    }, 10000); // run "do abc" after 10s. It same as timer, thread...

어떻게 해결할 수 있을까요?

  1. Anonymous Class 하여 오랫동안 작동하지 않거나 Static class 가 필요하고 WeakReference 를 activity, view ...로 전달해야합니다. ThreadAnonymous Class 와 동일합니다.
  2. 활동이 파괴되면 Handler , Timer 취소하십시오.


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow