모듈은 소스 파일 및 빌드 설정으로 구성된 모음이며, 이를 통해 프로젝트를 별개의 기능 단위로 분할할 수 있습니다. 프로젝트에서 하나 이상의 모듈이 포함될 수 있으며, 하나의 모듈이 다른 모듈을 종속성으로 사용할 수 있다. 각 모듈은 개별적으로 빌드, 테스트 및 디버그 할 수 있습니다
모듈vs라이브러리
라이브러리도 일종의 모듈입니다
안드로이드 라이브러리와 모듈은 구조적으로 완전히 동일합니다! 구조적으로 동일하다는건 안드로이드 라이브러리도 앱모듈과 동일하게 자바 코틀린파일이나 드로어블같은 리소스파일을 포함해서, 매니페스트까지도동일하게 포함하고 있습니다
차이는 뭘까?
빌드시 어떤 파일로 컴파일되는지의 차이다 라이브러리는 다른 앱모듈이나 다른 라이브러리에 종속적으로 포함되려하는 목적으로 있는거기 때문에 안드로이드 라이브러리는 aar이라는 안드로이드 아카이브파일로, 자바 라이브러리는 jav이라는 자바 아카이브 파일로 컴파일되는데 그래서 aar, jar은 다른 모듈에 종속적으로 포함해서 빌드하는데 사용됨 안드로이드 스튜디오 기준으로 봣을떄 점찍힌게 앱모듈이고책이 안드로이드 라이브러리입니다.
앱모듈은 apk를 빌드해주는거고 안드로이드 라이브러리를 빌드해보면 빌드 결과로 aar파일을 빌드해줍니다.
모듈은 언제 왜, 사용하나?
여러 앱을 빌드할 때, 일부 동일한 구성 요소를 재사용할 때 유용합니다.
또한 앱에 새로운 기능을 추가할때 기존 코드에 기능을 추가하려면 힘이 더 들지만, 새로운 기능을 위한 모듈을 따로 만들고, demoapp 을 만들어서 테스트하면 훨씬 쉽습니다. 만약 기존 앱의 domain, data 계층이 모듈화 되어있다면 기존앱의 리소스들을 그대로 가져다가 테스트할 수 있으므로 유용합니다.
모듈을 나눠서 개발하는 기준이 뭐에요?
(1) 이 기능이 조금이라도 다른 앱에 이용될 수 있는 가능이성이 있을때 (2) 이 기능이 잘 된다면 별도의 앱이 될 수 있다고 생각이 될때 (3) 이 기능이 너무 커서 demo가 실제 기능이 될 때 까지 오랜시간이 걸릴다고 판단될때 (4) 기존 앱이 너무 무거워서 개발할때 앱을 자주 빌드해서 확인하기 버거울 때
멀티 모듈의 장점
(1) 코드 재사용성이 높아진다 (2) 의존성이 적은 코드를 작성할 수 있다. (3) 모듈 단위의 코드 수정이 용이하다. (4) 모듈 단위 테스트를 할 수 있다. (5) 빌드 시간 감소 가능
여기서 주의할 건 빌드 시간 감소 '가능'하다는 겁니다!! 기본적으로 빌드를 할때 변경된 모듈만 빌드하므로 빌드 시간 감소를 기대할 수 있습니다 하지만! 모듈간 종속성이 복잡해지고 모듈의 수정이 많다면 빌드 시간이 증가될 수 있다. 또한 Dagger 같은 AnnotationProcessor 를 사용할때도 빌드 시간이 증가합니다.
단점
(1) 하나의 앱만 있는데, 모듈을 여러개로 나눠놓으면 코드량이 더 많게 느껴진다. (2) 해당 프로젝트를 처음보면 코드 전체를 보고 이해하기 어렵다. (3) 위의 이유로 오히려 빌드 시간이 늘어날 수 있다.
따라서 두개 이상의 앱을 서비스 하고 있거나, 여러 개의 앱으로 분리될 경우를 대비하고 싶은 경우에 잘 변하지 않는 기능/독립적으로 구성가능한 기능을 모듈화 하는게 좋습니다
먼저 가장 기초가 되는filter이다. Boolean 값에 따라 필터링을 할지 안 할지 결정한다. 위의 경우에는 “P”로 시작하는 항목만 true이고, 이 값들만 필터링된다.
FilterNot
dinnerList.filterNot {
it == "Pizza"
}
// [Risotto, Pasta, Hamburger]
filterNot은 말 그대로 조건이 아닌 경우에만 남길 때 사용하는 함수이다. 가독성이 걱정될 때 적절히 사용하면 로직을 보기 좋게 정리할 수 있을 것이다.
FilterIndexed
dinnerList.filterIndexed { index, s ->
index == 3
}
// [Hamburger]
인덱스를 통해 처리하고 싶을 때에는filterIndexed를 통해 인덱스와 값을 각각 받을 수 있다.
List<Any?> 에서의 필터 사용
리스트에null이 포함되어 있는 경우를 살펴보자.
val diNullList = listOf("Pizza", null, "Risotto", "Pasta", "Hamburger")
FilterNotNull
Notnull String에서 사용할 수 있는it.startsWith("P")와 같은 함수를 사용할 수 없어 컴파일되지 않을 것이다.
아래 예제는 간단한 예제이므로it?.startsWith("P") ?: false와 같이 사용할 수도 있지만, 필터를 통해 null값인 경우를 제외하고 시작할 수도 있다.filterNotNull을 사용하면 null 값을 제외한 List를 반환받는다. 이 리스트를 가지고 다시 필터링 할 수 있다.
Mutable 타입일경우 컬렉션의 수정이 가능하기 때문에sort, sorted함수 둘다 존재하며
sort는 리턴값 없이 컬렉션 자체를 정렬해준다.
data class Person(val name : String, val age : Int)
fun main(){
var list = listOf(2,1,3,4)
var list1 = list.reversed() // 순서 반대로
var list2 = list.sorted() // 오름차순 정렬
var list3 = list.sortedDescending() // 내림차순 정렬
var list4 = list.shuffled() // 랜덤 정렬
list1.forEach(){print("$it ")}
println()
list2.forEach(){print("$it ")}
println()
list3.forEach(){print("$it ")}
println()
list4.forEach(){print("$it ")}
println()
var personList = listOf(
Person("Han",25),
Person("Kim",19),
Person("Lee",27),
Person("Choi",25)
)
var personList1 = personList.sortedBy {it.age } // 조건부 오름차순 정렬
var personList2 = personList.sortedByDescending {it.age } // 조건부 내림차순 정렬
var personList3 = personList.sortedWith(compareBy({ it.age }, { it.name })) // 여러 조건으로 정렬
// 클래스배열이 아닌 그냥 배열이면 it.first... 등으로 요소 접근
personList1.forEach(){print("$it ")}
println()
personList2.forEach(){print("$it ")}
println()
personList3.forEach(){print("$it ")}
println()
}
RxKotlin, RxAndroid을 알아보기전에 먼저 알아볼것이 있습니다. 바로Reactive Programing입니다.
컴퓨터 프로그램에는 세가지 종류가 있는데요.
1.주어진 입력 값을 바탕으로 결과를 계산하는 변환 프로그램이며 일반적으로 컴파일러와 수치 계산 을 하는 프로그램입니다.
2.상호작용 프로그램으로 프로그램이 작업을 주도하며 사용자 혹은 다른 프로그램과 상호작용을 합니다. 사용자의 관점에서 볼때는 흔히 말하는 시분할 시스템은 상호작용 프로그램입니다.
3.리액티브 프로그램은프로그램 자신의 주변 환경과 끊임없이 상호작용을 하는데 프로그램이 주도하는 것이 아니라 환경이 변하면 이벤트를 받아 동작합니다.
즉, 근래에 떠오르고 있는Reactive Programing는 데이터의 흐름과 전달에 관한 프로그래밍 패러다임이라고 볼 수 있겠습니다.
(1) 그렇다면 왜 사용할까요? 자 한번 개발자적인 측면에서 생각해보면 간단합니다. 우리 개발자들은 사용자 경험(UX)을 향상시키고 싶어합니다. 사용자 경험(UX)이 좋은 앱을 만든다는것은 반응형 앱을 개발하고 싶은 것 입니다. 그래서 앱에서 메인스레드가 멈추거나 느려지지 않도록 해야하며 사용자들에게 부드러운 사용자 경험(UX)과 좋은 앱 성능을 제공하고 싶어 합니다. 하지만 메인 스레드를 자유롭게 핸들링 하면서 유지하려면 무겁고 시간이 오래 걸리는 작업은 백그라운드에서 해야 합니다. 그리고 백그라운드에서 조차 무겁고 복잡한 계산 작업이라면 서버에서 수행 하는 것이 Best 입니다. 그렇기 때문에네트워크 운영을 위한 비동기 작업이 필요합니다.
(2) 비동기 작업은 AsyncTask로도 되지 않나요? 맞습니다. 하지만2019년 11월 8일 개발자 Charles Munger에 의해 공식적으로 AsyncTask Deprecated되었습니다.즉, 다른 비동기 처리 라이브러리를 써야한다는 것이죠. 그렇다면 왜 이런 상황이 되었나 하고 AsyncTask를 자세히 살펴볼 필요가 있습니다.
현재도 그렇고 과거에도 그랬지만 많은 애플리케이션이 대표적으로 가지는 문제는 서버로부터 데이터를 가져오는 길고 긴 백그라운드 작업입니다. 네트워크 작업은 시간이 짧다면 상관이 없지만 오래 걸리는 상황이 잦기 때문에 비동기적으로 서버에 요청하고 데이터를 받아서 UI 업데이트를 하는 것이 중요합니다. 하지만 네트워크 요청이 완료 될 때 UI와 관련 된 부분에 어떠한 문제가 있어 더 이상 존재하지 않거나 에러가 발생하여 충돌 또는 버그가 발생할 수 있는 근본적인 문제가 있습니다. 이건 일반적인 스레드 프로그래밍이 가지고 있는 위험성과 크게 다르지 않습니다. AsyncTask는 전체적인 프로세스를 단순화 하지만안드로이드의 생명주기를 신경쓰지 않습니다.그렇기 때문에액티비티나 프레그먼트가 안드로이드 생명주기에 의해 재생성되거나 파괴되었을 때 마무리 작업에 대한 내용이 보호되지 않는 불편한 점이 있습니다. 이러한 단점 외에도 기타 단점이 여럿 존재합니다.
(3) 명령형 프로그래밍과 다르다!
명령형 프로그래밍(Imperative programming) – 작성된 코드가 정해진 순서대로 실행됨.
리액티브 프로그래밍(Reactive Programing) – 데이터 흐름을 먼저 정의하고 데이터가 변경되었을 때 연관되는 함수나 메서드가 업데이트 되는 방식.함수형 프로그래밍을 하다보면 비동기 처리가 아쉽기 때문에 리액티브에 절로 눈이가게 됩니다🤤
(4) 그래서 ReactiveX가 나왔다! ReactiveX는비동기 프로그래밍과 Observable 시퀀스를 이용해 이벤트를 처리하기위한 라이브러리입니다.
2. 아하! 이제 알겠어요! RxKotlin, RxAndroid도 같은 건가요?
사실 현재 많은 리액티브 관련 라이브러리가 나와 있는데 대부분 ReactiveX를 사용하기 때문에 RxKotlin, RxJava, RxAndroid, RxSwift 등들은 서로 다른 것이 아니라 하나의 ReactiveX Extensions이라고 보면 되겠습니다. 간단히 정리하면 아래와 같습니다.
RxJava: Java(JVM)를 위한 ReactiveX Extensions Reactive programming(리액티브 프로그래밍) 패러다임을 자바에서 구현한 프로그래밍 라이브러리
RxKotlin: Kotlin을 위한 ReactiveX Extensions RxJava 라이브러리를 기반으로 포팅하여 코틀린을 위한 리액티브 프로그래밍의 특정 부분을 함수형 프로그래밍으로써 구현한 라이브러리
RxAndroid: Android를 위한 ReactiveX Extensions RxJava에 최소한의 클래스를 추가하여안드로이드 앱에서 리액티브 구성요소를 쉽고 간편하게 사용하게 만드는 라이브러리
RxSwift: Swift를 위한 ReactiveX Extensions
위의 ReactiveX Extensions에는 공통점이 있습니다.
효율적으로 신속하게 비동기 처리를 도와줌
함수형 프로그래밍을 일부 지원함
옵저버패턴(Observer pattern)을 사용함
콜백(Callback)에서 또 콜백을 하는 콜백 지옥에서 벗어날 수 있다.
하지만 단점이 제일 크게 다가오는데요... 바로 러닝 커브가 가파르기에 진입장벽이 높습니다..
3. 결론
현재 리액티브 프로그래밍은 트렌드이자 대세이며 반드시 사용해야만 하니 필수로 알아둬야 합니다. 다양한 사용방법, 더 자세한 구조는 글보다는 실제로 사용해보지 않으면 알수가 없고 이는 실무에서 겪어봐야 할 것 같네요.
리액티브 프로그래밍! 꼭 사용합시다!!
더 자세한 내용을 원하시면 아래 참고사항에 사이트, 도서를 추천합니다. 특히 저는 책으로 도움을 많이 받았습니다.
안드로이드 프로젝트를 생성하면 Gradle Scripts 라는 영역에 build.gradle, settings.gradle 등의 파일이 자동으로 만들어진다.
이 파일들을 통해 안드로이드 프로젝트의 빌드 정보, 라이브러리 등을 관리한다.
Gradle Scripts 의 구조
Gradle Scripts는 아래 그림과 같이 총 7개의 파일로 구성되어 있다.(버전에 따라 다소 상이할 수 있다.)
1. build.gradle (Project: 프로젝트명)
: 프로젝트 수준의 그레이들 설정 파일
2. build.gradle (Module: 프로젝트명.app)
: 모듈 수준의 그레이들 설정 파일
: 모듈의 종류는 app모듈, 웨어러블 모듈, 안드로이드TV 모듈 등이 있다.
: 보통 phone&tablet 프로젝트를 생성하기 때문에, 기본으로 app 모듈 수준의 빌드설정/라이브러리 정보가 저장된다.
3. gradle-wrapper.properties (Gradle Version)
: 그레이들 자체와 관련된 설정 파일
4. proguard-rules.pro (ProGuard Rules for 프로젝트명.app)
: 코드 난독화 도구(ProGuard) 설정 파일
: 코드 난독화 시 추가할 규칙이 있다면 이 파일에 기술해주면 된다.
5. gradle.properties (Project Properties)
: 프로젝트 수준의 그레이들 환경 설정 파일
6. settings.gradle (Project Settings)
: 프로젝트에 포함된 모듈을 등록/관리하는 파일
: Phone&Tablet 으로 프로젝트를 생성한 경우, 아래처럼 'app'모듈만 기본으로 등록 되어 있다.
: 웨어러블 모듈이나 안드로이드TV모듈을 추가하면 여기에 등록된다.
7. local.properties (SDK Location)
: 안드로이드 SDK 경로를 관리하는 파일
: 열어보면 그냥 안드로이드SDK가 설치된 경로만 적혀 되어 있다.
1. build.gradle (Project: 프로젝트명)
: 프로젝트 수준의 그래이들 설정 파일이다.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
1) buildscript
- respositories :외부 저장소 설정. google()이 기본으로 설정된다.
- dependencies(의존성:라이브러리): gradle 플러그인 버전 설정
2) allprojects
- respositories : 위의 buildscript > respositories와 동일한 외부저장소가 설정된다.
3) task
: 프로젝트 전체적으로 공통으로 사용할 작업을 정의한다.
- clean(type: Delete): 기본으로 추가된 공통작업으로, 빌드시 생성되는 build디렉터리들을 삭제한다.
2. build.gradle (Module: 프로젝트명.app)
: 모듈 수준의 그레이들 설정 파일로, 각 모듈마다 생성된다.
// 안드로이드 개발 플러그인 설정
plugins {
id 'com.android.application'
}
// 컴파일/빌드/버전 설정
android {
compileSdkVersion 30 // 컴파일 SDK 버전(컴파일러 버전)
defaultConfig {
applicationId "com.example.projectstudy_java" // 패키지명(앱 식별자)
minSdkVersion 19 // 최소 SDK 버전(최소 지원 범위)
targetSdkVersion 30 // 타겟 SDK 버전(개발시 이용하고 있는 라이브러리 버전)
versionCode 1 // 버전 코드(앱의 버전 정보)
versionName "1.0" // 사용자에게 노출되는 버전
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
// 빌드 설정
buildTypes {
release {
minifyEnabled false // 빌드시 코드 난독화 설정(true: realease빌드시에만 코드 난독화)
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
// 컴파일 설정
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
// 외부 라이브러리 설정
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
1) plugins
: 안드로이드 개발을 위한 플러그인 설정 영역으로, 'com.android.application'이 기본으로 지정된다.
2) android
: 컴파일/빌드, 버전정보, 난독여부 등을 설정한다.
: 원래 버전정보 등은 manifest.xml에 설정했으나, 지금은 모듈 수준의 build.gradle 파일에 작성한다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
우리가 액티비티를 새로 생성하면 클래스 안에 onCreate가 자동으로 생성되어 있는 것을 볼 수 있다.
다른 메서드는 없는데 onCreate만!
그 말은 즉슨반드시 구현해야하는 메서드란 뜻이다.
onCreate는 액티비티가 생성되면 가장 먼저 실행되는 메서드이다.
화면 레이아웃 정의, 뷰를 생성하거나데이터 바인딩등을 이곳에서 하면 된다.
그리고 생명 주기 동안 딱 한 번만 실행되는 메서드이기도 하다.
그래서 액티비티최초실행 시에만 해야 할 작업을 여기서 하면 된다.
1-3. onStart
액티비티가 화면에 표시되기 직전에 호출된다.
화면에 진입할 때마다 실행되어야 하는 코드를 이곳에 작성하면 된다.
1-4. onResume
다른 액티비티가 액티비티를 덮어버리거나
앱 사용 중 전화가 와서 잠시 앱을 떠나거나
카톡에서 사진 첨부할 때 '갤러리'가 아닌 '카메라'를 클릭해 카메라를 켠다거나 등의 상황으로
잠시 액티비티가일시정지 되었다가 돌아오는 경우 onResume 메서드가 호출된다.
만약 액티비티가재개되었을 때 실행해야 할 코드가 있다면 이곳에 작성하면 된다.
Regardless of which build-up event you choose to perform an initialization operation in, make sure to use the corresponding lifecycle event to release the resource. If you initialize something after the ON_START event, release or terminate it after the ON_STOP event. If you initialize after the ON_RESUME event, release after the ON_PAUSE event.
그리고 공식문서를 보면 이렇게 적혀있다.
onStart에서 초기화 작업을 했다면 -> onStop에서 리소스 해제/종료 작업을 하고
onResume에서 초기화 작업을 했다면 -> onPause에서 리소스 해제/종료 작업을 해라
이걸 염두에 두고 코드를 작성하면 될 것 같다.
1-5. onPause
방금 앞서 말했듯 방해되는 이벤트가 발생하면 이 메서드가 호출된다.
즉, 사용자가 액티비티를 잠시 떠나기 때문에
리소스를 계속 소모하거나 GPS가 계속 작동하거나 이럴 필요가 없는 것이다.
포그라운드에 있지 않을 때 실행할 필요가 없는기능들을 onPause에서일시 정지하면된다.
종료되기 전에 호출되는 메서드가 onStop, onDestroy 이렇게 2개가 더 있어서
여기서 작업해도 되지 않을까 라는 생각이 들지만
onStop,onDestroy는메모리가 부족하면호출되지 않을 수 있다는 문제가 있다.
onPause()execution is very brief, and does not necessarily afford enough time to perform save operations. For this reason, you shouldnotuseonPause()to save application or user data, make network calls, or execute database transactions; such work may not complete before the method completes.
생명주기를 다룬 공식문서 페이지에서 유일한 굵은 글씨! (아주 중요하다는 뜻이겠지)
onPause는 아주 잠깐 실행되는 메서드이므로
데이터를 저장하거나, 네트워크를 호출하는 등무거운 작업을 하면 안 된다.
메서드가 완료되기 전에 작업이 끝나지 않을 수 있다고 경고하고 있다.
그러면 무거운 작업은 어떻게 해야 하는가? 바로 다음에 설명할 onStop에서 하면 된다.
1-6. onStop
액티비티가 사용자에게 더 이상 보이지 않으면 이 메서드가 호출된다.
앞서 말했듯이무거운 작업을 이곳에서 해주면 된다.
1-7. onRestart
홈으로 나갔다가 다시 돌아오거나
다른 액티비티로 갔다가 뒤로 가기 버튼을 통해서 돌아오는 경우 이 메서드가 호출된다.
1-8. onDestroy
앱을 종료하는경우 호출된다.
onStop에서 혹시 아직 해제하지 못한 리소스 작업을 하면 되는데
앞서 말했듯이 메모리가 부족하면 메서드 호출이 이루어지지 않고 종료될 수 있다는 것에 유의해야 한다.
액티비티 상태
종료될 확률
Created Started Resumed
낮음
Paused
중간
Stoped Destroyed
높음
공식문서에서는 액티비티에 따라 종료될 확률을 위와 같이 설명하고 있다.
확률이 없음이 아니라 낮음이라고 되어있는 거 보니 어쨌든 어느 상황에서든 종료될 확률은 있다는 거고