1. 메인 스레드와 백그라운드 스레드
프로세스(process)란?
🚀 프로세스(process)란 단순히 실행 중인 프로그램(program)으로 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 말함
🚀 이러한 프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성
스레드(thread)란?
🚀 스레드(thread)란 프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미
➡️ 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행
🚀 두 개 이상의 스레드를 가지는 프로세스를 멀티스레드 프로세스(multi-threaded process)라고 함
📍 앱이 처음 시작될 때 시스템이 스레드 하나를 생성하는데 이를 메인 스레드라고 함
⚡️ 메인 스레드의 역할
- 액티비티의 모든 생명 주기 관련 콜백 실행을 담당
- 버튼, 에디트텍스트와 같은 UI위젯을 사용한 사용자 이벤트와 UI드로잉 이벤트를 담당. UI 스레드라고도 불림.
작업량이 큰 연산이나, 네트워크 통신, 데이터베이스 쿼리 등은 처리에 긴 시간이 걸림. 이 모든 작업을 메인 스레드의 큐에 넣고 작업하면 한 작업의 처리가 완료될 때까지 다른 작업을 처리하지 못하기 때문에 사용자 입장에서는 마치 앱이 먹통이 된 것처럼 보이게 됨. 또, 몇 초 이상 메인 스레드가 멈추면 '앱이 응답하지 않습니다.'라는 메시지를 받게 됨
📍 백그라운드 스레드를 활용하면 이러한 먹통 현상을 피할 수 있음. * 백그라운드 스레드 = 워커 스레드
✓ 메인 스레드에서 너무 많은 일을 처리하지 않도록 백그라운드 스레드를 만들어 일을 덜어주는 것
✓ 백그라운드 스레드에서 복잡한 연산이나, 네트워크 작업, 데이터베이스 작업 등을 수행
✓ 주의할 점은 '백그라운드 스레드에서는 절대로 UI 관련 작업을 하면 안 된다는 점
➡️ 각 백그라운드 스레드가 언제 처리를 끝내고 UI에 접근할지 순서를 알 수 없기 때문에 UI는 메인 스레드에서만 수정할 수있게 한 것. 따라서 백그라운드 스레드에서 UI 자원을 사용하려면 메인 스레드에 UI 자원 사용 메시지를 전달하는 방법을 이용해야 함
📍 UI 스레드에서 UI 작업을 하는데 Handler 클래스, AsyncTask 클래스, runOnUiThread() 메서드 등을 활용할 수 있음
1) runOnUiThread() 메서드
🚀 runOnUiThread()는 UI 스레드(메인 스레드)에서 코드를 실행시킬 때 쓰는 액티비티 클래스의 메서드
Activity.java에 정의된 메서드
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
✓ if문을 살펴보면, 만약 현재 스레드가 UI 스레드가 아니면 핸들러를 이용해 UI 스레드의 이벤트큐에 action을 전달 post
✓ 만약 UI 스레드이면 action.run()을 수행. 즉 어떤 스레드에 있던지 runOnUiThread() 메서드는 UI스레드에서 Runable 객체를 실행
📍 다음과 같은 UI 관련 코드를 runOnUiThread()로 감싸주어 사용
runOnUiThread(object : Runnable {
override fun run() {
// 여기에 원하는 로직을 구현
}
})
📍 코틀린의 SAM Single Abstract Method를 사용하면 더 간단하게 표현
runOnUiThread {
// 여기에 원하는 로직을 구현
}
2. 스톱워치 만들기
1) 기본 레이아웃 설정
colors.xml과 strings.xml 설정
- 스톱워치에서 4가지 색상과 3가지 문자열을 사용
- [app] - [res] - [values] colors.xml에 파랑, 빨강, 노랑을 추가
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<!-- 직접 추가한 색 -->
<color name="blue">#603CFF</color>
<color name="red">#FF6767</color>
<color name="yellow">#E1BF5A</color>
</resources>
- [app] - [res] - [values] strings.xml에서 '시작', '일시정지', '초기화' 문자열을 추가
<resources>
<string name="app_name">thread_0529</string>
<!-- 추가한 문자열 -->
<string name="start">시작</string>
<string name="pause">일시정지</string>
<string name="refresh">초기화</string>
</resources>
버튼 추가
- [초기화]와 [시작] 버튼 생성
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="80dp"
android:backgroundTint="@color/blue"
android:padding="20dp"
android:text="@string/start"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/btnRefresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="50dp"
android:backgroundTint="@color/yellow"
android:padding="20dp"
android:text="@string/refresh"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/btnStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
텍스트뷰 추가
- 흐르는 시간을 표현해줄 텍스트뷰를 생성
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvMinute"
android:text="00"
android:textSize="45sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvSecond"
android:text=":00"
android:textSize="45sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvMillisecond"
android:text=".00"
android:textSize="30sp" />
- 레이아웃 미리보기 창에서 텍스트뷰들을 드래그하여 원하는 위치로 놓아줌
📍 수직 방향 제약 추가하기
- 세 텍스트 뷰의 수직 방향에 제약을 추가 ▶️ 초를 나타내는 텍스트뷰를 중심으로 왼쪽에는 분을 나타내는 텍스트뷰, 오른쪽에
는 밀리초를 나타내는 텍스트 뷰를 위치 - 먼저 초 텍스트뷰의 위쪽을 레이아웃의 위쪽에 초 텍스트 아래쪽을 [초기화] 버튼의 상단에 드래그 ▶️ 그럼 텍스트뷰가 상하 제약의 중간 지점에 놓임
- 분 텍스트뷰와 밀리초 텍스트뷰를 일직선 위에 놓이도록 제약을 추가.
✓ 모든 텍스트뷰를 일직선 위에 놓으려면 베이스라인을 사용
분 텍스트뷰 위에서 마우스 우클릭 -> [Show Baseline]을 선택. 그럼 텍스트의 아래쪽에 베이스라인 막대가 보임.분
텍스트뷰 막대를 초 텍스트뷰 막대 모양에 드래그. 밀리초 텍스트뷰 역시 똑같은 방법으로 베이스라인을 정렬.
📍 수평 방향 제약 추가하기
⚡️ 컨스트레인트 레이아웃에는 뷰 여러 개의 수직 또는 수평 여백을 손쉽게 관리하는 체인 Chain을 제공
- Ctrl 키를 누른 상태에서 세 텍스트뷰를 모두 클릭하여 선택
- 선택한 텍스트뷰 위에서 마우스 우클릭 후, [Chains] - [Create Horizontal Chain]을 선택
- 그럼 다음과 같이 세 텍스트뷰가 수평 방향으로 균등한 여백을 두고 위치
- 딱 붙어있어야 되는 경우 세 텍스트뷰 위에서 마우스 우클릭 후, [Chains] - [Horizontal Chain Style] - [packed] 선택
2) 버튼에 이벤트 연결하기
MainActivity.kt
class MainActivity : AppCompatActivity(), View.OnClickListener {
var isRunning = false // 실행 여부 확인용 변수
private lateinit var btnStart: Button
private lateinit var btnRefresh: Button
private lateinit var tvMinute: TextView
private lateinit var tvSecond: TextView
private lateinit var tvMillisecond: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 뷰 가져오기
btnStart = findViewById(R.id.btnStart)
btnRefresh = findViewById(R.id.btnRefresh)
tvMinute = findViewById(R.id.tvMinute)
tvSecond = findViewById(R.id.tvSecond)
tvMillisecond = findViewById(R.id.tvMillisecond)
// 버튼별 OnClickListener 등록
btnStart.setOnClickListener(this)
btnRefresh.setOnClickListener(this)
}
override fun onClick(p0: View?) {
when(p0?.id) {
R.id.btnStart -> {
if(isRunning) {
pause()
} else {
start()
}
}
R.id.btnRefresh -> {
refresh()
}
}
}
private fun start() {
// 스톱워치 측정을 시작하는 로직
}
private fun pause() {
// 스톱워치 측정을 일시정지하는 로직
}
private fun refresh() {
// 초기화하는 로직
}
}
✓ 클릭 이벤트를 처리하는 View.OnClickListener 인터페이스를 구현
✓ 스톱워치가 현재 실행되고 있는지를 확인하는 데 사용하는 isRunning 변수를 false로 초기화해 생성
✓ findViewById() 메서드로 xml 레이아웃 파일에서 정의한 뷰들을 액티비티에서 사용할 수 있게 가져옴
✓ btnStart와 btnRefresh에 구현한 리스너를 등록. setOnClickListener() 메서드를 이용해서 onClickListener를 추가해주어야 클릭이 가능
✓ 클릭 이벤트가 발생했을 때 어떤 기능을 수행할 지 구현. 따라서 View.OnClickListener 인터페이스는 반드시 onClick() 메서드를 오버라이딩해야함.
3) 스톱워치 시작 기능 구현하기
[시작] 버튼을 누르면 스톱워치가 시작되고 [시작] 버튼 텍스트가 '일시정지'로 바뀜
MainActivity에 타이머 관련 변수 timer와 time을 추가하고 초기화
class MainActivity : AppCompatActivity(), View.OnClickListener {
var isRunning = false // 실행 여부 확인용 변수
var timer: Timer? = null
var time = 0
start() 메서드를 구현
private fun start() {
btnStart.text = "일시정지" // 버튼 텍스트 변경
btnStart.setBackgroundColor(getColor(R.color.red)) // 배경색을 빨강으로 변경
isRunning = true // 실행상태 변경
// 스톱워치 측정을 시작하는 로직
timer = timer(period = 10) {
time ++ // 0.01초마다 time에 1을 더함
// 시간 계산
val milliSecond = time % 100
val second = (time % 6000) / 100
val minute = time / 6000
runOnUiThread { // 텍스트뷰가 UI 스레드에서 실행되도록 메서드 사용
// 시간이 한 자리일 때 전체 텍스트 길이를 두 자리로 유지
if (isRunning) {
tvMillisecond.text = if (milliSecond < 10) ".0${milliSecond}" else
".${milliSecond}" // 밀리초
tvSecond.text = if (second < 10) ":0${second}" else ":${second}" //초
tvMinute.text = if (minute < 10)"0${minute}" else "${minute}"//분
}
}
}
}
💡 코틀린에서 제공하는 timer(period = [주기]) {} 메서드는 일정한 주기로 반복하는 동작을 수행할 때 사용
{} 안에 쓰인 코드들은 모두 백그라운드 스레드에서 실행
주기를 나타내는 period 변수를 10으로 지정했으므로 10밀리초마다 실행
4) 스톱워치 멈춤 기능 구현
private fun pause() {
// 텍스트 속성 변경
btnStart.text = "시작"
btnStart.setBackgroundColor(getColor(R.color.blue))
// 스톱워치 측정을 일시정지하는 로직
isRunning = false // 멈춤 상태로 전환
timer?.cancel() // 타이머 멈추기
}
5) 스톱워치 초기화 기능 구현
private fun refresh() {
timer?.cancel() // 백그라운드 타이머 멈추기
btnStart.text = "시작"
btnStart.setBackgroundColor(getColor(R.color.blue))
isRunning = false // 멈춤 상태로 전환
// 초기화하는 로직
time = 0
tvMillisecond.text = ".00"
tvSecond.text = ":00"
tvMinute.text = "00"
}
[ 내용 참고 : IT 학원 강의 ]
'Android Studio' 카테고리의 다른 글
[Android Studio] 반응형 UI 만들기 (0) | 2024.05.30 |
---|---|
[Android Studio] 컨스트레인트 레이아웃 ConstraintLayout (0) | 2024.05.30 |
[Android Studio] 맵 클러스터링 (0) | 2024.05.28 |
[Android Studio] 서울 공공도서관 앱 개발하기 (0) | 2024.05.26 |
[Android Studio] 레트로핏 데이터 통신 라이브러리 (0) | 2024.05.26 |