코드를 읽어서 실행하는 객체, 한번에 한줄씩 차례대로 실행 하나의 프로세스를 실행하면 무조건 쓰레드 하나는 생성됨 그 친구를 메인 스레드라고 함
안드로이드는 화면작업은 !!오직!! 사장(Main Thread/ UI Thread / original thread)만하도록 요구함 네트워크 작업이나 DB작업등은 보조 스레드들이 하도록 함
앞으로 메인스레드에게 무한반복작업 할 생각을 하지 말어라~!
메인 스레드에서 오래 걸리는 프로그램 작업을 한다 가정해보자 그럼 이 작업때문에 아무것도 실행이 안됨 -> 앱이 반응을 안하게됨 -> 5초 이상 동작 안하면 ANR (이 앱이 응답하지 않습니다) error 뜨고 자동으로 앱을 죽여버림...
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="오래 걸리는 작업(Thread 미사용) ANR에러 발생"/>
<!-- 위에 작업 때문에 '확인버튼'이 클릭되지않는 것을 보기 위한 버튼-->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="확인버튼"/>
<!---->
<Button
android:id="@+id/btn_thread"
android:text="오래 걸리는 작업(Thread 사용)"
android:layout_width="match_parent"
android:backgroundTint="@color/black"
android:layout_height="wrap_content"/>
<!-- 스레드 걸리는 시간 카운트 세기 위한 Text -->
<TextView
android:id="@+id/tv"
android:padding="16dp"
android:layout_gravity="center"
android:textStyle="bold"
android:textSize="40sp"
android:textColor="@color/black"
android:text="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
package com.bsj0420.ex48thread1;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
Button btn, btnThread;
TextView tv;
//숫자 카운트 증가 하기 위한 변수
int num=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
//Thread x
btn = findViewById(R.id.btn);
btn.setOnClickListener(view->{
//오래 걸리는 작업 (ex. Network, DB작업 등..)
for (int i=0; i<20; i++) {
num++; //변수 값 1씩 증가 - 1초에 한번씩 값 증가하게 됨
tv.setText(num + ""); //글자 찍기
// setText는 바로바로 보여주는것이 아니다 for문이 완전히 끝나야 찍힘
//스레드를 1초간 잠재우기 -> 느리게 하기위한 장치
try {
Thread.sleep(1000); // .sleep()은 반드시 try-catch 써야됨
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//스레드 생성 o
btnThread = findViewById(R.id.btn_thread);
btnThread.setOnClickListener(view -> {
//오래 걸리는 작업을 별도의 Thread에게 수행하도록
//스레드를 상속 받아 만들어야함
MyThread t = new MyThread(); //직원 채용(스레드 생성)
t.start(); //스레드의 작업 시작 명령 -> Thread 클래스의 run()메소드가 실행됨
});
}
//오래 걸리는 작업을 수행할 스레드의 작업 내역 설계
class MyThread extends Thread {
@Override
public void run() { //스레드가 실행할 코드를 작성하는 영역 (업무 지시서)
//super.run(); //부모의 스레드 가봤자 암것도 없음 지워도 됨~
for (int i=0; i<20; i++) {
num++;
tv.setText(num+""); //오류 - 화면은 메인 스레드만 건드릴 수 있다
try {
Thread.sleep(1000); // .sleep()은 반드시 try-catch 써야됨
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
오직 메인스레드만이 뷰를 건드릴 수 있다는 오류 메세지 확인
UI 변경 작업은 반드시 메인 스레드만 하도록 강제하고 있음
즉, 별도의 스레드는 UI작업이 불가능함
①내가 만든 스레드의 데이터를 메인스레드에게 message 전달하는 Handler 친구(얘도 별도 스레드)
②핸들러는 메세지를 전달해주는데 메인스레드에 MessageQueue라는 트레이 안에 message를 넣어둠
③근데 메인스레드가 트레이를 매번 확인하기 힘겨움 -> 메세지 큐만 따로 확인하는 비서 친구를 또 따로 만듦(rooper- - 얘도 별도 스레드)
④rooper가 메세지큐를 확인하면 사장이 화면작업을 해야하는데 핸들러는 rooper가 확인하는걸 물끄러미 보고 있다가 대신 화면을 처리함 (핸들러는 사장 아들쯤 돼서 사장의 일을 좀 위임받음 -> ui작업도 할수 있음)
🔨 runOnUiThread(new Runnable())
package com.bsj0420.ex48thread1;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
Button btnThread;
TextView tv;
//숫자 카운트 증가 하기 위한 변수
int num=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//스레드 생성 o
btnThread = findViewById(R.id.btn_thread);
btnThread.setOnClickListener(view -> {
//오래 걸리는 작업을 별도의 Thread에게 수행하도록
//스레드를 상속 받아 만들어야함
MyThread t = new MyThread(); //직원 채용(스레드 생성)
t.start(); //스레드의 작업 시작 명령 -> Thread 클래스의 run()메소드가 실행됨
});
}
//오래 걸리는 작업을 수행할 스레드의 작업 내역 설계
class MyThread extends Thread {
@Override
public void run() { //스레드가 실행할 코드를 작성하는 영역 (업무 지시서)
//super.run(); //부모의 스레드 가봤자 암것도 없음 지워도 됨~
for (int i=0; i<20; i++) {
num++;
// ① 캡쳐 사진 확인
//tv.setText(num+""); //메인 스레드가 아닌 내가 작성한 스레드에서 view를 딜롱 쓰면 오류남!
//UI 변경 작업은 반드시 메인 스레드만 하도록 강제하고 있음
//즉 별도의 스레드는 UI작업이 불가능함
// ② 캡쳐 사진 확인
//메인 스레드에게 UI 변경작업 요청
//방법 1. Handler 객체를 이용하는 방법
//별도의 스레드의 데이터를 메인스레드에게 message 전달하는 Handler 친구 - 얘도 별도 스레드
//핸들러는 메세지를 가져다 주는데 메인스레드에 MessageQueue라는 트레이 안에 message를 넣어둠
//근데 매번 확인하기 힘겨움 -> 메세지 큐만 따로 확인하는 친구를 또 따로 만듦(rooper) - 얘도 별도 스레드
//rooper가 확인하면 사장이 작업을 해야하는데 핸들러는 rooper가 확인하는걸 물끄러미 보고 있다가
//대신 화면을 처리함
//근데 핸들러는 사장아들쯤 돼서 사장의 일을 좀 위임받음 -> ui작업도 할수 있음
handler.sendEmptyMessage(0);
//만약 내 스레드안에 지역변수로 값 전달할게 있었으면 sendMessage(보낼 값) 써야함
//현재 우리 코드는 num이 멤버변수로 있기 떄문에 sendEmptyMessage() 한것
//sendMessageDelayed() : ~초 있다가 띄워주라
//////////////////////////////////////
//방법 2. Activity 클래스의 runOnUiThread() 메소드를 이용 - 더 많이 씀!!
// 그냥 내 스레드 안에서 UI 위임자 만들겠어 하고 만든 것
runOnUiThread(new Runnable() { //매개변수로 Runnable 인터페이스
@Override
public void run() {
//이 영역 안에서는 ui작업 가능해짐
tv.setText(num+"");
}
});
try {
Thread.sleep(1000); // .sleep()은 반드시 try-catch 써야됨
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 방법 1
//별도 스레드가 메인 스레드에게 UI 변경작업을 요청할때
//그 메세지를 전달하는 역할을 하는 객체 생성
Handler handler = new Handler(Looper.getMainLooper()){ //생성자 매개변수로 메인스레드의 루퍼 넣어줌
@Override
public void handleMessage(@NonNull Message msg) {
//루퍼가 메세지 확인하면 메인이 할거 내가 위임받아서 처리하는 영역
//sendMessage()를 통해 메세지가 전달되면 자동으로 실행되는 영역
//이 안에서 UI작업이 가능함
tv.setText(num + "");
}
};
}
네트워크 상에 있는 이미지를 읽어와서 이미지뷰에 보여주기
네트워크 작업은 오래걸리는 작업으로 인식하기에 반드시 별도의 스레드가 해야만함!!!! 아님 무조건 오류
주의 네트워크 작업은 반드시 허가(퍼미션)을 받아야한다 [AndroidManifest.xml 에서 작업] => 인터넷과 연결되는 작업은 돈이 들 수 있으니까 사용자에게 허락 받아야함
1. 이미지의 인터넷 주소 준비
2. 스트림을 열수 있는 해임달객체(url) 생성
3. 서버주소까지 연결되는 무지개로드(stream) 열기
4. 네트워크 작업은 반드시 허가(퍼미션)을 받아야한다
5. 스트림을 통해서 이미지 읽어와서 이미지를 가진 자바용 객체로 만들어줘야하는데 안드로이드에선 걜 Bitmap이라고 말함!
(-> 이미지는 바이트의 배열로 생겨 있음 이걸 스트림이 알아서 Bitmap으로 가져옴)
6. 이미지뷰에 비트맵 설정
package com.bsj0420.ex49threadnetworkimage;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.Button;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
Button btn, btn_lib;
ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = findViewById(R.id.btn);
iv = findViewById(R.id.iv);
btn.setOnClickListener(view -> {clickBtn();});
}
void clickBtn(){
//네트워크 상에 있는 이미지를 읽어와서 이미지뷰에 보여주기
//네트워크 작업은 오래걸리는 작업으로 인식하기에 반드시 별도의 스레드가 해야만함!!!!
// ** 주의 ** 네트워크 작업은 반드시 허가(퍼미션)을 받아야한다 [AndroidManifest.xml 에서 작업]
//인터넷과 연결되는 작업은 돈이 들 수 있으니까 사용자에게 허락 받아야함
//1.스레드 바로 생성 및 작업 & 실행까지 한방에
new Thread(){
@Override
public void run() {
//다른 곳에서 데이터를 가져오기 위한 다리인 스트림 생성해야하는데
//그 다리를 만드는 url 생성
//1. 이미지의 인터넷 주소 준비
//안드로이드는 https만 취급 가능
String imgUrl="https://cdn.pixabay.com/photo/2021/05/09/10/51/dalmatian-6240485_960_720.jpg";
//2.스트림을 열수 있는 해임달객체(url) 생성
try {
URL url = new URL(imgUrl); //URL은 예외 처리 필요!
//3. 서버주소까지 연결되는 무지개로드(stream) 열기
InputStream inputStream = url.openStream(); //new로 객체 생성 못함 url에게 통해서 오픈
//4. 스트림을 통해서 이미지 읽어와서 이미지를 가진 자바용 객체로 만들어줘야하는데
// 안드로이드에선 걜 Bitmap이라고 말함!
// -> 이미지는 바이트의 배열로 생겨 있음 이걸 스트림이 알아서 Bitmap으로 가져옴
Bitmap bm = BitmapFactory.decodeStream(inputStream); //BitmapFactory 비트맵으로 만들어주는 공장
//5. 이미지뷰에 비트맵 설정
//iv.setImageBitmap(bm); //ERROR : UI작업은 main Tread만 할수 있으니까
//5-1) UI Tread (메인스레드)에서 ui 작업 하도록 요청
runOnUiThread(new Runnable() {
@Override
public void run() {
//5-2) 이미지뷰에 setImageBitmap()
iv.setImageBitmap(bm); //이미지 뷰 작업 여기서~
}
});
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}.start();
}
}
Glide.with(this).load(imgUrl).into(iv);
Glide.with(context).load(이미지).into(이미지 붙일 곳);
package com.bsj0420.ex49threadnetworkimage;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.Button;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
Button btn_lib;
ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = findViewById(R.id.iv);
// 이미지로드 라이브러리 사용
btn_lib = findViewById(R.id.btn_lib);
btn_lib.setOnClickListener(view -> {
//1. Glide
String imgUrl = "https://cdn.pixabay.com/photo/2019/04/22/04/50/flower-4145675_960_720.jpg";
Glide.with(this).load(imgUrl).into(iv);
//2. Picasso
});
}
}
상태 진행 바
package com.bsj0420.ex50threadprogressdialog;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_wheel).setOnClickListener(view -> ClickBtnWheel()); //실행문이 한줄일땐 {}도 생략 가능
findViewById(R.id.btn_bar).setOnClickListener(view -> CliBtnBar());
}
ProgressDialog dialog;
//프로그래스 값 증가 시키기 위한 변수
int gauge =0 ;
void ClickBtnWheel(){
// 휠 타입의 progress dialog
// 언제까지라는 말이 없을 때 사용
if(dialog != null) return; //같은 객체가 두개 일까봐 예외처리
dialog = new ProgressDialog(this);
dialog.setTitle("wheel dialog");
dialog.setMessage("downloading...");
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); //스타일 속성 무조건!
dialog.setCanceledOnTouchOutside(false);
dialog.show();
//테스트 목적으로...
//3초 뒤에 다이아 로그 종료시키기
handler.sendEmptyMessageDelayed(0,3000);
//dialog.dismiss(); // 다이아 로그 해제
}
Handler handler = new Handler(Looper.getMainLooper()){ //이 방법으로 인트로 페이지 설정도 가능
@Override
public void handleMessage(@NonNull Message msg) {
dialog.dismiss();
dialog = null; //null 까지 해야 없어짐
}
};
void CliBtnBar(){
if(dialog != null) return;
dialog = new ProgressDialog(this);
dialog.setTitle("Bar Type Dialog");
dialog.setMessage("다로드중");
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMax(100);
dialog.setCanceledOnTouchOutside(false);
dialog.show();
//막대바의 게이지값을 증가시ㅣ는 별도의 Thread
new Thread(){
@Override
public void run() {
//1.시작하면 게이지 0으로 초기화
gauge =0;
//2.
while (gauge < 100) {
gauge++;
dialog.setProgress(gauge); // 다이아로그도 UI인데 내부적으로 runOnUiThread() 알아서함
//0.05초 대기
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
dialog.dismiss();
dialog = null; //다이아 로그 참조값도 null로 만듦
}
}.start();
}
}
cf) 3초뒤 보여줘라 방법을 쓰면 인트롤 화면도 할 수 있음
앱이 끝나면 메인 스레드는 죽지만 별도의 스레드는 여전히 살아 있다
액티비티가 죽어도 프로세스가 살아 있어서 계속 별도스레드가 계속 실행됨 -> 배터리가 닳거나 별도스레드에서 네트워크에서 뭘 받아오는 중이었다면 데이터가 사라진다,,,,
☝ 안드로이드 31버전 부턴 뒤로가기 버튼을 눌러서 메인화면이 종료되어도 앱이 종료되지않고 숨겨놓는다
public void onBackPressed() {
finish(); //화면 종료
}
package com.bsj0420.ex51thread2;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
MyThread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//화면에 보이지 않더라도 별도 스레드는 백그라운드에서 동작하고 있다는 것을 확인하기 위한 예제
// 5초마다 토스트 띄워주는 예제 생성
findViewById(R.id.btn).setOnClickListener(view -> {
thread = new MyThread();
thread.start();
});
}
//이너 클래스///////////////////////////////////////////
class MyThread extends Thread{
boolean isRun = true;
@Override
public void run() {
//5초 마다 현재 시간을 토스트로 보이도록..
while (isRun) {
//현재시간을 문자열
Date now = new Date(); //객체로 만들어진 순간의 시간을 담음
String nowS = now.toString();
//토스트로 현재 시간 보여주기 => 토스트도 view라서 별도 스레드는 바로 거드릴 수 없음
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, nowS, Toast.LENGTH_SHORT).show();
Log.i("EX51" , nowS);
}
});
//5초간 잠재우기
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
//액티비티가 메모리에서 없어질때(종료) 자동으로 실행되는 콜백 메소드
@Override
protected void onDestroy() {
super.onDestroy();
//안드로이드 스튜드오의 [Logcat]탭에 기록(log) 남기기
Log.i("EX51", "메인 화면 onDestroy");
}
//디바이스의 [뒤로가기 버튼]을 클릭 했을 때 반응하는 callback메소드
@Override
public void onBackPressed() { //그래서 백버튼 눌렀을 때 다이아로그 띄우는 거 여기다 하면됨
//super.onBackPressed();
//첫번째 화면은 MainActivity를 뒤로가기를 눌러도 안꺼짐 (단지 안보일뿐)
//강제로 MainActivity 종료
finish(); //화면 종료
}
}
액티비티가 메모리에서 없어질때(종료) 자동으로 실행되는 콜백 메소드 onDestroy() 할때 내가 만든 스레드도 반드시!! 종료 시켜주자!!
package com.bsj0420.ex51thread2;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
MyThread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//화면에 보이지 않더라도 별도 스레드는 백그라운드에서 동작하고 있다는 것을 확인하기 위한 예제
// 5초마다 토스트 띄워주는 예제 생성
findViewById(R.id.btn).setOnClickListener(view -> {
thread = new MyThread();
thread.start();
});
}
//이너 클래스///////////////////////////////////////////
class MyThread extends Thread{
boolean isRun = true;
@Override
public void run() {
//5초 마다 현재 시간을 토스트로 보이도록..
while (isRun) {
//현재시간을 문자열
Date now = new Date(); //객체로 만들어진 순간의 시간을 담음
String nowS = now.toString();
//토스트로 현재 시간 보여주기 => 토스트도 view라서 별도 스레드는 바로 거드릴 수 없음
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, nowS, Toast.LENGTH_SHORT).show();
Log.i("EX51" , nowS);
}
});
//5초간 잠재우기
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Log.i("EX51" , "myThread 종료!!!!!!!");
}
}
//액티비티가 메모리에서 없어질때(종료) 자동으로 실행되는 콜백 메소드
@Override
protected void onDestroy() {
super.onDestroy();
//안드로이드 스튜드오의 [Logcat]탭에 기록(log) 남기기
Log.i("EX51", "메인 화면 onDestroy");
//MyThread의 작업을 그만 하도록 해야할 의무가 있음!!!!!!
thread.isRun = false;
thread = null; //객체 해제
}
//디바이스의 [뒤로가기 버튼]을 클릭 했을 때 반응하는 callback메소드
@Override
public void onBackPressed() { //그래서 백버튼 눌렀을 때 다이아로그 띄우는 거 여기다 하면됨
//super.onBackPressed();
//첫번째 화면은 MainActivity를 뒤로가기를 눌러도 안꺼짐 (단지 안보일뿐)
//강제로 MainActivity 종료
finish(); //화면 종료
}
}