1. 시작하기
이 실용적인 Codelab은 1단원: Android 개발자 기초 (버전 2) 과정 시작하기의 일부입니다. Codelab을 순서대로 따라해 보면 이 과정을 최대한 활용할 수 있습니다.
- 이 과정의 전체 Codelab 목록은 Android 개발자 기초 (V2) Codelabs를 참고하세요.
- 모든 개념 챕터, 앱, 슬라이드의 링크를 비롯하여 과정에 관한 자세한 내용은 Android 개발자 기초 (버전 2)를 참조하세요.
소개
이 실습에서는 활동 수명 주기에 관해 자세히 알아봅니다. 수명 주기는 활동이 생성될 때부터 소멸되고 시스템에서 리소스를 회수할 때까지 전체 기간 동안 활동이 있을 수 있는 일련의 상태입니다. 사용자가 앱에서 활동 간에 그리고 앱 안팎으로 이동함에 따라 활동은 수명 주기의 여러 상태 간에 전환됩니다.
활동 수명 주기의 각 단계에는 상응하는 콜백 메서드(onCreate(), onStart(), onPause() 등)가 있습니다. 활동이 상태를 변경하면 연결된 콜백 메서드가 호출됩니다. 이미 onCreate() 메서드 중 하나를 알아봤습니다. Activity 클래스에서 수명 주기 콜백 메서드를 재정의하면 사용자 또는 시스템 작업에 응답하여 활동의 기본 동작을 변경할 수 있습니다.
활동 상태도 기기 구성 변경(예: 사용자가 기기를 세로 모드에서 가로 모드로 회전할 때)에 응답하여 변경될 수 있습니다. 이러한 구성 변경이 발생하면 활동이 소멸되고 기본 상태로 재생성되므로 사용자는 활동에 입력한 정보를 잃을 수 있습니다. 사용자에게 혼란을 주지 않기 위해 예기치 않은 데이터 손실을 방지하도록 앱을 개발하는 것이 중요합니다. 이 실습 후반부에서는 구성 변경을 실험하고 기기 구성 변경 및 기타 활동 수명 주기 이벤트에 응답하여 활동 상태를 보존하는 방법을 알아봅니다.
이 실습에서는 TwoActivities 앱에 로깅 문을 추가하고 앱을 사용하면서 활동 수명 주기 변경사항을 살펴봅니다. 그런 다음 이러한 변경사항을 적용하고 이러한 조건에서 사용자 입력을 처리하는 방법을 알아봅니다.
기본 요건
다음을 실행할 수 있어야 합니다.
- Android 스튜디오에서 앱 프로젝트를 만들고 실행합니다.
- 앱에 로그 구문을 추가하고 Logcat 창에서 이러한 로그를 확인합니다.
- 활동과 인텐트를 이해하고 이를 활용하며 편안하게 상호작용할 수 있습니다.
학습할 내용
- 활동 수명 주기의 작동 방식
- Activity가 시작, 일시중지, 중지, 소멸될 때
- 활동 변경사항과 연결된 수명 주기 콜백 메서드에 관한 정보
- 활동 수명 주기 이벤트를 초래할 수 있는 작업 (예: 구성 변경)의 영향입니다.
- 수명 주기 이벤트에서 활동 상태를 유지하는 방법
실습할 내용
- 이전 실습의 TwoActivities 앱에 코드를 추가하여 로깅 문을 포함하도록 다양한 활동 수명 주기 콜백을 구현합니다.
- 앱이 실행될 때 그리고 앱의 각 활동과 상호작용할 때 상태 변경사항을 관찰합니다.
- 기기에서 사용자 동작이나 구성 변경에 응답하여 예기치 않게 다시 생성되는 Activity의 인스턴스 상태를 유지하도록 앱을 수정합니다.
2. 앱 개요
이 실습에서는 TwoActivities 앱에 추가합니다. 앱은 지난 Codelab에서와 거의 동일하게 보이고 동작합니다. 두 개의 Activity 구현을 포함하며 사용자에게 두 구현 간에 전송할 수 있는 기능을 제공합니다. 이 실습에서 이뤄진 앱 변경사항은 표시되는 사용자 동작에 영향을 미치지 않습니다.
3. 3. 작업 1: TwoActivities에 수명 주기 콜백 추가
이 작업에서는 모든 활동 수명 주기 콜백 메서드를 구현하여 이러한 메서드가 호출될 때 Logcat에 메시지를 출력합니다. 이러한 로그 메시지를 통해 Activity 수명 주기에서 상태가 변경되는 시점과 이러한 수명 주기 상태 변경이 실행 중인 앱에 미치는 영향을 확인할 수 있습니다.
1.1(선택사항) TwoActivities 프로젝트 복사
이 실습의 작업에서는 지난 실습에서 빌드한 기존 TwoActivities 프로젝트를 수정합니다. 이전 TwoActivities 프로젝트를 그대로 유지하려면 부록: 유틸리티의 단계에 따라 프로젝트의 사본을 만드세요.
1.2 MainActivity로 콜백 구현
- Android 스튜디오에서 TwoActivities 프로젝트를 열고 Project > Android 창에서 MainActivity를 엽니다.
- onCreate() 메서드에서 다음 로그 구문을 추가합니다.
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
- 해당 이벤트의 로그에 문과 함께 onStart() 콜백 재정의를 추가합니다.
@Override
public void onStart(){
super.onStart();
Log.d(LOG_TAG, "onStart");
}
바로가기의 경우 Android 스튜디오에서 Code > Override Methods를 선택합니다. 클래스에서 재정의할 수 있는 모든 메서드가 포함된 대화상자가 표시됩니다. 목록에서 콜백 메서드를 하나 이상 선택하면 슈퍼클래스에 대한 필수 호출을 비롯하여 해당 메서드의 전체 템플릿이 삽입됩니다.
- onStart() 메서드를 템플릿으로 사용하여 onPause(), onRestart(), onResume(), onStop(), onDestroy() 수명 주기 콜백을 구현합니다.
모든 콜백 메서드의 서명은 동일합니다(이름 제외). onStart()를 복사하여 붙여넣기하여 이러한 다른 콜백 메서드를 만드는 경우, 슈퍼클래스에서 올바른 메서드를 호출하도록 콘텐츠를 업데이트하고 올바른 메서드를 로깅하는 것을 잊지 마세요.
- 앱을 실행합니다.
- Android 스튜디오 하단에 있는 Logcat 탭을 클릭하여 Logcat 창을 표시합니다. 활동이 시작될 때 전환된 세 가지 수명 주기 상태를 보여주는 로그 메시지 3개가 표시됩니다.
D/MainActivity: -------
D/MainActivity: onCreate
D/MainActivity: onStart
D/MainActivity: onResume
1.3 SecondActivity에서 수명 주기 콜백 구현
이제 MainActivity의 수명 주기 콜백 메서드를 구현했으므로 SecondActivity에도 동일한 작업을 실행합니다.
- SecondActivity를 엽니다.
- 클래스 상단에서 LOG_TAG 변수의 상수를 추가합니다.
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
- 두 번째 Activity에 수명 주기 콜백과 로그 구문을 추가합니다. MainActivity에서 콜백 메서드를 복사하여 붙여넣을 수 있습니다.
- 완료() 메서드 직전에 returnReply() 메서드에 로그 구문을 추가합니다.
Log.d(LOG_TAG, "End SecondActivity");
1.4 앱이 실행될 때 로그 관찰**
- 앱을 실행합니다.
- Android 스튜디오 하단에 있는 Logcat 탭을 클릭하여 Logcat 창을 표시합니다.
- 검색창에 '활동'을 입력합니다. Android Logcat은 매우 길고 어수선할 수 있습니다. 각 클래스의 LOG_TAG 변수에는 MainActivity 또는 SecondActivity라는 단어가 포함되어 있으므로 이 키워드를 사용하면 관심 있는 항목으로만 로그를 필터링할 수 있습니다.
앱을 사용하여 실험하고 다양한 작업에 반응하여 발생하는 수명 주기 이벤트를 확인합니다. 특히 다음 작업을 시도해 보세요.
- 앱을 정상적으로 사용합니다(메시지를 보내고 다른 메시지로 답장).
- 뒤로 버튼을 사용하여 두 번째 활동에서 기본 활동으로 돌아갑니다.
- 앱 바에서 위쪽 화살표를 사용하여 두 번째 활동에서 기본 활동으로 돌아갑니다.
- 앱의 기본 활동과 두 번째 활동 모두에서 기기를 서로 다른 시간에 회전하고 * 로그와 화면에서 어떤 일이 일어나는지 관찰합니다.
- 최근 사용 버튼 (홈 오른쪽의 정사각형 버튼)을 누르고 앱을 닫습니다 (X 탭하기).
- 홈 화면으로 돌아가 앱을 다시 시작합니다.
팁: 에뮬레이터에서 앱을 실행하는 경우 Control+F11 또는 Control+Function+F11로 회전을 시뮬레이션할 수 있습니다.
작업 1 솔루션 코드
다음 코드 스니펫은 첫 번째 작업의 솔루션 코드를 보여줍니다.
MainActivity
다음 코드 스니펫은 MainActivity에 추가된 코드를 보여주지만 전체 클래스는 보여주지 않습니다.
onCreate() 메서드는 다음을 충족해야 합니다.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Log the start of the onCreate() method.
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);
}
다른 수명 주기 메서드:
@Override
protected void onStart() {
super.onStart();
Log.d(LOG_TAG, "onStart");
}
@Override
protected void onPause() {
super.onPause();
Log.d(LOG_TAG, "onPause");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(LOG_TAG, "onRestart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(LOG_TAG, "onResume");
}
@Override
protected void onStop() {
super.onStop();
Log.d(LOG_TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "onDestroy");
}
SecondActivity
다음 코드 스니펫은 SecondActivity에 추가된 코드를 보여주지만 전체 클래스는 보여주지 않습니다.
SecondActivity 클래스 상단에서 다음을 실행합니다.
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
returnReply() 메서드:
public void returnReply(View view) {
String reply = mReply.getText().toString();
Intent replyIntent = new Intent();
replyIntent.putExtra(EXTRA_REPLY, reply);
setResult(RESULT_OK, replyIntent);
Log.d(LOG_TAG, "End SecondActivity");
finish();
}
다른 수명 주기 메서드:
위의 MainActivity와 동일합니다.
4. 4. 작업 2: Activity 인스턴스 상태 저장 및 복원
시스템 리소스와 사용자 동작에 따라 앱의 각 활동이 생각보다 훨씬 더 자주 소멸되고 재구성될 수 있습니다.
지난 섹션에서 기기나 에뮬레이터를 회전할 때 이 동작을 확인했을 수 있습니다. 기기 회전은 기기 설정 변경의 한 가지 예입니다. 회전이 가장 일반적인 것이지만 모든 구성 변경은 현재 Activity가 소멸되고 새 활동인 것처럼 다시 생성됩니다. 코드에서 이 동작을 고려하지 않으면 구성 변경이 발생할 때 활동 레이아웃이 기본 모양과 초깃값으로 되돌아갈 수 있으며 사용자가 앱에서 위치, 데이터 또는 진행 상황 상태를 잃을 수 있습니다.
각 Activity의 상태는 Activity 인스턴스 상태라는 Bundle 객체에 키-값 쌍의 집합으로 저장됩니다. 시스템은 Activity가 중지되기 직전에 기본 상태 정보를 인스턴스 상태 Bundle에 저장하고 이 Bundle을 새 Activity 인스턴스에 전달하여 복원합니다.
Activity가 예기치 않게 소멸되어 다시 생성될 때 데이터가 손실되지 않도록 하려면 onSaveInstanceState() 메서드를 구현해야 합니다. 시스템은 Activity가 소멸되었다가 재생성될 가능성이 있는 경우 Activity (onPause()와 onStop() 사이)에서 이 메서드를 호출합니다.
인스턴스 상태에 저장하는 데이터는 현재 앱 세션 중에 이 특정 활동의 이 인스턴스에만 적용됩니다. 새 앱 세션을 중지했다가 다시 시작하면 Activity 인스턴스 상태가 사라지고 Activity가 기본 모양으로 되돌아갑니다. 앱 세션 간에 사용자 데이터를 저장해야 하는 경우 공유 환경설정 또는 데이터베이스를 사용하세요. 이 둘 모두에 관해서는 이후 실습에서 알아봅니다.
2.1 onSaveInstanceState()로 Activity 인스턴스 상태 저장
기기를 회전해도 두 번째 Activity의 상태에 전혀 영향을 미치지 않습니다. 이는 두 번째 Activity 레이아웃과 상태가 레이아웃과 이를 활성화한 인텐트에서 생성되기 때문입니다. 활동이 다시 만들어져도 인텐트는 여전히 존재하며 두 번째 활동의 onCreate() 메서드가 호출될 때마다 인텐트의 데이터가 계속 사용됩니다.
또한 각 활동에서 메시지 또는 EditText 요소에 입력한 모든 텍스트는 기기가 회전해도 유지됩니다. 이는 레이아웃에 있는 일부 View 요소의 상태 정보가 구성 변경 시 자동으로 저장되고 EditText의 현재 값이 이러한 사례 중 하나이기 때문입니다.
따라서 관심 있는 Activity 상태는 기본 Activity에 있는 회신 헤더와 회신 텍스트의 TextView 요소뿐입니다. 두 TextView 요소는 기본적으로 표시되지 않습니다. 두 번째 Activity에서 기본 Activity로 메시지를 다시 보낸 경우에만 두 TextView 요소가 나타납니다.
이 작업에서는 onSaveInstanceState()를 사용하여 이러한 두 TextView 요소의 인스턴스 상태를 보존하는 코드를 추가합니다.
- MainActivity를 엽니다.
- onSaveInstanceState()의 이 스켈레톤 구현을 활동에 추가하거나 Code > Override Methods를 사용하여 스켈레톤 재정의를 삽입합니다.
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
- 헤더가 현재 표시되는지 확인하고 이 경우 putBoolean() 메서드와 'reply_visible' 키를 사용하여 이러한 가시성 상태를 번들 상태에 배치합니다.
if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
outState.putBoolean("reply_visible", true);
}
답장 헤더와 텍스트는 두 번째 활동에서 답장이 올 때까지 표시되지 않음으로 표시됩니다. 헤더가 표시되면 저장해야 하는 답장 데이터가 있는 것입니다. 여기서는 표시 상태에만 관심이 있습니다. 헤더의 실제 텍스트는 변경되지 않으므로 저장할 필요가 없습니다.
- 동일한 확인 내에서 답장 텍스트를 번들에 추가합니다.
outState.putString("reply_text",mReplyTextView.getText().toString());
헤더가 표시되면 답장 메시지 자체도 표시된다고 가정할 수 있습니다. 답장 메시지의 현재 표시 상태는 테스트하거나 저장하지 않아도 됩니다. 메시지의 실제 텍스트만 'reply_text' 키가 포함된 Bundle 상태로 들어갑니다.
Activity가 생성된 후 변경될 수 있는 View 요소의 상태만 저장합니다. 앱의 다른 뷰 요소 (EditText, Button)는 언제든지 기본 레이아웃에서 다시 만들 수 있습니다.
시스템은 EditText의 콘텐츠와 같은 일부 View 요소의 상태를 저장합니다.
2.2 onCreate()에서 Activity 인스턴스 상태 복원
Activity 인스턴스 상태를 저장한 후에는 Activity가 다시 생성될 때도 복원해야 합니다. 이 작업은 onCreate()에서 수행하거나 활동이 생성된 후 onStart() 이후에 호출되는 onRestoreInstanceState() 콜백을 구현하여 수행할 수 있습니다.
대부분의 경우 Activity 상태를 복원하기에 더 좋은 위치는 onCreate()에서 상태를 비롯한 UI를 가능한 한 빨리 사용할 수 있도록 하는 것입니다. 때로는 모든 초기화가 완료된 후 onRestoreInstanceState()에서 실행하거나 서브클래스에서 기본 구현의 사용 여부를 결정하도록 허용하는 것이 편리합니다.
- onCreate() 메서드에서 View 변수가 findViewById()로 초기화된 후 savedInstanceState가 null이 아님을 확인하는 테스트를 추가합니다.
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);
// Restore the state.
if (savedInstanceState != null) {
}
Activity가 생성되면 시스템은 Bundle 상태를 onCreate()에 유일한 인수로 전달합니다. onCreate()가 처음 호출되고 앱이 시작되면 Bundle이 null입니다. 앱이 처음 시작될 때는 기존 상태가 없습니다. 이후 onCreate() 호출에는 onSaveInstanceState()에 저장된 데이터로 채워진 번들이 있습니다.
- 이 검사 내에서 'reply_visible' 키를 사용하여 번들에서 현재 공개 상태 (true 또는 false)를 가져옵니다.
if (savedInstanceState != null) {
boolean isVisible =
savedInstanceState.getBoolean("reply_visible");
}
- isVisible 변수의 이전 줄 아래에 테스트를 추가합니다.
if (isVisible) {
}
상태 번들에 return_visible 키가 있고 isVisible이 true인 경우 상태를 복원해야 합니다.
- isVisible 테스트 내에서 헤더가 표시되도록 합니다.
mReplyHeadTextView.setVisibility(View.VISIBLE);
- Bundle에서 'reply_text' 키가 포함된 텍스트 답장 메시지를 가져오고 해당 문자열을 표시하도록 회신 TextView를 설정합니다.
mReplyTextView.setText(savedInstanceState.getString("reply_text"));
- 회신 TextView도 표시되도록 합니다.
mReplyTextView.setVisibility(View.VISIBLE);
- 앱을 실행합니다. 기기나 에뮬레이터를 회전해 보고 활동이 다시 생성된 후에도 답장 메시지 (있는 경우)가 화면에 유지되는지 확인합니다.
작업 2 솔루션 코드
다음 코드 스니펫은 이 작업의 솔루션 코드를 보여줍니다.
MainActivity
다음 코드 스니펫은 MainActivity에 추가된 코드를 보여주지만 전체 클래스는 보여주지 않습니다.
onSaveInstanceState() 메서드:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// If the heading is visible, message needs to be saved.
// Otherwise we're still using default layout.
if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
outState.putBoolean("reply_visible", true);
outState.putString("reply_text",
mReplyTextView.getText().toString());
}
}
onCreate() 메서드는 다음을 충족해야 합니다.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);
// Restore the saved state.
// See onSaveInstanceState() for what gets saved.
if (savedInstanceState != null) {
boolean isVisible =
savedInstanceState.getBoolean("reply_visible");
// Show both the header and the message views. If isVisible is
// false or missing from the bundle, use the default layout.
if (isVisible) {
mReplyHeadTextView.setVisibility(View.VISIBLE);
mReplyTextView.setText(savedInstanceState
.getString("reply_text"));
mReplyTextView.setVisibility(View.VISIBLE);
}
}
}
전체 프로젝트:
Android 스튜디오 프로젝트: TwoActivitiesLifecycle
5. 코딩
과제: 사용자가 빌드하는 목록에 관한 기본 활동과 일반적인 쇼핑 항목 목록에 관한 두 번째 활동이 있는 간단한 쇼핑 목록 앱을 만듭니다.
- 기본 활동에는 빌드할 목록이 포함되어야 하며, 이 목록은 10개의 빈 TextView 요소로 구성되어야 합니다.
- 기본 활동의 항목 추가 버튼은 일반적인 쇼핑 항목 (치즈, 쌀, 사과 등) 목록이 포함된 두 번째 활동을 실행합니다. Button 요소를 사용하여 항목을 표시합니다.
- 항목을 선택하면 사용자가 기본 활동으로 돌아가고 빈 TextView가 업데이트되어 선택된 항목이 포함됩니다.
인텐트를 사용하여 한 활동에서 다른 활동으로 정보를 전달합니다. 쇼핑 목록의 현재 상태는 사용자가 기기를 회전할 때 저장되어야 합니다.
6. 요약
- 활동 수명 주기는 활동이 이동하는 일련의 상태로, 처음 생성될 때 시작되어 Android 시스템이 해당 활동의 리소스를 회수할 때 끝납니다.
- 사용자가 한 활동에서 다른 활동으로 그리고 앱 안팎으로 이동할 때 각 활동은 활동 수명 주기의 상태 간에 이동합니다.
- Activity 수명 주기의 각 상태에는 Activity 클래스에서 재정의할 수 있는 상응하는 콜백 메서드가 있습니다.
- 수명 주기 메서드는 onCreate(), onStart(), onPause(), onRestart(), onResume(), onStop(), onDestroy()입니다.
- 수명 주기 콜백 메서드를 재정의하면 Activity가 해당 상태로 전환될 때 발생하는 동작을 추가할 수 있습니다.
- Android 스튜디오에서 Code > Override를 사용하여 클래스에 스켈레톤 재정의 메서드를 추가할 수 있습니다.
- 회전과 같은 기기 구성 변경으로 인해 Activity가 소멸되고 새 것처럼 다시 생성됩니다.
- 일부 활동 상태(EditText 요소의 현재 값 포함)는 구성 변경 시 보존됩니다. 다른 모든 데이터의 경우 데이터를 직접 명시적으로 저장해야 합니다.
- onSaveInstanceState() 메서드에 Activity 인스턴스 상태를 저장합니다.
- 인스턴스 상태 데이터는 간단한 키-값 쌍으로 번들에 저장됩니다. Bundle 메서드를 사용하여 데이터를 Bundle에 넣고 번들에서 다시 가져옵니다.
- onCreate()(선호되는 방법) 또는 onRestoreInstanceState()에서 인스턴스 상태를 복원합니다.