[Android] LifeCycle - Activity

HEETAE HEO·2022년 5월 18일
0

Lifecycle

목록 보기
1/3
post-thumbnail

안드로이드 앱에서 화면 UI를 구성하는 고정된 화면이자 사용자의 진입점이 되어주는 Acitivty에는 생성과 소멸에 관한 LifeCycle이 존재한다.

Activity LifeCycle

다음 도표는 안드로이드 공식문서에 있는 표이다.

onCreate()

Activity가 생성될 때 최초 1회, 가장 먼저 실행되는 함수이다.
onCreate 함수의 경우 전체 수명 주기 동안 딱 한번 실행되므로, 기본 애플리케이션 로직을 짤 때 본 함수에서 수행한다. 또는 화면 방향 전환 등으로 Destroy된 이전 Activity 상태에 대한 복원을 위해 savedInstanceState를 가져오는 작업을 수행한다. -> Data의 Binding , ViewModel 연결 , Class 인스턴스

onStart()

Activity가 사용자에게 표시되며 앱에서는 Activity를 포그라운드로 보내 상호작용을 준비한다. 앱이 UI를 관리하는 코드를 본 함수에서 초기화한다.
-> Listener 등록

onResume()

앱과 사용자가 클릭 등의 상호작용이 가능한 시점이다. 사용자가 Home버튼을 누르는등 앱의 포커스가 사라지지않는 한 onResume()상태를 유지한다. 앱이 사용자에게 보여지기 때문에 모든 기능을 활성화 한다.

onPause()

Activity가 사용자에게 보여지지않을 때 호출된다. 앱 사용도중 전화를 받거나 다른 앱으로 이동한다면 onPause()가 동작한다. 컴포넌트들이 사용자에게 보여지지않을 때 사용할 필요가 없는 기능들을 정지할 수 있도록 한다.
-> Shared Preferences를 통한 현재 값 임시 저장

onStop()

사용자가 Activity를 떠나 사용자에게 보여지지 않을 때 onPause()다음으로 호출되는 함수이다. 새로 시작된 다른 동작이 사용자의 화면을 완전히 차지 했을 때 호출된다.

onPause와의 차이점 : onStop에서 기능을 정지 했을 때에는 멀티 윈도우 화면에서 기능이 정지하지 않고 수행된다.
또한, 데이터베이스에 정보를 쓰는 작업 or 규모가 크고 CPU를 많이 차지하는 종료 작업의 경우 onPause보다는 onStop 시점에서 작업 한다.

onRestart()

onStop() 상태에서 다시 사용자가 앱의 Activity로 돌아왔을 때 실행된다.
onRestart가 수행된 후, 다시 onStart() -> onResume() 순서를 진행하게 된다.

onDestroy()

lifecycle에서 해당 Activity가 완전히 소멸 됐을 때 실행된다.

예를 들어 finish()를 통해 Activity가 소멸 됐을 경우, 화면 전환으로 인해 시스템에서 일시적으로 Activity를 소멸시키는 경우이다.

onPause, onStop에서 해제하지 않은 모든 리소스를 해제하는 동작을 본 함수에서 수행하게 된다.

Activity에서 새로운 Activity로의 LifeCycle 변화 순서

해당 내용은 공식문서에 작성되어있는데

A Activity에서 B Activity로 이동할 때의 순서는 다음과 같다.

A onPause() -> B onCreate() -> B onStart() -> B onResume() -> A onStop이다.

B로 넘어온 다음 뒤로가기를 눌러 다시 A Activity로 이동하게 되면 어떻게 될까?

B onPause() -> A onRestart() -> A onStart() -> A onResume() -> B onStop() -> onDestroy()이다.

B Activity는 A Activity가 정상적으로 구동하기 전까지 대기하다가 완전히 사라지는 것을 볼 수 있다.

수명 주기 전반에 걸친 상태 관리

Activity가 중지되거나 파괴되면 시스템은 나중에 재수화할 수있도록 Activity의 상태를 저장할 수 있는 기회를 제공합니다. 이 저장된 상태를 인스턴스 상태라고 합니다.
Android는 활동 수명 주기 동안 인스턴스 상태를 저장하기 위한 세 가지 옵션을 제공합니다.

1. Android가 상태를 저장하는 데 사용할 Dictionary(Bundle)로 알려진 기본 값을 저장한다.

2. 비트맵과 같은 복잡한 값을 보유할 사용자 정의 클래스 만들기. Android는 이 사용자 정의 클래스를 사용하여 상태를 저장합니다.

3. 구성 변경 수명 주기를 우회하고 활동의 상태 유지에 대한 완전한 책임을 가정합니다.

Bundle State

인스턴스 상태를 저장하기 위한 기본 옵션은 Bundle로 알려진 키/값 사전 객체를 사용하는 것입니다. onCreate()메소드가 매개변수로 번들로 전달 되는 활동이 생성될 때 이 번들은 인스턴스 상태를 복원하는 데 사용할 수 있습니다.

Activity는 번들에서 인스턴스 상태를 저장하고 검색하는 데 도움이 되는 메서드를 제공하는 데

  • OnSaveInstanceState : 활동이 소명될 때 Android에서 호출합니다. 활동은 키/값상태 항목을 유지해야 하는 경우 이 방법을 구현할 수 있습니다.

  • OnRestoreInstanceState : OnCreate메서드가 완료된 후 호출되며 초기화가 완료된 후 Activity가 상태를 복원할 수 있는 또 다른 기회를 제공합니다.

OnSaveInstanceState

활동이 중지되면 OnSaveInstaceState가 호출됩니다. Activity가 상태를 저장할 수 있는 Bundle 매개변수를 수신합니다. 장치에서 구성 변경이 발생하면 Activty Bundle는 전달된 개체를 재정의해 사용함으로서 Activity 상태를 보존할 수 있습니다.

C#

int c;
protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);

  this.SetContentView (Resource.Layout.SimpleStateView);

  var output = this.FindViewById<TextView> (Resource.Id.outputText);

  if (bundle != null) {
    c = bundle.GetInt ("counter", -1);
  } else {
    c = -1;
  }
  output.Text = c.ToString ();

  var incrementCounter = this.FindViewById<Button> (Resource.Id.incrementCounter);

  incrementCounter.Click += (s,e) => {
    output.Text = (++c).ToString();
  };
}

c의 코드는 버튼을 클릭할 때 정수를 증가시켜 incrementCounter에 결과를 표시합니다. 구성 변경이 발생하면(스마트폰의 회전) 위 코드는 값을 잃게 됩니다.

그렇기에 화면이 회전을 하여도 값을 유지하기 위해 Activity를 재정할 수 있다. 그 때 사용하는 것이 OnsaveInstanceState이다.

C#

protected override void OnSaveInstanceState (Bundle outState)
{
  outState.PutInt ("counter", c);
  base.OnSaveInstanceState (outState);
}

이렇게 선언을 한다면 장치가 새 방향으로 회전하면 정수가 번들에 저장되고 다음 행과 함께 검색이 된다.

OnRestoreInstanceState

OnRestoreInstanceState는 onStart()가 동작한 후에 호출되는데 이전에 번들에 저장된 상태를 복원할 수 있는 기회를 Activity에 제공합니다.

OnRestoreInstanceState의 상태 복원의 방법을 설명드리겠습니다.

C#

protected override void OnRestoreInstanceState(Bundle savedState)
{
    base.OnRestoreInstanceState(savedState);
    var myString = savedState.GetString("myString");
    var myBool = savedState.GetBoolean("myBool");
}

이 메서드는 상태를 복원해야하는 시기에 대한 우연성을 제공하기 위해 존재하는데 인스턴스 상태를 복원하기 전에 모든 초기화가 완료될 때 까지 기다리는 것이 더 적절하비다. 또한 기존 활동의 하위 클래스는 인스턴스 상태에서 특정 값만 복원하려고 할 수 있습니다. OnRestoreInstancesState을 쓰면 제공된 Bundle을 이용하여 복원하는 것이므로 재정의할 필요가 없습니다.

Bundle외에 다른 방법

Bundle에 데이터를 유지하는 것 외에도 Android는 OnRetainNonConfigurationInstance를 재정의하고 유지할 데이터가 포함된 인스턴스를 반환하여 데이터를 저장을 지원한다. Java.Lang.Object.OnRetainNonConfigurationInstance에 상태를 저장하는데 두 개의 주요 장점이 있습니다.

1. 반환된 객체는 OnRetainNonConfigurationInstance 메모리가 이 객체를 유지하기 때문에 더 크고 복잡한 데이터 유형에서 잘 수행됩니다.

2. OnRetainNonConfigurationInstance메서드는 필요할 때만 호출됩니다. 그렇기에 수동 캐시를 사용하는 것 보다 경제적입니다.

OnRetainNonConfigurationInstance 예제

OnRetainNonConfigurationInstance은 동일한 데이터를 여러 번 검색하는 상황에서 적합한데 예를 들어 Twitter를 검색하는 코드가 있을 때
C#

public class NonConfigInstanceActivity : ListActivity
{
  protected override void OnCreate (Bundle bundle)
  {
    base.OnCreate (bundle);
    SearchTwitter ("xamarin");
  }

  public void SearchTwitter (string text)
  {
    string searchUrl = String.Format("http://search.twitter.com/search.json?" + "q={0}&rpp=10&include_entities=false&" + "result_type=mixed", text);

    var httpReq = (HttpWebRequest)HttpWebRequest.Create (new Uri (searchUrl));
    httpReq.BeginGetResponse (new AsyncCallback (ResponseCallback), httpReq);
  }

  void ResponseCallback (IAsyncResult ar)
  {
    var httpReq = (HttpWebRequest)ar.AsyncState;

    using (var httpRes = (HttpWebResponse)httpReq.EndGetResponse (ar)) {
      ParseResults (httpRes);
    }
  }

  void ParseResults (HttpWebResponse httpRes)
  {
    var s = httpRes.GetResponseStream ();
    var j = (JsonObject)JsonObject.Load (s);

    var results = (from result in (JsonArray)j ["results"] let jResult = result as JsonObject select jResult ["text"].ToString ()).ToArray ();

    RunOnUiThread (() => {
      PopulateTweetList (results);
    });
  }

  void PopulateTweetList (string[] results)
  {
    ListAdapter = new ArrayAdapter<string> (this, Resource.Layout.ItemView, results);
  }
}

JSON의 형식으로 결과를 출력합니다. 이 상태 또한 구성 변경이 발생하면 (스마트폰의 회전) 프로세스를 반복해서 검색을 시작합니다. 이러한 문제를 예방하기 위해 OnRetainNonConfigurationInstance에 결과를 저장하여 재사용을 통해 재검색을 막아줍니다.

C#

public class NonConfigInstanceActivity : ListActivity
{
  TweetListWrapper _savedInstance;

  protected override void OnCreate (Bundle bundle)
  {
    base.OnCreate (bundle);

    var tweetsWrapper = LastNonConfigurationInstance as TweetListWrapper;

    if (tweetsWrapper != null) {
      PopulateTweetList (tweetsWrapper.Tweets);
    } else {
      SearchTwitter ("xamarin");
    }

    public override Java.Lang.Object OnRetainNonConfigurationInstance ()
    {
      base.OnRetainNonConfigurationInstance ();
      return _savedInstance;
    }

    ...

    void PopulateTweetList (string[] results)
    {
      ListAdapter = new ArrayAdapter<string> (this, Resource.Layout.ItemView, results);
      _savedInstance = new TweetListWrapper{Tweets=results};
    }
}

이제 스마트폰을 회전하면 LastNonConfigurationInstance속성에서 원래 결과가 검색됩니다. 결과는 String[] 포홤되어 있니다.OnRetainNonConfigurationInstance를 반환 해야 하므로 하위 클래스로 분류하는 클래스로 랩핑 됩니다.

class TweetListWrapper : Java.Lang.Object
{
  public string[] Tweets { get; set; }
}

예를 들어 TextView에서 반환된 객체로 사용하려고 하면OnRetainNonConfigurationInstance를 아래와 같이 사용해 주면 됩니다.

TextView _textView;

protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);

  var tv = LastNonConfigurationInstance as TextViewWrapper;

  if(tv != null) {
    _textView = tv;
    var parent = _textView.Parent as FrameLayout;
    parent.RemoveView(_textView);
  } else {
    _textView = new TextView (this);
    _textView.Text = "This will leak.";
  }

  SetContentView (_textView);
}

public override Java.Lang.Object OnRetainNonConfigurationInstance ()
{
  base.OnRetainNonConfigurationInstance ();
  return _textView;
}

Bundle에는 간단한 상태 데이터를 보존하고 복잡한 데이터 유형의 경우는 OnRetainNonConfigurationInstance를 사용하여 데이터를 저장하면 된다는 것을 알게되었습니다.

profile
Android 개발 잘하고 싶어요!!!

0개의 댓글