Android 앱을 "한 번" 이라도 해본 사람은 Context 를 보지 못했을 수가 없습니다.

저는 Context 를 사용할 때, Context 가 대체 뭐야? 하고 상위클래스를 따라가보다가 수천줄에 기겁하고, 닫아버렸던 기억이 있네요.

그렇게 얕게 Context 가 무엇인지 대충 블로그를 통해 학습하고, 넘겼었습니다.

 

이번엔 그런식으로 넘기는게 아니라, 다시 한 번, Context 에 대하여 분석을 해볼까 합니다.

 

Android Reference 에 나온 설명
Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.

애플리케이션 환경에 대한 글로벌 정보에 대한 인터페이스입니다. Android 시스템에서 구현을 제공하는 추상 클래스입니다. 이를 통해 애플리케이션 별 리소스 및 클래스에 액세스 할 수있을뿐만 아니라 활동 시작, 인 텐트 브로드 캐스트 및 수신 등과 같은 애플리케이션 수준 작업에 대한 상향 호출도 가능합니다.

응 블로그 열면 거의 인삿말이이였지, 그래서?


1. Context 는 글로벌 정보에 대한 인터페이스입니다. (Context 는 추상클래스입니다.)

2. Context 는 Android 시스템에서 구현을 제공합니다. 

 

일단 이 2가지에 대한 설명을 기억한채로 이어가겠습니다.

 

 

 

 

Context를 직접적으로 상속받는 하위 클래스와 간접적으로 상속받는 하위 클래스 (간접으로 상속받는 하위 클래스는 더 많습니다.)

 

일단, 우리에게 익숙한 클래스인 Application 과 Activity, 그리고 Service 를 보였습니다.

더보기

Android Component

  • Activity  : Context 의 하위 클래스
  • Service : Context 의 하위 클래스
  • Broadcast receiver : public abstract class BroadcastReceiver { ... }
  • Content provider : public abstract class  ContentProvider implements ContentInterface, ComponentCallbacks2 {...}

 

위에 작성한 것처럼, Android Component 중에서는 Activity 와 Service 만 Context 의 하위 클래스입니다.

 

아, 그렇다면 Context 가 최상위에 존재하고, Application , Activity, Service 들은 하위 클래스 이므로, 서로 다른 Context 가 생성되겠구나!

까지만 생각하지 마시고, 우리는 좀더 깊게!!

 

Application 의 Context 는 Singleton 입니다.

즉, 1개의 앱은 1개의 Application Context 만을 갖고 있습니다.

Activity 와 Service 의 Context 는 생성될 때마다 각자의 새로운 Context 를 갖고 있습니다.

 

이해를 돕기 위하여 바로 다음단계를 작성하겠습니다.

LifeCycle 에 따른 사용 가능한 Context

Application Context 와 Activity Context 의 존재시점

위 그림처럼 Context 는 LifeCycle 과 함께 존재합니다.

자, 이제 우리는 getApplicationContext(), getBaseContext(), this 등으 로 아무 생각없이 사용하던 Context 가 어느 곳에 있는 Context 를 사용하고 있는 것인지 생각할 수 있게 되었습니다.

 

Context 를 사용하는 코드로 이 글의 결론에 한발짝 다가서봅니다.

1. Activity 에 선언된 View 의 Context 는 누구일까?

public class MainActivity extends Activity {
    private final String TAG = MainActivity.class.getSimpleName();
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.tvTitle);

        if (textView.getContext() == getApplicationContext()) {
            Log.d(TAG,"Application Context");
        }

        if (textView.getContext() == this) {
            Log.d(TAG,"Activity Context");
        }
    }
}

정답은 Activity Context 입니다.

위 코드처럼 작성한 View 는 Activity Context 를 전달받게 됩니다. 따라서 View 의 Context 는 Activity Context 와 동일합니다.

 

2. CustomView 를 만들어서 xml 에 직접 추가하는 경험을 하신 분들도 계실겁니다. 그렇다면 View 를 직접 만들 땐..?

public class MainActivity extends Activity {
    private final String TAG = MainActivity.class.getSimpleName();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView1 = new TextView(this);
        TextView textView2 = new TextView(getApplicationContext());

        if (textView1.getContext() == getApplicationContext()) {
            Log.d(TAG,"textView1 : Application Context");
        }

        if (textView1.getContext() == this) {
            Log.d(TAG,"textView1 : Activity Context");
        }

        if (textView2.getContext() == getApplicationContext()) {
            Log.d(TAG,"textView2 : Application Context");
        }

        if (textView2.getContext() == this) {
            Log.d(TAG,"textView2 : Activity Context");
        }
    }
}

전달받은 Context 가 View 의 Context 가 됩니다.

이 상황에서 문제가 발생합니다.

현재 코드의 이상향은, MainActivity 가 종료되고 다른 Activity 가 실행된다고 하면, MainActivity 에 있는 객체들은 GC 의 대상이 되어야 합니다.

그러나 textView2 같은 경우는 Application Context 를 사용하여 만들어졌고, Application Context 를 참조하고 있습니다.(실행결과)

 

그렇다면 textView2 는 GC 대상에서 제외됩니다. 이런 경우 메모리 릭이 발생합니다..

*메모리 릭 : 메모리 누수(memory leak) 현상은 컴퓨터 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상이다. (wiki)

 

유사 케이스로 아래처럼 View 를 static 으로 선언하여 사용하려는 경우에도 메모리 릭이 발생합니다.

(하지만, 이 경우는 경고 메시지를 통해서 '아 static 쓰지 말라는구나' 하고 지워버리기 때문에 그냥 넘어가는 분들도 계실겁니다.)

 

3. View 만 조심하면 되는데, 그럼 끝난거 아닌가?

 

public class MyClass {
    private static MyClass instance;
    private Context mContext;

    public static MyClass getInstance(Context context) {
        if(instance == null) {
            instance = new MyClass(context);
        }
        return instance;
    }

    private MyClass(Context context) {
        mContext = context;
    }

    public String toString(Context context) {
        StringBuilder sb = new StringBuilder();
        sb.append("MyClass: ");
        if(context == mContext) {
            sb.append("True");
        } else {
            sb.append("false");
        }
        return sb.toString();
    }
}

위와 같이 context 를 전달받는 싱글톤 객체가 있다고 가정하겠습니다.

 

public class MainActivity extends Activity {
    private final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, MyClass.getInstance(this).toString(this));

        startActivity(new Intent(MainActivity.this, NextActivity.class));
        finish();
    }
}

public class NextActivity extends Activity {
    private final String TAG = NextActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, MyClass.getInstance(this).toString(this));
    }
}

그리고 이렇게 실행한 결과를 보도록 하겠습니다.

MainActivity 에서는 MainActivity 의 Context 와 싱글토 객체의 Context 가 일치합니다.

MainActivity 의 Context 를 전달해 주었기 때문이죠..

 

그러나 NextActivity 에서는 false 가 출력됩니다.

NextActivity 의 Context 는 MainActivity 의 Context 와 다르기 때문이죠.

 

그럼 Application Context 랑 비교하면?

저희는 위에서 공부했듯이 당연히 false , false 가 출력될 것이라는 것을 알 수 있습니다.

 

그렇다면, 이 코드의 문제는?

역시나, MainActivity 의 Context 가 사라지지 않는다는 것입니다.

싱글톤 객체는 어짜피 유지되어야 하지 않나요?

우리는 싱글톤 객체가 유지되기를 원한것이지, MainActivity의 Context 까지 유지되기를 원한 것이 아닙니다.

만약 싱글톤 객체에서 Context 의 기능이 필요하다면 Application Context 를 사용하는게 메모리릭을 방지 할 수 있겠죠?

Application Context 는 앱에서 단 한개만 존재하고, 그렇다는 것은 어디서 비교해서 true true 가 나오겠죠?

 

 

오늘의 결론

1. Application Context 는 실행이후 단 1개만 존재하며, Application 의 LifeCycle 과 함께 해야 한다. 

2. Activity Context 는 생성되는 경우에 따라 여러개 존재하며, Activity 의 LifeCycle 과 함께 해야 한다.

3. 만약 Activity LifeCycle 과 함께하는 데이터가 Application Context 를 사용한다면, 그것은 곧 메모리 릭으로 이어진다.

 

* Context 사용시 LifeCycle 을 고려하여 작성하자! 
이 객체는 Activity 에서 사용하니까 Activity Context 를 써야지 라고 생각하면 안된다!!

객체의 LifeCycle 을 고려해야 한다!! Static 변수를 조심하자!!

메모리 릭이 계속 발생한다면, 정상적으로 동작하던 앱을 끄지 않고 이것저것 사용하는 도중에 갑자기 꺼질 수도...

'BackUp (관리중지) > Android 이론' 카테고리의 다른 글

Android Service  (0) 2021.05.14
Android Coroutine [코루틴]  (0) 2021.05.04
[Android Jetpack] LiveData란,  (0) 2021.04.21
Fragment 와 Fragment LifeCycle 분석  (0) 2021.04.19
Activity 와 Activity LifeCycle 분석  (0) 2021.04.15

+ Recent posts