과정 4 : 생명주기와 로깅(Lifecycles and logging)
# 시작하며
안드로이드를 하나씩 공부해나가고 있지만, 할때마다 느끼는건 기본이 부족하다는 것을 느낀다. 뭔가 메뉴얼대로 공부하면 정말 좋을텐데 싶어 공식문서도 읽어보고하다가 코드랩이 있는 것을 발견하고 하나씩 따라해보기로 했다. 그중 생명주기와 로깅파트가 어렵지는 않지만 기본적인 개념을 이해하는데 중요하다고 생각이 들었다.
# TODO
코드랩에서는 감사하게도 샘플 스타터 코드를 제공해준다. 0부터 만드는 것이 아닌, 기본적인건 다 짜여있고 공부할 내용들만 추가해나가면서 직접 확인해보는 시간이다. 제공된 디저트 클리커앱은 가운데 디저트를 누르면 Sold개수와 $가 증가한다. 그리고 해당 앱을 통해서 생명주기를 알아보고, Timber를 사용하기 위한 Application클래스를 배운다. 추가적으로 앞전 강좌에서는 Data Binding을 학습할 수 있었다. View Binding이랑은 비슷하면서도 다른 느낌 이었다.
- 생명주기(Life Cycles)
- Timber
- Application 클래스
- Data Binding
# 생명주기(Life Cycles)
생명주기는 액티비티 또는 프래그먼트가 지니는 생성부터 소멸까지를 관리하기 위해서 존재한다. 해당 코드랩에서는 마치 나비의 생명주기에 비유했다.
프래그먼트는 액티비티와 비슷한 생명주기를 가진다. 그러나 그 쓰임에 맞게 액티비티 안에서 하나의 View로써 기능을 하지만 그 View가 독립적인 생명주기를 가진다. 다시 말해 하나의 화면(액티비티)에 여러개의 2개의 프래그먼트를 띄우면 두개의 프래그먼트는 독립적인 라이프 사이클을 가진다는 것이다.
위 액티비티의 생명주기에서 크게 세가지에 주목해보자. 바로 onCreate와 onDestroy, focus와 visible이다.
안드로이드 프로젝트를 진행하면서 위 다이어그램과 생명주기라는 말을 꽤 많이 들어봤지만, focus와 visible이 대체 뭐가 다른지에 대한 고민을 항상 가지고 있었고, 그래서 '생명주기가 어쩌라고?' 생각하기만 했었는데, 이번기회에 나름 중요한 특징을 알 수 있었다.
## onCreate
onCreate는 액티비티가 생성됨에 있어서 반드시 한번 실행되어야 한다. 또한 액티비티가 Destroy 즉 없어지지 않는 이상 두번 실행되느 일은 없다.
just after the activity is initialized (when the new Activity object is created in memory). After onCreate() executes, the activity is considered created.
,
For example, in onCreate() you inflate the layout, define click listeners, or set up data binding.
액티비티가 생성되면 메모리에 액티비티가 할당되고 나서 바로 onCreate가 호출 된다.
## onStart
액티비티가 onCreate 또는 onRestart되면서 실행되는 곳이다. 이 구간을 지났다는 뜻은 현재 화면에 액티비티가 보인다(visible)하다는 소리이다. 즉 화면에서 현재 액티비티가 보이지 않는 경우( 홈버튼 클릭 시 )는 onStart와 대비되는 onStop구간이 실행되고 현재 화면에 액티비티가 보이지 않는 다는 뜻이 된다.
## onResume
액티비티에 현재 focus가 됐다는 뜻, 즉 사용자(user)가 화면을 터치하는등의 동작을 통해서 상호작용(interact)을 할 수 있다. 아직은 focus가 애매모호 하다는 것을 알 수 있다. 이제 디저트 클릭커 앱을 통해서 확인해보자.
## 생명주기 경험해보기
앱을 시작한 상태로 생명주기 구간에 로그를 찍어 놓고 확인해보자. 최초로 앱을 실행하면 onCreate-> onStart -> onResume 까지 거쳐 사용자가 컵케이크를 누르면 반응함을 알 수 있다. 즉 현재 visible -> focus 상태이다. 여기서 공유하기 버튼을 누르면 현재 액티비티의 화면은 보이지만 그 화면에 interact할 수 없음을 알 수 있다. 이 상태가 focus가 된 상태가 아닌 것 이다.
이렇게 화면이 visible상태인 것과 focus된 상태의 차이점을 알 수 있었다. 여기서 공유하기 창을 꺼버리면 onResume이 호출되어 다시 focus상태가 됨을 확인 할 수 있다.
이제 홈키를 눌러서 앱을 백그라운드 상태로 만들면, 앱은 종료되지 않았지만 화면에는 표시되지 않는다. 이 상태는 visible상태(onStart)에 대비되는 onStop이 호출 된 이후의 상황이 될 것이다. 다번에 onStop이 호출되는 것이 아닌 onPause가 호출 된 후 onStop이 호출된다.
구글 코드랩에서는 이렇게 onStop이후 백그라운드에서 관리 될 것을 코딩하라고 했다.
The important point here is that onStart() and onStop() are called multiple times as the user navigates to and from the activity. You should override these methods to stop the app when it moves into the background, or start it up again when it returns to the foreground.
## 정리
즉 핵심적으로 기억할 것은 아래와 같이 나눠 볼 수 있을 것이다.
- onCreate와 onDestory는 오직 한번씩만 실행 됨
- onStart, onStop은 visible을 관리함, 이것이 호출되면 화면에 해당 액티비티가 보이거나(foregroude) 안보이거나(background)를 관리함
- onResume, onPause는 focus의 상태를 관리 즉 유저가 어떠한 상호작용을 할 수 있는 상태
# Timber
팀버는 로깅을 좀더 쉽게 도와주는 라이브러리이다.
implementation 'com.jakewharton.timber:timber:4.7.1'
위와 같이 종속관계를 추가하여 라이브러리를 사용할 수 있다.
Timber.i("onCreate called")
다음과 같이 Tag를 따로 작성하지 않아도 실행된 액티비티에서의 태그가 작동한다. 팀버를 사용하기 위해서는 팀버를 base class에 추가해야 한다.
# Application class ( Base class )
위에서 추가했던 팀버와 같이, 앱 전체단에서 관리해야하는 것들이 존재할 것이다. Application class를 통해서 생성 할 수 있으며 기본적으로 가장 먼저 실행된다. 따로 해당 클래스를 만들지 않는 경우에는 안드로이드가 자체적으로 지정한 베이스 클래스가 실행된다.
BaseClass로 사용할 코틀린 파일을 생성해 준다. 이번 예제에서는 ClickerApplication이라는 이름을 사용 했다.
class ClickerApplication : Application(){
override fun onCreate() {
super.onCreate()
// init Timber library
Timber.plant(Timber.DebugTree())
Timber.i("Applcition!")
}
}
위와 같이 Applcation을 상속하는 베이스클래스를 만들어 준 후, 메니페스트에 가서 해당 베이스클래스를 사용한다고 알려주어야 적용된다.
아래와 같이 name속성에 내가 만든 베이스 클래스를 추가해주면 끝이다.
<application
...
android:name=".ClickerApplication"
...
>
이제 앱이 시작되면 해당 베이스클래스가 가장 먼저 실행되고 Timber라이브러리를 사용 할 수 있게 초기화 과정을 맞추어 메모리에 올려 놓는다.
# Data Binding
View Binding이랑은 비슷한 기능을 하면서 추가 기능을 제공해준다. 무조건 Data Binding이 좋은 건 아니고 속도의 차이도 나기 때문에 상황에 맞는 것을 사용해야 한다. 여기서는 이런게 있다고만 정리해두록 하겠다.
앱 단위에의 build.gradle에 아래와 같이 옵션을 추가해준다.
android {
buildFeatures {
dataBinding true
}
}
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use Data Binding to get reference to the views
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.dessertButton.setOnClickListener {
onDessertClicked()
}
// Set the TextViews to the right values
binding.revenue = revenue
binding.amountSold = dessertsSold
// Make sure the correct dessert is showing
binding.dessertButton.setImageResource(currentDessert.imageId)
}
위와 같이 binding해서 사용한다. view Binding이랑 비슷하다.
뷰 바인딩과 다르게 레이아웃(xml)에서 동적인 데이터를 관리 할 수 있다.
// Set the TextViews to the right values
binding.revenue = revenue
binding.amountSold = dessertsSold
액티비티에서 위 코드는 레이아웃(xml)에서 아래와 같이 사용 할 수 있다.
<data>
<variable
name="revenue"
type="Integer" />
<variable
name="amountSold"
type="Integer" />
</data>
<TextView
android:id="@+id/revenue_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{"$" + revenue.toString()}'/>
<TextView
android:id="@+id/amount_sold_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{amountSold.toString()}"/>
# 끄읏!
코드랩이 생각보다 친절하다! 그리고 영문이 한글보다 이해가 더 잘되는 아주 이상한.... 번역이다