Activity 코드를 작성하다보면, 한 번쯤은 onCreate()
콜백 메서드를 본 적이 있을 것이다.
안드로이드 Activity의 대표적인 생명주기인 onCreate()는 사실 두 종류
가 존재한다.
아래 코드를 살펴보자.
override fun onCreate(savedInstanceState: Bundle?)
우리가 흔히 마주하는 Activity의 생성
을 알리는 메서드이다.
액티비티가 파괴되기 전까지는 오직 한 번만 호출된다.
따라서, onCreate()
에서 View Inflate
를 진행하고 Activity에서 사용하기 위한 객체를 초기화한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
코드에서 볼 수 있다시피 setContentView(View)
를 호출하여, 액티비티에서 보여줄 View를 Inflate 있다.
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?)
반면, 인자가 조금 다른 다음 메서드를 살펴보자
안드로이드 개발을 어느정도 해본 사람이라면, onCreate()를 오버라이드 하려다가 위 메서드를 잘못 추가하여, setContentView(...)
를 호출해도 화면이 그려지지 않고
앱이 강제 종료
되는 상황을 경험해보았을 것이다.
처음 본 onCreate()와 얼핏 비슷해보이지만, PersistableBundle
이 인자로 추가되어 있다.
위 메서드를 이해하기 위해서는 PersistableBundle
에 대한 이해가 있어야 한다.
Persistable
을 번역기에 입력해보면 지속 가능한
이라는 의미로 해석된다.
이름 그대로, 지속 가능한 Bundle임을 의미한다.
공식 문서에 따르면, 아래와 같이 설명되고 있다.
문자열 키에서 다양한 유형의 값으로의 매핑입니다. 이 클래스에서 지원하는 유형 세트는 의도적으로 디스크에서 안전하게 유지되고 복원될 수 있는 간단한 개체로 제한됩니다.
일반적인 Bundle은 물리 메모리
에서 관리되다가, 애플리케이션 종료
또는 액티비티 종료
상황이 되면 GC(Garbage Collection)
에 의해 수거되거나 메모리 할당 해제
된다.
하지만 PersistableBundle
은 물리 메모리가 아니라, 디스크
에서 관리된다.
간단하게 내부 코드를 살펴보면 다음과 같다.
public final class PersistableBundle
extends BaseBundle
implements Cloneable,Parcelable, XmlUtils.WriteMapCallback {
...
@Nullable
public static byte[] toDiskStableBytes(@NonNull PersistableBundle bundle) throws IOException {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bundle.writeToStream(outputStream);
return outputStream.toByteArray();
}
public void writeToStream(@NonNull OutputStream outputStream) throws IOException {
TypedXmlSerializer serializer = Xml.newFastSerializer();
serializer.setOutput(outputStream, UTF_8.name());
serializer.startTag(null, "bundle");
try {
saveToXml(serializer);
} catch (XmlPullParserException e) {
throw new IOException(e);
}
serializer.endTag(null, "bundle");
serializer.flush();
}
public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
saveToXml(XmlUtils.makeTyped(out));
}
...
}
PersistableBundle의 핵심 메서드는 위 3가지이다.
toDiskStableBytes(PersistableBundle)
을 호출하면, PersistableBundle
을 OutputStream
을 통해 Disk
에 쓰여진다는 사실을 알 수 있다.
이 때, writeToStream(OutputStream)
, saveToXml(XmlSerializer)
가 호출되는데, 이름에서 유추할 수 있는 것처럼 Xml
형태로 Disk에 읽기 쓰기된다.
따라서 앱 종료 후에도 영속적으로
저장해야 하는 간단한 데이터는 PersistableBundle를 통해 관리할 수 있다.
실험해본 결과, 해당 함수는 아무런 설정을 하지 않으면 호출되지 않는다.
또한 호출되는 시점이 꽤나 흥미로웠다.
<activity
android:name=".MainActivity"
android:persistableMode="persistAcrossReboots"/>
Manifest
에서 위 메서드가 호출되기를 원하는 Activity에 persistableMode
속성 값으로 persistAcrossReboots
을 설정해주어야 한다.
그렇지 않으면, 두 번째 onCreate()
메서드는 호출되지 않는다.
onCreate()
가 언제 호출되는지 알아보기 위해 아래 테스트용 코드를 작성해볼 것이다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
Log.d("buna", "persist onCreate")
super.onCreate(savedInstanceState, persistentState)
}
override fun onCreate(savedInstanceState: Bundle?) {
Log.d("buna", "onCreate")
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
...
이렇게 두 메서드를 오버라이드 해놓고 앱을 실행해보자.
재밌게도 첫 번째 onCreate()
메서드만 호출되었다.
이렇게 보고 나니, 왜 두 번째 onCreate()
에서 setContentView()
를 설정해주면 화면을 그리지 못하는지 알 수 있었다.
다음으로, PersistableBundle
에 값을 저장하기 위해 실험해볼 메서드는 onSaveInstanceState()
이다.
override fun onSaveInstanceState(outState: Bundle) {
Log.d("buna", "normal save")
Log.d("buna", "############################")
super.onSaveInstanceState(outState)
// Bundle에 저장
outState.putString("normal in normal", "normal in normal")
}
override fun onSaveInstanceState(
outState: Bundle,
outPersistentState: PersistableBundle
) {
Log.d("buna", "persist save")
Log.d("buna", "############################")![](https://velog.velcdn.com/images/buna1592/post/2bb6041f-44ae-4352-8a83-f9494e183950/image.png)
super.onSaveInstanceState(outState, outPersistentState)
// Bundle에 저장
outState.putString("normal in persist", "normal in persist")
outPersistentState.putString("persist in persist", "persist in persist")
}
onCreate()
실험과 동일하게, 구분을 위해 PersistableBundle 을 가진 메서드
와 아닌 메서드
를 모두 호출해보겠다.
위 코드에서는 다음과 같이 로깅하고 있다.
normal
: Bundle
만 가지고 있는 메서드persist
: PersistableBundle
을 가지고 있는 메서드그리고 화면을 회전시켜 구성 변경
을 시켜보겠다.
호출되는 순서는 fun onSaveInstanceState(Bundle, PersistableBundle)
이 먼저 호출된다.
그리고 해당 함수에서 fun onSaveInstanceState(Bundle)
를 호출하고 있다.
이제 저장한 값들을 복원해보는 실험을 해보자.
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
Log.d("buna", "normal restore : ${savedInstanceState.getString("normal in normal")}")
Log.d("buna", "normal restore : ${savedInstanceState.getString("normal in persist")}")
Log.d("buna", "############################")
super.onRestoreInstanceState(savedInstanceState)
}
override fun onRestoreInstanceState(
savedInstanceState: Bundle?,
persistentState: PersistableBundle?,
) {
Log.d("buna", "persist restore : ${savedInstanceState?.getString("normal in normal")}")
Log.d("buna", "persist restore : ${savedInstanceState?.getString("normal in persist")}")
Log.d("buna", "persist restore : ${persistentState?.getString("persist in persist")}")
Log.d("buna", "############################")
super.onRestoreInstanceState(savedInstanceState, persistentState)
}
구성 변경
으로 인해 액티비티가 파괴되면서 onSaveInstanceState()
가 호출되었던 것을 봤다.
그리고 다시 Activity가 재생성되면서 onRestoreInstanceState()
를 통해 액티비티의 상태를 복원할 것이다.
한 번 로그를 찍어보자.
onRestoreInstanceState()
도 마찬가지로 PersistableBundle
을 가지고 있는 메서드가 우선 호출된다.
결과를 보면, onRestoreInstanceState(Bundle)
의 Bundle
과 onRestoreInstanceState(Bundle?, PersistableBundle?)
의 Bundle
은 서로 같다는 사실을 알 수 있다.
그리고 PersistableBundle
의 데이터도 정상적으로 가져옴을 확인할 수 있다.
그렇다면, fun onCreate(Bundle, PersistableBundle)
은 언제 호출될까?
위 함수는 일반적인 상황에서는 호출되지 않는다.
PersistableBundle
에 대한 추가적인 설명이다.
활동이 이전에 종료되거나 전원이 꺼진 후 다시 초기화되는 경우 이 번들은 onSaveInstanceState에 가장 최근에 제공한 데이터를 포함합니다.
참고: 그렇지 않으면 null 입니다.
즉, 전원을 종료
했다가 다시 전원을 켰을 때
PersistableBundle
에 저장했던 데이터들을 불러온다.
늘 그랬듯이 한 번 실험해보자.
실제 과정을 영상으로 올리고 싶지만 너무 긴 관계로 실험 순서
만 공유하겠다.
Activity
와 Manifest
에 코드를 작성
하고 앱을 실행
시킨다.그럼 아래와 같은 순서로 로그가 출력된다.
그냥 앱을 실행시켰을 때와 달리, persist onCreate
가 출력되었고, 맨 아래에 PersistableBundle
에 저장했던 persist in persist
값까지 정상 출력되고 있다.
그 외에 일반 Bundle 의 값은 당연히 메모리에서 소멸되었기 때문에 null 을 나타낸다.
삽질 TIP. AVD
에서 실험했을 때에는 잘 안 될 수 있기 때문에, 실제 기기
로 테스트하는 것을 권장한다.
onCreate(Bundle)
에 대해 다룬 글은 많지만, onCreate(Bundle, PersistableBundle)
은 다룬 글은 (내 기준) 아예 보지 못했다.
보통 onCreate() 를 호출해도 화면이 그려지지 않는 버그!
라는 주제와 함께 아래의 내용으로 매우 짧게 포스팅이 마무리되는 경우가 많았다.
onCreate(Bundle) 로 변경하세요.
이러한 이유로, 직접 실험을 해보았고 그제서야 무엇인지 파악할 수 있었다.
일반적으로 Disk에 저장하기 위해서는 Database
또는 가벼운 데이터의 경우 SharedPreference
에 자주 저장하기 때문에, 아직까지는 PersistableBundle
의 필요성은 느끼지 못하고 있다.
전체 코드
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
Log.d("buna", "persist onCreate")
super.onCreate(savedInstanceState, persistentState)
Log.d("buna", "${persistentState?.getString("persist in persist")}")
Log.d("buna", "############################")
}
override fun onCreate(savedInstanceState: Bundle?) {
Log.d("buna", "onCreate")
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
override fun onSaveInstanceState(outState: Bundle) {
Log.d("buna", "normal save")
Log.d("buna", "############################")
super.onSaveInstanceState(outState)
// Bundle에 저장
outState.putString("normal in normal", "normal in normal")
}
override fun onSaveInstanceState(
outState: Bundle,
outPersistentState: PersistableBundle,
) {
Log.d("buna", "persist save")
Log.d("buna", "############################")
super.onSaveInstanceState(outState, outPersistentState)
// Bundle에 저장
outState.putString("normal in persist", "normal in persist")
outPersistentState.putString("persist in persist", "persist in persist")
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
Log.d("buna", "normal restore : ${savedInstanceState.getString("normal in normal")}")
Log.d("buna", "normal restore : ${savedInstanceState.getString("normal in persist")}")
Log.d("buna", "############################")
super.onRestoreInstanceState(savedInstanceState)
}
override fun onRestoreInstanceState(
savedInstanceState: Bundle?,
persistentState: PersistableBundle?,
) {
Log.d("buna", "persist restore : ${savedInstanceState?.getString("normal in normal")}")
Log.d("buna", "persist restore : ${savedInstanceState?.getString("normal in persist")}")
Log.d("buna", "persist restore : ${persistentState?.getString("persist in persist")}")
Log.d("buna", "############################")
super.onRestoreInstanceState(savedInstanceState, persistentState)
}
}