일반적인 Android 프로그래밍에서 Handler는 비동기 작업을 수행하고 반환된 결과를 처리할 때 자주 사용됩니다. 일반적으로 우리 코드는 다음과 같이 구현됩니다.
다음과 같이 코드 코드를 복사합니다.
공개 클래스 SampleActivity는 활동을 확장합니다.
개인 최종 핸들러 mLeakyHandler = new Handler() {
@보수
공개 무효 핸들 메시지(메시지 메시지) {
// ...
}
}
}
그러나 실제로 위 코드는 Android Lint 도구를 사용하면 이러한 경고가 표시될 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
Android에서 핸들러 클래스는 정적이어야 합니다. 그렇지 않으면 누수가 발생할 수 있습니다. 애플리케이션 스레드의 MessageQueue에 대기열에 포함된 메시지는 대상 핸들러도 유지합니다. 핸들러가 내부 클래스인 경우 외부 클래스 누출을 방지하기 위해 해당 외부 클래스도 유지됩니다. 핸들러를 외부 클래스에 대한 WeakReference가 있는 정적 중첩 클래스로 선언합니다.
이 내용을 보면 코드의 어느 부분에서 메모리 누수가 발생하고 어떻게 메모리 누수가 발생하는지 혼란스러울 수 있습니다. 그럼 천천히 분석해 볼까요?
1. Android 애플리케이션이 시작되면 애플리케이션의 메인 스레드에서 사용할 Looper 인스턴스가 자동으로 생성됩니다. Looper의 주요 작업은 메시지 대기열의 메시지 개체를 하나씩 처리하는 것입니다. Android에서는 Android 프레임워크의 모든 이벤트(예: 활동 수명 주기 메서드 호출 및 버튼 클릭 등)가 메시지에 들어간 다음 메시지 큐에 추가되어 Looper가 처리하며, Looper는 이를 처리하는 일을 담당합니다. 하나씩. 메인 스레드에서 Looper의 수명 주기는 현재 애플리케이션만큼 깁니다.
2. 핸들러가 메인 스레드에서 초기화되고 이 핸들러를 대상으로 하는 메시지를 Looper가 처리하는 메시지 큐로 보낼 때 실제로 전송된 메시지에는 이미 Handler 인스턴스에 대한 참조가 포함되어 있습니다. 이 방법으로만 Looper가 처리됩니다. 메시지가 수신되면 Handler#handleMessage(Message)를 호출하여 메시지의 올바른 처리를 완료할 수 있습니다.
3. Java에서는 비정적 내부 클래스와 익명 내부 클래스가 암시적으로 외부 클래스에 대한 참조를 보유합니다. 정적 내부 클래스는 외부 클래스에 대한 참조를 보유하지 않습니다. 이에 대한 자세한 내용은 Chat Java: "잘못된" 비공개 한정자를 참조하세요.
위의 코드 예제는 메모리 누수를 감지하기가 약간 어려운 것이 사실이지만 다음 예제는 매우 분명합니다.
다음과 같이 코드 코드를 복사합니다.
공개 클래스 SampleActivity는 활동을 확장합니다.
개인 최종 핸들러 mLeakyHandler = new Handler() {
@보수
공개 무효 핸들 메시지(메시지 메시지) {
// ...
}
}
@보수
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(savedInstanceState);
// 메시지를 게시하고 실행을 10분 동안 지연합니다.
mLeakyHandler.postDelayed(new Runnable() {
@보수
공개 무효 실행() { /* ... */ }
}, 1000 * 60 * 10);
// 이전 활동으로 돌아갑니다.
마치다();
}
}
위 코드를 분석해 보면 활동의 종료 메소드를 실행하면 지연된 메시지가 처리되기 전 10분 동안 기본 스레드 메시지 큐에 존재하며 이 메시지에는 핸들러에 대한 참조가 포함되어 있으며 핸들러는 익명의 인스턴스입니다. 내부 클래스는 외부 SampleActivity에 대한 참조를 보유하므로 이로 인해 SampleActivity가 재활용될 수 없게 됩니다. 결과적으로 SampleActivity가 보유한 많은 리소스를 재활용할 수 없게 됩니다. 이를 흔히 메모리 누수라고 합니다.
위의 새로운 Runnable은 익명의 내부 클래스로도 구현되며 SampleActivity에 대한 참조도 보유하며 SampleActivity가 재활용되는 것을 방지합니다.
이 문제를 해결하려면 비정적 내부 클래스를 사용하지 않는 것이 좋습니다. Handler를 상속할 때 이를 별도의 클래스 파일에 배치하거나 정적 내부 클래스를 사용하십시오. 정적 내부 클래스는 외부 클래스에 대한 참조를 보유하지 않으므로 외부 클래스 인스턴스의 메모리 누수가 발생하지 않습니다. 정적 내부 클래스에서 외부 활동을 호출해야 하는 경우 약한 참조를 사용하여 이를 처리할 수 있습니다. 또한 Runnable도 정적 멤버 속성으로 설정해야 합니다. 참고: 정적 익명 내부 클래스 인스턴스는 외부 클래스에 대한 참조를 보유하지 않습니다. 수정 후 메모리 누수가 발생하지 않는 코드는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
공개 클래스 SampleActivity는 활동을 확장합니다.
/**
* 정적 내부 클래스의 인스턴스는 암시적 클래스를 보유하지 않습니다.
* 외부 클래스에 대한 참조입니다.
*/
개인 정적 클래스 MyHandler는 Handler를 확장합니다.
private final WeakReference<SampleActivity> mActivity;
공개 MyHandler(SampleActivity 활동) {
mActivity = new WeakReference<SampleActivity>(활동);
}
@보수
공개 무효 핸들 메시지(메시지 메시지) {
SampleActivity 활동 = mActivity.get();
if (활동 != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
/**
* 익명 클래스의 인스턴스는 암시적 속성을 보유하지 않습니다.
* "정적"인 경우 외부 클래스에 대한 참조입니다.
*/
개인 정적 최종 Runnable sRunnable = 새로운 Runnable() {
@보수
공개 무효 실행() { /* ... */ }
};
@보수
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(savedInstanceState);
// 메시지를 게시하고 실행을 10분 동안 지연합니다.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// 이전 활동으로 돌아갑니다.
마치다();
}
}
실제로 Android에서 많은 메모리 누수는 이 기사에서 언급한 것처럼 Activity에서 비정적 내부 클래스를 사용하여 발생하므로 비정적 내부 클래스를 사용할 때는 특별한 주의를 기울여야 합니다. 보유된 객체가 외부 클래스 객체보다 크면 메모리 누수가 발생할 수 있습니다. 개인적으로 나는 이 문제를 해결하기 위해 기사의 정적 클래스와 약한 참조 방법을 사용하는 경향이 있습니다.