Context 유출 원인:Handler & 내부 클래스

11983 단어 handler
다음 코드를 생각하다
1 public class SampleActivity extends Activity {
2 
3   private final Handler mLeakyHandler = new Handler() {
4     @Override
5     public void handleMessage(Message msg) {
6       // ... 
7     }
8   }
9 }

만약 자세히 관찰하지 않았다면, 위의 코드는 심각한 메모리 유출을 초래할 수 있었다.Android Lint는 다음과 같은 경고를 제공합니다.
In Android, Handler classes should be static or leaks might occur.
그런데 도대체 누설, 어떻게 일어난 거야?문제의 근원을 확인하고, 안드로이드 프로그램이 처음 시작되었을 때, 안드로이드 프레임워크는 프로그램의 메인 라인Looper 대상을 만듭니다.하나의 Looper가 간단한 메시지 대기열을 실현하여 순환 Message 대상을 처리합니다.모든 주요 응용 프로그램 프레임워크 이벤트 (예를 들어 활동 생명주기 방법 호출, 단추 누르기 등) 는 Message 대상에 포함되며, Looper의 메시지 대기열에 추가되어 하나씩 처리됩니다.운영 스레드의 Looper는 애플리케이션의 전체 수명 주기에 걸쳐 존재합니다.2. Handle가 주 라인에서 실례화되면 Looper의 메시지 대기열에 연결됩니다.메시지 대기열에 전송된 메시지는 안드로이드 프레임워크가 Looper 이 메시지를 최종적으로 처리할 때 호출할 수 있도록 Handler 인용을 가지고 있습니다. Handler#handleMessage(Message)3. 자바에서 비정상적인 내부 클래스와 익명 클래스는 외부 클래스의 인용을 은밀하게 가지고 있다.정적 내부 클래스는 그렇지 않습니다.
그렇다면 도대체 메모리 유출인가?이해하기 어려울 것 같은데, 아래의 코드를 예로 들자
 1 public class SampleActivity extends Activity {
 2  
 3   private final Handler mLeakyHandler = new Handler() {
 4     @Override
 5     public void handleMessage(Message msg) {
 6       // ...
 7     }
 8   }
 9  
10   @Override
11   protected void onCreate(Bundle savedInstanceState) {
12     super.onCreate(savedInstanceState);
13  
14     //   10        
15     mLeakyHandler.postDelayed(new Runnable() {
16       @Override
17       public void run() { }
18     }, 60 * 10 * 1000);
19  
20     //      Activity
21     finish();
22   }
23 }

 
이Activity가finished에 의해 처리되면, 지연된 메시지는 주 라인의 메시지 대기열에서 처리될 때까지 10분 동안 살아남습니다.이 메시지는 이 Activity의handler를 가지고 있으며, 이handler는 그의 외부 클래스를 은밀하게 가지고 있다. (이 예는 SampleActivity이다.)메시지가 처리되기 전까지 이 인용은 풀리지 않을 것이다.따라서 Activity는 쓰레기 회수 메커니즘에 의해 회수되지 않고 그가 가지고 있는 응용 프로그램 자원을 누설하지 않는다.열다섯 번째 줄의 익명 Runnable 클래스도 마찬가지입니다.익명 클래스의 비정상적인 사례는 은밀한 외부 클래스 인용을 가지고 있기 때문에 context가 유출될 것입니다.
이 문제를 해결하기 위해서handler의 하위 클래스는 새 파일에 정의되거나 정적 내부 클래스를 사용해야 합니다.정적 내부 클래스는 외부 클래스의 인용을 은밀하게 가지고 있지 않습니다.그래서 Activity가 유출되지 않습니다.만약Handle 내부에서 외부Activity를 호출하는 방법이 필요하다면,Handler는 의외로 context가 유출되지 않도록 Activity의 약한 인용 (Weak Reference) 을 가지고 있어야 한다.익명 Runnable 클래스로 인한 메모리 유출을 실례화하기 위해서, 우리는 정적 변수로 그를 인용할 것입니다. (익명 클래스의 정적 사례는 외부 클래스의 인용을 은밀하게 가지고 있지 않기 때문입니다.)
 1 public class SampleActivity extends Activity {
 2     /**
 3     *                       
 4     */
 5     private static final Runnable sRunnable = new Runnable() {
 6             @Override
 7             public void run() {
 8             }
 9         };
10 
11     private final MyHandler mHandler = new MyHandler(this);
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16 
17         //   10        .
18         mHandler.postDelayed(sRunnable, 60 * 10 * 1000);
19 
20         //      Activity
21         finish();
22     }
23 
24     /**
25     *                       。
26     */
27     private static class MyHandler extends Handler {
28         private final WeakReference<SampleActivity> mActivity;
29 
30         public MyHandler(SampleActivity activity) {
31             mActivity = new WeakReference<SampleActivity>(activity);
32         }
33 
34         @Override
35         public void handleMessage(Message msg) {
36             SampleActivity activity = mActivity.get();
37 
38             if (activity != null) {
39                 // ...
40             }
41         }
42     }
43 }

정적과 비정적 내부류의 차이는 이해하기 어렵지만 안드로이드 개발자마다 알아야 한다.개발 중에 건드릴 수 없는 지뢰밭은 무엇입니까?Activity에서 비정상적인 내부 클래스를 사용하지 않기 때문에 Activity보다 생명주기가 길지 않습니다.반대로 Activity가 약한 인용을 가진 정적 내부 클래스를 최대한 사용한다.
번역 링크

좋은 웹페이지 즐겨찾기