티스토리 뷰
[Android(안드로이드) 앱 개발 기초] 런타임 설정(로테이션, orientation) 변환 라이프사이클
Unikys 2012. 12. 5. 23:12* 이번에는 이전의 라이프사이클에 이어서 앱을 실행 중일때 설정이 바뀌는, 대표적인 예로 화면 회전이 일어나는 경우 처리하는 방법에 대해서 알아보자.
2012/11/05 - [Android(안드로이드) 앱 개발 기초] Eclipse 개발환경 설정하기, Android SDK 설치하기
2012/11/07 - [Android(안드로이드) 앱 개발 기초] 안드로이드 프로젝트 생성하고 에뮬레이터로 앱 실행하기
2012/11/10 - [Android(안드로이드) 앱 개발 기초] 간단한 인터페이스 구현과 다른 Activity로 넘어가기
2012/11/21 - [Android(안드로이드) 앱 개발 기초] Activity 라이프사이클 공부
2012/11/24 - [Android(안드로이드) 앱 개발 응용] Google Map API로 지도 보여주기(MapView), Overlay Item 그려주기 예제
2012/11/28 - [Android(안드로이드) 앱 개발 응용] Location GPS 위치 가져오기 및 최적화
* 개요
: 단말에서는 화면 orientation 변환, 키보드 변환, 언어 설정 변환 등과 같이 몇몇 설정들은 런타임 중에 실시간으로 변환하는 경우들이 있다. 이러한 경우에 안드로이드는 현재 Activity를 다시 시작하게 된다. 따라서 이전의 라이프사이클에서 공부했다시피 Activity의 onDestroy를 호출하고 다시 onCreate를 호출하게 되는 과정을 거치게 된다. 이렇게 다시 시작하는 것은 Activity를 다시 시작함으로써 바뀐 설정에 대한 resource들을 쉽게 다시 설정하도록 도와주기 위한 것이다.
: 이렇게 다시 시작을 할 때에 Activity에서 가지고 있었던 정보들을 잘 보존해야 하는데 이전에 공부했던 onSaveInstanceState를 호출하게 해서 정보를 저장하고, 다시 onCreate나 onRestoreInstanceState에서 저장했던 정보를 다시 불러오도록 하는 것이다. 이 부분은 이전의 라이프사이클 부분에서 이미 공부했었으므로, 오래되서 기억이 가물가물하다면 후딱 한번 훑어보고 오는 것도 좋을 것이다.
2012/11/21 - [Android(안드로이드) 앱 개발 기초] Activity 라이프사이클 공부
: 이렇게 앱이 다시 시작되는 것을 테스트하기 위해서는 앱으로 다양한 작업들을 하면서 이것저것 화면 로테이션 등과 같은 환경 설정의 변화를 줘보면서 테스트를 하면 좋을 것이다. 위의 라이프사이클 이벤트들을 잘 구현하고 있다면 Activity는 데이터를 잘 잘 보존하고 있을 것이다. 하지만, 이런 변화가 즉각적인 변화를 요구하고 있다면, 때로는 방대한 데이터를 저장하고 복원하는 것에 대한 자원 소모가 심각하여 UX를 갑소 시킬지도 모른다. 그럴 때에는 2가지 방법으로 해결할 수 있다.
- Object를 보관하여 새롭게 시작하는 Activity에 넘겨주는 방법
- 시스템이 환경 설정을 바꾸고 Activity를 다시 시작하는 것을 방지하고 직접 처리하는 방법
: 이제부터 각 방법에 대해서 공부해보자.
* Object를 보관하여 새롭게 시작하는 Activity에 넘겨주는 방법
: 만약에 Activity를 시작할 때 방대한 데이터를 복원해야하고, 네트워크 연결도 복원하고, 어떠한 복잡한 처리과정이 필요로 한다면, 환경 변화로 Activity를 다시 시작하는 것은 매우 소모적인 일일 것이다. onSaveInstanceState() 함수에서 저장하는 Bundle은 커다란 데이터를 저장하기 위한 용이 아니라 특히 비트맵 이미지 등과 같은 데이터를 넘겨주기 곤란한 경우가 있다. 게다가 Object로 넘겨주는 경우 serialize 단계를 거쳐서 넘겨주고 다시 deserialize 단계를 거치는 처리과정이 필요하기 때문에 많은 메모리를 요구하게 되고, 환경 설정의 변화를 더디게 할 수 있을 것이다. 그럴 때에는 데이터를 저장한 Object를 보존함으로써 그러한 재시작에 대한 자원 소모를 최소화할 수 있는 것이다.
: 이것은 두 단계에서 일어나게 된다.
- onRetainNonConfigurationInstance() 함수를 오버라이드해서 보존하고 싶은 object를 저장
- Activity가 시작될 때 getLastNonConfigurationInstance()에서 이전에 보존한 object 복원
: 안드로이드 시스템에서 Activity를 환경 설정의 변화로 인해 재시작을 한다면, 중간에 onRetainNonConfigurationInstance()를 onStop()과 onDestroy() 사이에서 호출을 하게 되고, 이 onRetainNonConfigurationInstance() 함수에서는 기존의 정보와 상태들을 복원시키기 편한 아무 Object를 리턴해서 복원을 하면 되는 것이다.
: 가장 많이 사용될 수 있는 시나리오는 바로 인터넷에서 앱으로 많은 양의 데이터를 받았을 때, 사용자가 화면을 돌려서 재시작을 해야한다면, 이럴때 인터넷에서 받은 데이터를 다시 받기보다는 onRetainNonConfigureInstance() 함수를 통해 데이터를 저장하고 환경설정이 바뀐 뒤에 다시 시작될 때, getLastNonConfigurationInstance()를 통해 복원시키면 되는 것이다.
: 이제 간단한 예를 살펴보자. 일단 Activity 안의 onRetainNonConfigurationInstace() 함수를 오버라이드하자.
@Override public Object onRetainNonConfigurationInstance() { final MyDataObject data = collectMyLoadedData(); return data; }
: 주의할 것은 Activity에 연결되어있는 변수를 묶어서 보내면 안된다는 것이다. 예를 들면, Drawable, Adapter나 View와 같이 Context에 연결된 것들은 만약에 넘겨주게 된다면 연관되어있는 뷰와 리소스들이 모두 memory leak이 일어나게 된다. 이러한 경우 엄청난 양의 메모리를 잡아먹게 될 것이므로 반드시 주의하자.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final MyDataObject data = (MyDataObject) getLastNonConfigurationInstance(); if (data == null) { data = loadMyData(); } ... }
: 위의 경우 getLastNonConfigurationInstance()는 onRetainNonConfigurationInstace()에서 저장한 Object를 넘겨주게 된다. 만약에 위와 같이 Configuration, 환경설정을 통한 시작이 아니라면 (앱이 처음으로 구동된다던가 등) data는 null을 리턴하는 것으로 판별하고 새롭게 데이터를 로드할 수 있도록 설정할 수 있을 것이다. 그리고 null이 아니라면 기존의 데이터를 가지고 다시 작업을 진행하면 되는 것이 첫번째 방법이다.
* 시스템의 환경이 바뀌는 것을 직접 다루기
: 만약에 개발하고 있는 앱이 리소스를 업데이트할 필요가 없고, Activity가 재시작할 때의 퍼포먼스 문제가 있다고 한다면, Activity가 재시작하는 것을 막고, 직접 Activity에서 환경의 변화를 어떻게 적용시킬것인가 설정하면 된다.
: 만약에 직접 환경 설정을 다루게 된다면 다른 새로운 리소스를 이용하는 것은 매우 복잡할 것이다. 왜냐하면 일반적으로 시스템에서 자동적으로 모든 것을 적용해주면서 재시작하기 때문이다. 따라서 이 방법은 재시작을 피해야할 때 사용해야할 마지막 수단으로 대부분의 앱에서는 추천되지 않는 방법이다.
:만약 환경 설정이 바뀌는 것을 직접 제어하고 싶다면, manifest 파일에 android:configChanges 속성을 추가해서 제어를 하고 싶은 해당하는 환경 설정을 인자로 추가해주면 된다. android:configChanges에서 가능한 값들은 아래와 같다.
값 |
설정 |
mcc |
IMSI Mobile Country Code(MCC) 가 바뀜 - SIM에 의해 MCC가 업데이트 될 때 호출 |
mnc |
IMSI Mobile Network Code(MNC) 가 바뀜 - SIM에 의해 MNC가 업데이트 될 때 호출 |
locale |
사용자가 새로운 언어를 선택했을 때 표시 언어 바꿈 |
touchscreen |
터치스크린이 바뀜 (왠만해선 안일어남) |
keyboard |
키보드의 형태가 바뀜, 예를 들면 외부 키보드를 플러그인함 |
keyboardHidden |
키보드의 접근성이 바뀜 - 예를 들면 하드웨어 키보드를 이용할 때 일어남 |
navigation |
네비게이션 종류가 바뀜 (트랙볼/dpad) - 왠만해선 안 일어남 |
screenLayout |
화면 레이아웃이 변한 - 다른 디스플레이가 작동했을 경우 |
fontScale |
폰트의 scaling 수치가 바뀜 - 사용자가 글로벌 폰트 사이즈를 바뀌었을 때 |
uiMode | UI 모드가 바뀜 - 사용자가 도크에 장착했거나, night 모드가 바뀌었을 때 |
orientation | 화면의 orientation이 바뀜 - 사용자가 화면을 돌렸을 때 |
screenSize | 현재 화면 크기가 변했을 때 - 위와 같이 orientation이 바뀌었거나, 현재 가용한 화면의 크기가 변했을 때, API level 13 이상에서는 orientation 말고 screenSize를 사용할 수 있다 |
smallestScreenSize | 물리적인 화면 크기가 변함 - orientation과 무관하게 오로지 물리적인 화면이 바뀌었을 때 |
layoutDirection | 레이아웃 방향이 Left-To-Right (LTR)에서 Right-To-Left (RTL)로 바뀌었을 때, API level 17에 추가 |
: 위와 같은 값을 configChanges에 추가하되, 여러 개의 이벤트를 제어하고 싶다면, | 로 이어서 작성하면 된다. 아래에 그 예가 있다.
<activity android:name=".MyActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:label="@string/app_name">
: 이제 MyActivity에서는 위의 "orientation"과 "keyboardHidden" 환경 설정이 바뀌었을 때에는 재시작을 하지 않고 onConfigurationChanged() 함수를 콜백하게 되고, 인자로 Configuration 객체를 하나 넘겨줘서 어떠한 새로운 환경설정이 바뀌었는지 명시해준다. Configuration을 읽음으로써 바뀐 환경설정이 무엇인지, 그리고 리소스나 인터페이스에 알맞는 변화와 갱신을 해주면 된다. 이 onConfigurationChanged() 함수가 호출 됐을 때에는 이미 resource들이 갱신된 상태이므로 이 리소스들을 바탕으로 UI를 재설정하면 된다.
* 주의: 안드로이드 API level 13 이상으로 작업하는 경우 화면이 전환될 때에는 "orientation" 뿐만아니라 "screenSize" 또한 호출되므로 screenSize까지 제어를 해주지 않으면 무의미하다. 따라서 android:configChanges="orientation|screenSize" 로 해야 재시작을 방지할 수 있다.
: 아래는 onConfigurationChanged() 함수를 구현하여 단말의 orientation을 체크하는 예이다.
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Checks the orientation of the screen if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show(); } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){ Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show(); } }
: Configuration에 있는 값들은 int 값으로 Configuration 에 자체적으로 들어있는 상수값들과 환경설정이 매칭된다. 각각의 환결설정이 어떠한 값을 가지고 있는지는 아래의 웹페이지에서 참고하면 된다.
http://developer.android.com/reference/android/content/res/Configuration.html
: Configuration 파일은 바뀐 환경설정 뿐만아니라 현재의 모든 설정을 포함하고 있다. 왜냐하면, 리소스를 설정하는데 있어서 어떠한 설정이 바뀌었는지보다는 현재의 설정이 어떤가에 따라 다르게 하는 경우가 대부분이기 때문이다. 이미 Activity가 재시작하면서 리소스가 다시 다 갱신되었기 때문에 이제는 ImageView에서 setImateResource() 등을 사용해서 새로운 환경설정에 맞도록 적용하면 된다.
: 만약 이렇게 직접 Activity의 재시작을 제어하게 된다면, 개발자가 모든 항목들을 재설정하고 바궈주는 것에 대한 책임이 있다. 예를 들면 화면을 돌렸을 때 이미지가 landscape에서 portrait으로 바꾸려고 한다면 개발자가 직접 onConfigurationChange() 함수 안에서 설정을 해주고 바꿔줘야한다.
: 만약에 환경설정이 바뀌어도 설정을 바꿀것이 없다면 onConfigrationChange() 함수를 구현안하면 된다. 이는 이전에 사용하던 리소스들을 그대로 환경이 바뀌고난 뒤에도 사용하고, 단순히 Activity의 재시작을 방지하고자할 때 이렇게 하면 된다. 하지만 어찌됐든, 앱은 항상 꺼질 준비가 되어있어야하고 꺼지게 되면 이전의 상태를 보존하면서 다시 시작하는 작업 또한 수월하게 해야하고, 사용자가 앱을 사용안할 때 destroy되는 경우가 있으므로 그러한 상태 변환에 대한 대비를 위해 이러한 방식으로의 대비는 좋지는 않다.
* 지금까지 Activity의 라이프사이클과 관련된 내용을 주로 공부했고, 다음에는 Fragment에 대해서 공부해보도록 하자.
끝.
- 다음 글
2012/12/19 - [Android(안드로이드) 앱 개발 응용] 쉽게 Google Map 위에 말풍선 띄우기
2013/03/03 - [Android(안드로이드) 앱 개발 기초] Fragment 기초
2014/09/24 - [Android(안드로이드) 앱 개발 기초] ContentProvider 앱 간 데이터 공유 기본
2014/10/20 - [Android(안드로이드) 앱 개발 기초] MediaPlayer 음악 재생하기
Portions of this page are reproduced from work created and shared by the Android Open Source Project and used according to terms described in theCreative Commons 2.5 Attribution License.
참고: http://developer.android.com/training/basics/activity-lifecycle/index.html
- Total
- Today
- Yesterday
- 안드로이드 앱 개발 기초
- mini project
- HTML5 튜토리얼
- 탐론 17-50
- HTML5
- 샷
- Javascript
- GX-10
- lecture
- google app engine
- 강좌
- ny-school
- Python
- php
- gre
- java
- 자바스크립트
- K100D
- c++
- 속깊은 자바스크립트 강좌
- 안드로이드
- TIP
- 팁
- 사진
- 뽐뿌
- 삼식이
- Writing
- gae
- Android
- 서울
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |