본문 바로가기

Android & Kotlin

Lesson 6: App Architecture (Persistence)

4. Designing Entities

Entity: Object or concept to store in the database. Entity class defines a table, each instance is stored as a table row. 

Query: Request for data or information from a database table or tables, or a request to perform some action on the table. 

dataclass를 사용하여 테이블을 정의. 
annotation을 사용하여서 어느 property가 primary key인지 등을 나타낸다. 
interface를 생성하여 Database와 어떻게 interact할 것인지를 정의한다. 
Room에서는 그러한 class를 DAO(Data Access Object)라고 annotate한다. 

5. Exercise: Creating the SleepNight Entity

@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
    @PrimaryKey(autoGenerate = true)
    var nightId: Long = 0L,

    @ColumnInfo(name = "start_time_milli")
    val startTimeMilli: Long = System.currentTimeMillis(),

    @ColumnInfo(name = "end_time_milli")
    var endTimeMilli: Long = startTimeMilli,

    @ColumnInfo(name = "quality_rating")
    var sleepQuality: Int = -1
)

6. Data Access Object(DAO)

Room DB를 사용할 때 database에 SQL query와 매핑된 kotlin 함수를 생성하고 호출하여 query를 던진다. 
그러한 매핑을 DAO(Data Access Object)에 annotation을 사용해서 정의해준다. 그러면 Room은 필요한 코드를 생성해준다. 

DAO를 데이터베이스에 접근하기 위해 정의한 커스텀 인터페이스로 생각해보라. 보통 사용하는 Data Operation을 위해 Room 라이브러리는 annotation을 제공한다. @Insert, @Delete, @Update, @Query. SQLite에서 지원하는 어떤 쿼리도 작성 가능하다. 

7. Exercise: DAO-SleepDatabaseDao

Room makes sure that this live data is updated whenever the database is updated. This means that we only have to get data once. Attach an observer to it and then do the data changes, the UI will update itself to show the changed data without us having to get the data again. 

8. Creating a Room Database

Database라는 annotation을 갖는 abstract database holder class를 생성해야한다. 
이 클래스는 instance가 존재하지 않을 때 database의 instance를 생성하거나 존재하는 Database의 reference를 return 하는 메소드를 갖는다. 

The general purpose of getting a Room Database is 

  • Room database를 extend하는 public abstract class 생성. 이 클래스는 abstract한데 그 이유는 Room이 우리를 대신해서 implementation 해주기 때문이다. 
  • Class를 @Database로 annotate하고 arguments에 database를 위한 entity를 선언하고 version number를 set. 
  • Companion object에 DAO를 return 하는 abstract 메소드나 property를 선언한다. 
  • 전체 앱에서 같은 Room database에 대해서는 단 하나의 instance만 필요하다. 
    그래서 Room database를 singletone으로 만든다. 
    Database가 존재하지 않는 경우에만 Room의 database bulider를 사용하여 database를 생성한다. 존재하면 존재하는 것을 반환한다. 

 

9. Exercise: Creating a Room Database

@Database(entities = [SleepNight::class], version = 1,  exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {

entities: data class를 적어줌
version: Schema를 변경하면 version number를 올려줘야한다. 만약 잊고 처리해주지 않으면 앱이 동작하지 않는다. 
exportSchema는 default로 true이고 database의 schema 폴더에 저장한다. => Database의 version history를 남길 수 있다. 

Companion object는 client가 class의 인스턴스화 없이 database를 생성하고 가져오기 위해 method에 접근하는 것을 허용해준다. 
Companion object의 내부 INSTANCE는 우리가 일단 database가 생성되면 database에 대한 참조를 유지하도록 해준다. 
이것은 DB에 연결을 반복해서 open하는 것(expensive)을 방지해준다. 

@Volatile(휘발성의): It helps us make sure the value of instance is always up to date and the same to all execution threads. The value of a volatiel variable will never be cached, and all writes and reads will be done to and from the main memory. It means that changes made by one thread to INSTANCE are visible to all other threads immediately. 

Synchronized block: Only one thread of execution at a time can enter this block of code which makes sure the database only gets initialized once. 

14. Multithreading and coroutine

예제 앱에는 clear button 이 있는데 앱에 수면 데이터가 쌓여있다고 가정하자. 이 데이터들은 clear(모두 지우기)하면 데이터가 크기 때문에 시간이 오래 걸릴 수 있다. 이런 시간이 오래 걸리는 operation의 경우 seperate thread에서 동작 시켜야한다. 

모바일 기기는 processor가 존재한다. 오늘날은 보통 다수개의 하드웨어 프로세서가 존재하고 각 프로세서는 동시에 동작한다(concurrently). => 이것을 multiprocessing이라고 부른다. 

프로세서를 더 효율적으로 사용하기 위해서 OS는 앱이 프로세서에서 한 개 이상의 Thread를 생성하여 실행하는 것을 허락한다. => 이것을 Multi threading이라고 한다. 

Reader는 각 책을 챕터별로 번갈아 읽을 수는 있지만 한번에 한 권의 책 밖에 읽지 못한다. 

 

 

 

 

위와 같이 threads가 동작하는 것을 manga 하기 위해서는 많은 infrastructure가 필요하다. 
예를 들면,
scheduler: 우선순위를 고려하고 모든 thread가 동작하고 종료되는지 확인해야한다. 사용되지 않는 Thread가 있어선 안된다. 
dispatcher: set up threads. 작업해야할 것을 할당한다. it sends you books that you need to read and specifies a context for that to happen in. 

User-facing application usually has a main thread that runs in a foreground and can dispatch other threads that may run into background. 

안드로이드에서 main thread는 single thread이고 모든 UI의 update를 handle한다. 또 모든 clickhandler와 다른 UI, lifecycle callbacks를 호출한다. 그래서 UI thread라고도 불린다. default thread로 명시적으로 변경하거나 다른 thread에서 동작하는 class를 사용하지 않으면 main thread에서 돌아간다. 이 점이 잠재적인 문제를 야기할 수 있는데 UI Thread는 사용자에게 훌륭한 UX를 제공하기 위해 smooth하게 동작 해야한다. 멈춤없이 update하려면 매 16밀리초 마다 혹은 더 자주 업데이트해야한다. 60Frames/sec.
이 속도에서 완전 부드럽게 동작한다고 느끼는데 이것은 많은 프레임을 짧은 시간 안에 업데이트 하는 것이다. 그래서 안드로이드에서 UI Thread를 blocking 하는 것(UI Thread가 기다리는 것, 아무것도 안하는 것)을 막는 것은 필수적이다. 

대개 많은 작업들, 인터넷에서 데이터 가져오기, 큰 파일 읽기, 데이터베이스에 데이터 쓰기와 같은 것들은 16밀리초 보다 더 오래 걸린다. 이런 코드를 main thread에서 호출하는 것은 app의 멈춤, 버벅임, freeze를 야기한다. 

Main Thread를 너무 길게 block하면 앱이 crash 나거나 application not responding dialogue가 뜨기도 한다. 따라서 long-running operation을 main/UI thread에서 실행하면 안된다! => coroutine!

오래 걸리는 task를 blocking 없이 main thread 에서 사용하기 위해 callback이 사용된다. callback을 사용하면 long-running task를 background에서 시작할 수 있다. task가 완료되면 arguments로 제공된 callback이 호출되어 main thread에 결과를 알려준다. 

callback의 단점: 가독성 저하, 추론하기 어려움, callback 코드는 비동기적으로 미래에 실행되지 때문. 언어의 일부 기능 exception 같은 것의 사용을 허락하지 않는다. 

Coroutines: In Kotlin we have coroutines to handle long-running tasks elegantly and efficiently. 
coroutine은 callback based 코드를 sequential code(가독성 향상, exception 사용가능)로 변경할 수 있게 해준다. 

Coroutines

  • Asynchronous: The coroutine runs independently from the main execution steps of your program. This could be in parallel, on a separate processor, but could also be that while the rest of the app is, for example, waiting for input,  we sneak in a bit of processing. 
  • Non-blocking: The system is not blocking the main or UI Thread. So users will always have the smoothest possible experience, because the UI interaction will always have priority. For coroutines, the compiler will make sure the results of the coroutine are available before continuing or resuming.
  • Sequential Code

Coroutine needs

  • Job: A background job. Conceptually, a job is a cancellable thing with a life-cycle that culminates in its completion. 모든 coroutine은 job이 있고, coroutine을 취소하기 위해 사용할 수 있다. Jobs는 부모-자식 계층구조로 배열되어 있어서 부모에 대한 취소는 즉 모든 자식에 대한 취소를 의미한다. 
  • Dispatcher: The dispatcher sends off coroutines to run on various threads. 
  • Scope: The scope combines information, including a job and dispatcher to define the context in which the coroutine runs.

'Android & Kotlin' 카테고리의 다른 글

[내보내번] Inheritance | Kotlin  (0) 2022.03.02
Overloading / Overriding  (0) 2022.03.02
Lesson 5: App Architecture(UI Layer)  (0) 2022.02.22
공부 주제  (0) 2022.02.19
Lesson 4: Activity & Fragment Lifecycle  (0) 2022.02.11