스마트기기개발관련/안드로이드 개발

안드로이드 Android 메모리 누수(memory leak) 관련

AlrepondTech 2020. 9. 19. 04:52
반응형

 

 

 

 

=================================

=================================

=================================

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%8A%A4-memory-leak

 

발생 원인!!

 

이미지 버튼만 4개를 만든 페이지에서 계속 메모리 누수가 발생하여... 4~5번 실행하면 실행이 되지 않고

오버 플로우가 발생!!!!

 

메모리가 누수되었을 경우 확인 부분

 

1. 전역변수나 클래스로 불러온 bitmap을 recycle함수로 초기화 시켰는지 확인

2. Context는 되도록 applicationContext를 사용하도록

3. 바로 이걸 하세요!!

/*

 * Copyright (C) 2010 The Android Open Source Project

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

 

import android.view.View;

import android.view.ViewGroup;

import android.widget.AdapterView;

import android.widget.ImageView;

 

/**

 * @author givenjazz

 *

 */

public class RecycleUtils {

       

    private RecycleUtils(){};

 

    public static void recursiveRecycle(View root) {

        if (root == null)

            return;

        root.setBackgroundDrawable(null);

        if (root instanceof ViewGroup) {

            ViewGroup group = (ViewGroup)root;

            int count = group.getChildCount();

            for (int i = 0; i < count; i++) {

                recursiveRecycle(group.getChildAt(i));

            }

 

            if (!(root instanceof AdapterView)) {

                group.removeAllViews();

            }


        }

        if (root instanceof ImageView) {

            ((ImageView)root).setImageDrawable(null);

        }

        root = null;

 

        return;

    }

}

 

 

화면에 남겨 있는 view들을 찾아 모두 초기화 시켜주는 클래스 입니다.!!

참조 : http://givenjazz.tistory.com/48

 

사용법은 

@Override

protected void onDestroy() {

RecycleUtils.recursiveRecycle(getWindow().getDecorView());

System.gc();

super.onDestroy();

}

Destroy함수에 추가만 해주세요!!!!!

 

위 클래스 정말 효율적입니다.^^ 

 

바로써보시길~

 

그럼 즐 코딩~

 

 

 

 

=================================

=================================

=================================

 

 

 

출처: 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()를 해줘야한다.

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package com.givenjazz.android;
 
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ImageView;
 
/**
 * @author givenjazz
 *
 */
public class RecycleUtils {
       
    private RecycleUtils(){};
 
    public static void recursiveRecycle(View root) {
        if (root == null)
            return;
        root.setBackgroundDrawable(null);
        if (root instanceof ViewGroup) {
            ViewGroup group = (ViewGroup)root;
            int count = group.getChildCount();
            for (int i = 0; i < count; i++) {
                recursiveRecycle(group.getChildAt(i));
            }
 
            if (!(root instanceof AdapterView)) {
                group.removeAllViews();
            }
 
        }
       
        if (root instanceof ImageView) {
            ((ImageView)root).setImageDrawable(null);
        }
 


 
        root = null;
 
        return;
    }
}

 

사용 예제)

@Override
    protected void onDestroy() {
        RecycleUtils.recursiveRecycle(getWindow().getDecorView());
        System.gc();
 
        super.onDestroy();
    }

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

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

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

접기

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

 

 

 

=================================

=================================

=================================

 

 

 

package com.givenjazz.android;
 
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
 
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
 
import com.givenjazz.android.RecycleUtils;
 
public class OptionAdapter extends BaseAdapter {
    private ArrayList<Integer> mOptionList;
    private Activity mContext;
    //멤버변수로 해제할 Set을 생성
    private List<WeakReference<View>> mRecycleList = new ArrayList<WeakReference<View>>();
    public int selectedIndex = -1;
 
    OptionAdapter(Activity c, ArrayList<Integer> list) {
        mContext = c;
        mOptionList = list;
    }
 
    //onDestory에서 쉽게 해제할 수 있도록 메소드 생성
    public void recycle() {
        for (WeakReference<View> ref : mRecycleList) {
            RecycleUtils.recursiveRecycle(ref.get());
        }
    }
 
    @Override
    public int getCount() {
        return mOptionList.size();
    }
 
    @Override
    public Object getItem(int position) {
        return mOptionList.get(position);
    }
 
    @Override
    public long getItemId(int position) {
        return position;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView i = new ImageView(mContext);
        i.setImageResource(mOptionList.get(position));
       
        //메모리 해제할 View를 추가
        mRecycleList.add(new <WeakReference<View>(i));
        return i;
    }
}

 

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

@Override
    protected void onDestroy() {
        //Adapter가 있으면 어댑터에서 생성한 recycle메소드를 실행 
        if (mImageAdapter != null)
            mImageAdapter.recycle();
        RecycleUtils.recursiveRecycle(getWindow().getDecorView());
        System.gc();
 
        super.onDestroy();
    }

 

 

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

 

 

=================================

=================================

=================================

 

 

반응형