(익명) 내부 클래스를 사용하는 것이 언제 안전합니까?


324

Android에서 메모리 누수에 대한 기사를 읽었 으며 주제에 대한 Google I / O의 흥미로운 비디오를 보았습니다 .

아직도, 나는 개념을 이해하지 못하며, 특히 Activity 내부의 사용자 내부 클래스에 안전하거나 위험한 경우 .

이것이 내가 이해 한 것입니다.

내부 클래스의 인스턴스가 외부 클래스 (활동)보다 오래 지속되면 메모리 누수가 발생합니다. -> 어떤 상황에서 이런 일이 일어날 수 있습니까?

이 예제에서는 익명 클래스 OnClickListener가 활동보다 오래 살 수있는 방법이 없기 때문에 누출 위험이 없다고 가정합니다 .

    final Dialog dialog = new Dialog(this);
    dialog.setContentView(R.layout.dialog_generic);
    Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok);
    TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title);

    // *** Handle button click
    okButton.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    titleTv.setText("dialog title");
    dialog.show();

자,이 예는 위험한가요? 왜 그렇습니까?

// We are still inside an Activity
_handlerToDelayDroidMove = new Handler();
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000);

private Runnable _droidPlayRunnable = new Runnable() { 
    public void run() {
        _someFieldOfTheActivity.performLongCalculation();
    }
};

이 주제를 이해하는 것은 활동이 파괴되고 재생성 될 때 유지되는 내용을 자세하게 이해하는 것과 관련이 있다는 사실에 대해서는 의문의 여지가 있습니다.

그렇습니까?

방금 장치의 방향을 바꿨습니다 (누수가 가장 흔한 원인). super.onCreate(savedInstanceState)my에서 언제 호출 되면 onCreate()방향 값이 변경되기 전과 같이 필드 값이 복원됩니까? 내부 클래스의 상태도 복원됩니까?

나는 내 질문이 매우 정확하지 않다는 것을 알고 있지만 일을 더 명확하게 할 수있는 설명에 감사드립니다.


14
이 블로그 게시물 블로그 게시물은 메모리 누수와 내부 클래스에 대한 좋은 정보가 있습니다. :)
Alex Lockwood

답변:


651

당신이 묻는 것은 꽤 어려운 질문입니다. 하나의 질문이라고 생각할 수도 있지만 실제로 한 번에 여러 가지 질문을합니다. 나는 그것을 다뤄야한다는 지식으로 최선을 다할 것이며, 다른 사람들이 내가 놓칠 수있는 것들을 다루기 위해 함께 참여하기를 바랍니다.

중첩 클래스 : 소개

Java에서 OOP를 얼마나 편하게 사용할 수 있는지 잘 모르겠으므로 몇 가지 기본 사항이 적용됩니다. 중첩 클래스는 클래스 정의가 다른 클래스에 포함 된 경우입니다. 기본적으로 정적 중첩 클래스와 내부 클래스의 두 가지 유형이 있습니다. 이들의 실제 차이점은 다음과 같습니다.

  • 정적 중첩 클래스 :
    • "최상위"로 간주됩니다.
    • 포함하는 클래스의 인스턴스를 생성하지 않아도됩니다.
    • 명시 적 참조없이 포함 클래스 멤버를 참조 할 수 없습니다.
    • 자신의 평생을 보내십시오.
  • 내부 중첩 클래스 :
    • 포함하는 클래스의 인스턴스는 항상 생성해야합니다.
    • 포함하는 인스턴스에 대한 암시 적 참조를 자동으로 갖습니다.
    • 참조없이 컨테이너의 클래스 멤버에 액세스 할 수 있습니다.
    • 수명은 컨테이너의 수명 보다 길지 않아야 합니다.

가비지 콜렉션 및 내부 클래스

가비지 콜렉션은 자동이지만 사용중인 것으로 생각하는지 여부에 따라 오브젝트를 제거하려고 시도합니다. 가비지 콜렉터는 꽤 똑똑하지만 완벽하지는 않습니다. 객체에 대한 활성 참조가 있는지 여부에 의해서만 무언가가 사용되고 있는지 확인할 수 있습니다.

여기서 진짜 문제는 내부 클래스가 컨테이너보다 오래 살아있을 때입니다. 이것은 포함하는 클래스에 대한 암시 적 참조 때문입니다. 이것이 발생할 수있는 유일한 방법은 포함하는 클래스 외부의 객체가 포함 객체와 상관없이 내부 객체에 대한 참조를 유지하는 경우입니다.

이것은 내부 객체가 (참조를 통해) 살아 있지만 포함 객체에 대한 참조가 다른 모든 객체에서 이미 제거 된 상황으로 이어질 수 있습니다. 따라서 내부 객체는 포함 객체를 항상 참조 하므로 포함 객체를 활성 상태로 유지 합니다. 이것의 문제는 프로그래밍되지 않으면 포함 객체로 돌아가서 살아 있는지 확인하는 방법이 없다는 것입니다.

이 깨달음의 가장 중요한 측면은 그것이 활동에 있든지 드로어 블에 있든 차이가 없다는 것입니다. 당신은 것입니다 항상 내부 클래스를 사용하여 컨테이너의 그들은 결코 오래 살 객체 있는지 확인하면 조직적해야합니다. 운 좋게도 코드의 핵심 객체가 아닌 경우 누출이 비교적 적을 수 있습니다. 불행히도, 이들은 누출이 가장 많이 발견 될 때까지 눈에 띄지 않을 가능성이 높기 때문에 가장 찾기 어려운 누출입니다.

솔루션 : 내부 클래스

  • 포함 객체에서 임시 참조를 얻습니다.
  • 내부 객체에 대한 장기 참조를 유지하려면 포함하는 객체 만 허용하십시오.
  • 공장과 같은 기존 패턴을 사용하십시오.
  • 내부 클래스에 포함 클래스 멤버에 대한 액세스가 필요하지 않은 경우 내부 클래스를 정적 ​​클래스로 바꾸는 것이 좋습니다.
  • 활동에 있는지 여부에 관계없이주의해서 사용하십시오.

활동과 견해 : 소개

활동에는 실행하고 표시 할 수있는 많은 정보가 포함되어 있습니다. 활동은 반드시보기가 있어야하는 특성으로 정의됩니다. 또한 특정 자동 처리기가 있습니다. 지정 여부에 관계없이 활동에는 포함 된보기에 대한 암시 적 참조가 있습니다.

뷰를 만들려면 뷰를 만들 위치와 표시 할 수있는 자식이 있는지 여부를 알아야합니다. 이것은 모든 뷰가 (via getContext()) 액티비티에 대한 참조를 가지고 있음을 의미합니다 . 또한 모든 View는 자식 (즉 getChildAt())에 대한 참조를 유지합니다 . 마지막으로 각 뷰는 해당 디스플레이를 나타내는 렌더링 된 비트 맵에 대한 참조를 유지합니다.

액티비티 (또는 액티비티 컨텍스트)에 대한 참조가있을 때마다 레이아웃 계층 구조를 따라 전체 체인을 따라갈 수 있습니다. 활동 또는 뷰와 관련된 메모리 누수가 그렇게 큰 이유입니다. 그것은 할 수있다 메모리를 한 번에 모든 유출된다.

활동, 견해 및 내부 수업

내부 클래스에 대한 위의 정보를 고려하면 가장 일반적인 메모리 누수이지만 가장 일반적으로 피할 수 있습니다. 내부 클래스에 활동 클래스 멤버에 직접 액세스하는 것이 바람직하지만, 많은 사람들은 잠재적 인 문제를 피하기 위해 정적으로 만들려고합니다. 활동 및보기의 문제점은 그보다 훨씬 깊습니다.

누출 된 활동,보기 및 활동 컨텍스트

그것은 모두 맥락과 LifeCycle에 달려 있습니다. 활동 컨텍스트를 종료시키는 특정 이벤트 (예 : 오리엔테이션)가 있습니다. 많은 클래스와 메소드에 컨텍스트가 필요하기 때문에 개발자는 컨텍스트에 대한 참조를 잡고이를 유지하여 일부 코드를 저장하려고 할 때가 있습니다. 액티비티를 실행하기 위해 생성해야하는 많은 객체가 액티비티 LifeCycle 외부에 있어야 액티비티가 필요한 작업을 수행 할 수 있습니다. 어떤 오브젝트가 활동, 컨텍스트 또는 해당 뷰가 소멸 될 때 해당 뷰에 대한 참조를 가지면 해당 활동 및 전체 뷰 트리가 유출 된 것입니다.

솔루션 : 활동 및 견해

  • 어떤 경우에도보기 또는 활동에 대한 정적 참조를 피하십시오.
  • 활동 컨텍스트에 대한 모든 참조는 수명이 짧아야합니다 (기능 기간).
  • 오래 지속되는 컨텍스트가 필요한 경우 애플리케이션 컨텍스트 ( getBaseContext()또는 getApplicationContext())를 사용하십시오 . 이것들은 암시 적으로 참조를 유지하지 않습니다.
  • 또는 구성 변경을 대체하여 활동 파괴를 제한 할 수 있습니다. 그러나 이것은 다른 잠재적 이벤트가 활동을 파괴하는 것을 막지는 않습니다. 이 작업을 수행 할 는 있지만 여전히 위의 사례를 참조 할 수 있습니다.

런너 블 : 소개

런너 블은 실제로 그렇게 나쁘지 않습니다. 그들이 수는 있지만 실제로 우리는 이미 대부분의 위험 지대에 부딪 쳤습니다. Runnable은 작성된 스레드와 독립적으로 작업을 수행하는 비동기 작업입니다. 대부분의 실행 파일은 UI 스레드에서 인스턴스화됩니다. 본질적으로 Runnable을 사용하면 약간 더 관리되는 다른 스레드가 생성됩니다. Runnable을 표준 클래스처럼 분류하고 위의 지침을 따르는 경우 몇 가지 문제가 발생합니다. 실제로 많은 개발자들이이 작업을 수행하지 않습니다.

용이성, 가독성 및 논리적 인 프로그램 흐름에서, 많은 개발자는 Anonymous Inner Classes를 사용하여 위에서 만든 예제와 같은 Runnable을 정의합니다. 위에서 입력 한 것과 같은 예가 나타납니다. 익명 이너 클래스는 기본적으로 이산 이너 클래스입니다. 완전히 새로운 정의를 만들 필요없이 적절한 방법을 재정의하면됩니다. 다른 모든 측면에서 내부 클래스이므로 컨테이너에 대한 암시 적 참조를 유지합니다.

런너 블 및 활동 /보기

예이! 이 부분은 짧을 수 있습니다! Runnables가 현재 스레드 외부에서 실행되기 때문에 장기적으로 비동기 작업을 수행 할 위험이 있습니다. 실행 가능 파일이 활동 또는 뷰에서 익명 내부 클래스 또는 중첩 내부 클래스로 정의 된 경우 몇 가지 심각한 위험이 있습니다. 이전에 언급 한 바와 같이, 그것은 때문이다 컨테이너가 누구인지 알 수 있습니다. 방향 변경 (또는 시스템 종료)을 입력하십시오. 이제 이전 섹션을 다시 참조하여 방금 발생한 상황을 이해하십시오. 예, 당신의 모범은 매우 위험합니다.

솔루션 : 런너 블

  • 코드의 논리를 위반하지 않으면 Runnable을 시도하고 확장하십시오.
  • 확장 된 Runnables가 중첩 된 클래스 여야하는 경우 확장 된 Runnable을 정적으로 작성하도록 최선을 다하십시오.
  • 익명보다 Runnable를 사용해야하는 경우, 그들을 만들지 않도록 모든 사용중인 활동 또는보기에 수명이 긴 참조가 객체입니다.
  • 많은 Runnable은 AsyncTasks처럼 쉽게 할 수 있습니다. 기본적으로 VM 관리 대상이므로 AsyncTask를 사용하십시오.

마지막 질문 에 답하기 이제이 게시물의 다른 섹션에서 직접 다루지 않은 질문에 대답하십시오 . "내부 클래스의 개체는 언제 외부 클래스보다 오래 살아남을 수 있습니까?" 이에 도달하기 전에 다시 강조하겠습니다. 활동에서이 문제에 대해 걱정할 수는 있지만 어디에서나 누수가 발생할 수 있습니다. 나는 단지 설명하기 위해 간단한 예제 (활동을 사용하지 않고)를 제공 할 것이다.

아래는 기본 팩토리의 일반적인 예입니다 (코드 누락).

public class LeakFactory
{//Just so that we have some data to leak
    int myID = 0;
// Necessary because our Leak class is an Inner class
    public Leak createLeak()
    {
        return new Leak();
    }

// Mass Manufactured Leak class
    public class Leak
    {//Again for a little data.
       int size = 1;
    }
}

이것은 일반적인 예는 아니지만 설명하기에 충분히 간단합니다. 여기서 핵심은 생성자입니다.

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Gotta have a Factory to make my holes
        LeakFactory _holeDriller = new LeakFactory()
    // Now, let's get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//Store them in the class member
            myHoles[i] = _holeDriller.createLeak();
        }

    // Yay! We're done! 

    // Buh-bye LeakFactory. I don't need you anymore...
    }
}

이제는 누출이 있지만 공장은 없습니다. 비록 우리가 팩토리를 릴리즈했지만, 모든 누출은 참조를 가지고 있기 때문에 메모리에 남아있을 것입니다. 외부 클래스에 데이터가없는 것조차 중요하지 않습니다. 이것은 생각보다 훨씬 자주 발생합니다. 우리는 창조자가 필요하지 않으며 단지 창조물 만 필요합니다. 그래서 우리는 하나를 일시적으로 만들지 만 그 창조물을 무기한으로 사용합니다.

생성자를 약간 변경하면 어떻게 될지 상상해보십시오.

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Now, let's get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//WOW! I don't even have to create a Factory... 
        // This is SOOOO much prettier....
            myHoles[i] = new LeakFactory().createLeak();
        }
    }
}

이제 새로운 LeakFactories의 모든 하나가 유출되었습니다. 당신은 어떻게 생각하십니까? 이것은 내부 클래스가 어떤 유형의 외부 클래스보다 오래 지속될 수 있는지에 대한 두 가지 매우 일반적인 예입니다. 만약 그 외부 계급이 활동 이었다면, 얼마나 더 나빠졌을지 상상해보십시오.

결론

여기에는 이러한 개체를 부적절하게 사용하는 주요 위험이 나열되어 있습니다. 일반적으로이 게시물은 귀하의 질문 대부분을 다루었을 것입니다. 그러나이 게시물이 loooong 게시물 인 것으로 알고 있으므로 설명이 필요하면 알려주세요. 위의 관행을 따르는 한 누출 위험이 거의 없습니다.


3
이 명확하고 자세한 답변을 주셔서 감사합니다. "많은 개발자들이 클로저를 사용하여 실행 파일을 정의합니다"
Sébastien

1
Java의 클로저는 설명하는 Runnable과 같은 익명의 내부 클래스입니다. Runnable을 확장하는 정의 된 클래스를 작성하지 않고 클래스를 활용하는 방법 (거의 확장)입니다. 실제 포함 객체 내에 자체 닫힌 메모리 공간이 있다는 점에서 "클로즈 된 클래스 정의"이므로 클로저라고합니다.
퍼지 컬 로직

26
계몽 글쓰기! 용어에 관한 한 가지 언급 : Java 에는 정적 내부 클래스 와 같은 것이 없습니다 . ( 문서 ). 중첩 클래스는 static 또는 inner 이지만 동시에 둘 다일 수는 없습니다.
jenzz

2
기술적으로는 정확하지만 Java를 사용하면 정적 클래스 내에 정적 클래스를 정의 할 수 있습니다. 이 용어는 저의 이익이 아니라 기술적 의미를 이해하지 못하는 다른 사람들의 이익을위한 것입니다. 이것이 그들이 "최상위 수준"이라고 처음 언급 된 이유입니다. Android 개발자 문서도이 용어를 사용하며 이는 Android 개발을보고있는 사람들을위한 것이므로 일관성을 유지하는 것이 더 좋다고 생각했습니다.
퍼지 컬 로직

13
StackOverflow에서 최고 중 하나 인 Android 용 esp.
StackOverflowed
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.