본문 바로가기
안드로이드

안드로이드 메모리 누수 줄이기

by 호군 2011. 7. 7.
반응형
원문 : http://givenjazz.tistory.com/48


안드로이드 진저브리드(2.3)부터 이미지 기본 디코딩방식이 16비트에서 32비트로 변경되었고, 이미지를 처리할 때 메모리를 3~4배쯤 더 사용하는 듯하다. 메모리누수는 더 심해져서 액티비티를 종료해도 상황에 따라 메모리가 다 반환이 되질 않는다. 결국 메모리를 직접 환원해줘야한다.

내일인 17일부터 갤럭시S의 진저브리드 업데이트가 시작되고, 앱이 죽는 걸 많은 사람들이 겪게 될텐데, 이 문제를 해결하기 위해 자원마다 null로 설정해주고 gc를 하는 것은 자바에서 작성하기 꽤나 괴로운 일이다. 다행히 메모리를 많이 잡아먹는 drawable만 리커시브로 해제해줘도 대부분의 메모리는 환원이 된다.

스택오버플로우랑 구글을 검색해도 질문만 있고 이렇다할 해결방법이 없길래 그냥 직접 작성해서 아파치2.0 라이센스로 공개한다. 다음의 메소드는 View에 붙어있는 View의 child를 리커시브로 null로 설정해주는 메소드다. 액티비티가 죽으면 가비지콜렉팅을 해도 레퍼런스가 삭제되서 메모리 환원이 안되므로 onDestroy안에서 System.gc()를 해줘야한다.

접기

  1. /*
  2.  * Copyright (C) 2010 The Android Open Source Project
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *      http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16.  
  17. package com.givenjazz.android;
  18.  
  19. import android.view.View;
  20. import android.view.ViewGroup;
  21. import android.widget.AdapterView;
  22. import android.widget.ImageView;
  23.  
  24. /**
  25.  * @author givenjazz
  26.  *
  27.  */
  28. public class RecycleUtils {
  29.        
  30.     private RecycleUtils(){};
  31.  
  32.     public static void recursiveRecycle(View root) {
  33.         if (root == null)
  34.             return;
  35.         root.setBackgroundDrawable(null);
  36.         if (root instanceof ViewGroup) {
  37.             ViewGroup group = (ViewGroup)root;
  38.             int count = group.getChildCount();
  39.             for (int i = 0; i < count; i++) {
  40.                 recursiveRecycle(group.getChildAt(i));
  41.             }
  42.  
  43.             if (!(root instanceof AdapterView)) {
  44.                 group.removeAllViews();
  45.             }
  46.  
  47.         }
  48.        
  49.         if (root instanceof ImageView) {
  50.             ((ImageView)root).setImageDrawable(null);
  51.         }
  52.  

  53.  
  54.         root = null;
  55.  
  56.         return;
  57.     }
  58. }

접기

 
사용 예제)

더보기

 
위에 코드만으로도 어느정도 효과를 볼 수 있을 것이다. 하지만 코드를 보면 짐작할 수 있듯이 AdapterView는 제대로 환원되지 않는다. AdapterView를 사용한다면 Adapter에 null로 설정해주는 메소드를 만들어서 onDestory()에 삽입해야한다. -수정- WeakReference로 만들지 않아도 액티비티를 종료할 때는 메모리 환원이 되지만 액티비티가 돌아가는 동안 어댑터에서 활용하는 뷰들은 메모리환원이 되질 않는다. Reference를 활용하면 어댑터가 돌아가는 동안 안쓰는 뷰도 메모리 환원이 가능하다.

어댑터가 가비지컬렉팅을 제대로 못해서 죽는 경우가 있는데, 이 때는 OutOfMemoryError 예외를 잡아내서 제대로 환원 못한 뷰를 수동으로 풀어줘야한다. 다음에 예제와 곁들여서 제대로 설명하겠다.

 null 설정해주는 메소드 만드는 예제)

접기

주석으로 설명한 부분만 예제처럼 만들어주면 되고 나머지는 그냥 예제일 뿐이다.

  1.  
  2. package com.givenjazz.android;
  3.  
  4. import java.util.ArrayList;
  5. import java.util.HashSet;
  6. import java.util.Set;
  7.  
  8. import android.app.Activity;
  9. import android.view.View;
  10. import android.view.ViewGroup;
  11. import android.widget.BaseAdapter;
  12. import android.widget.ImageView;
  13.  
  14. import com.givenjazz.android.RecycleUtils;
  15.  
  16. public class OptionAdapter extends BaseAdapter {
  17.     private ArrayList<Integer> mOptionList;
  18.     private Activity mContext;
  19.     //멤버변수로 해제할 Set을 생성
  20.     private List<WeakReference<View>> mRecycleList = new ArrayList<WeakReference<View>>();
  21.     public int selectedIndex = -1;
  22.  
  23.     OptionAdapter(Activity c, ArrayList<Integer> list) {
  24.         mContext = c;
  25.         mOptionList = list;
  26.     }
  27.  
  28.     //onDestory에서 쉽게 해제할 수 있도록 메소드 생성
  29.     public void recycle() {
  30.         for (WeakReference<View> ref : mRecycleList) {
  31.             RecycleUtils.recursiveRecycle(ref.get());
  32.         }
  33.     }
  34.  
  35.     @Override
  36.     public int getCount() {
  37.         return mOptionList.size();
  38.     }
  39.  
  40.     @Override
  41.     public Object getItem(int position) {
  42.         return mOptionList.get(position);
  43.     }
  44.  
  45.     @Override
  46.     public long getItemId(int position) {
  47.         return position;
  48.     }
  49.  
  50.     @Override
  51.     public View getView(int position, View convertView, ViewGroup parent) {
  52.         ImageView i = new ImageView(mContext);
  53.         i.setImageResource(mOptionList.get(position));
  54.        
  55.         //메모리 해제할 View를 추가
  56.         mRecycleList.add(new <WeakReference<View>(i));
  57.         return i;
  58.     }
  59. }
 

접기

 
액티비티에서 어댑터까지 제거하는 예제)

더보기

 
액티비티를 종료하기 전부터 메모리 오류가 발생한다면 가비지콜렉터 문제가 아니라 메모리에 로딩자체를 못하는 것이므로 xml을 inflate시키지말고 BitmapFactory.Options로 작게 디코드하거나 16비트타입으로 디코딩해야한다.
반응형