본문 바로가기

수업 정리

74일차 수업 정리(Android - Thread)

**안드로이드의 ANR(Application Not Responding)

    => Activity가 사용자의 이벤트에 반응하지 못하는 현상

    => 안드로이드에서는 일정 시간동안 사용자의 이벤트에 반응하지 못하면 시스템이 Activity를 종료시킴

    => 서버에 처리를 요청한 경우 오래 걸리면 이런 현상이 벌어짐

        - 서버에서는 빠르게 처리하지만 실제 안드로이드 기기까지 결과가 도착하는데 시간이 오래걸려 발생하는 현상

        - 스마트폰은 무선 사용(무선은 접속에 시간이 많이 소요)하고, 움직이며 사용하므로 네트워크 상황 장담 불가

    => 스마트폰 API에서 이런 작업은 스레드를 이용하여 처리하는 것을 권장

    => 네트워크 작업은 스레드를 이용하지 않을 경우 작업을 수행하지 않고, 예외 발생

        - iOS의 경우 강제는 아니지만 일정시간 반응하지 않을경우 앱 자체를 reject 시켜버림

 

**Thread

    => Process : 실행중인 프로그램(자원 공유 X)

        - Process는 하나의 Process가 종료되어야만 다른 Process에 제어권이 넘어감

    => Thread : Process안에 존재하는 자원 할당 및 실행의 단위

        - 실행중에 제어원을 다른 thread에게 넘길 수 있음

        - 사용중인 자원을 다른 스레드가 수정시 문제가 발생

    => 2개이상의 스레드를 만들어 실행시키는 경우 공유자원 문제에 신경 써야 함

    => 동시 수정문제(상호배제), 생상자와 소비자 문제, 데드락

    => 데몬 스레드의 개념 알아보기

 

**Android에서의 Thread : GUI 프로그램은 모두 유사

    => Android App이 실행되면 운영체제는 App을 하나의 스레드를 할당하여 App을 실행

        - 이 스레드를 main Thread라고 하는데, GUI프로그램에서 이 thread만이 UI 변경이 가능하여 UI Thread라고도 함

    => main thread 이외의 thread에서 UI를 변경할 수 있게되면 main thread가 스레드에 안전하기 않게되어 자원의 공유 문제 발생

    => Android에서는 메인 스레드 이외의 스레드에서 UI를 갱신해도 에러가 발생하지 않는 경우도 있음

        - 이러한 코드는 운영체제 버전 별로 다르게 동작

    => 안드로이드에서는 자바 기반의 스레드 사용 가능

1. 자바 API를 이용해서 스레드를 생성하는 방법

    => Runnable 인터페이스를 이용하는 방법

    => Thread 클래스를 이용하는 방법

    => Callable 인터페이스를 이용하는 방법 : 스레드가 수행을 종료하고 데이터를 리턴할 수 있음

 

2. 일반 메소드에서 출력하는 코드는 마지막에 모아서 처리

    => onCreate 메소드에서 오랜 시간이 걸리는 작업을 스레드없이 수행하게 되면 작업이 끝날 때까지 화면을 출력할 수 없음

        - onCreate에서 오랜시간이 걸리는 작업시 반드시 스레드로 처리해야 함

 

3. 실습

    => 별도의 스레드 생성 없이 onCreate에서 1초마다 출력하는 작업을 10번 수행

  1) Application 생성

 

  2) activity_main.xml 파일에 화면 디자인

    => TextView 1개와 버튼 1개 배치

<?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"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:id="@+id/txtdisplay"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="클릭"
        android:id="@+id/btnstart" />

</LinearLayout>

 

  3) MainActivity.java 파일의 onCreate메소드에 작성

      //뷰 찾아오기
        txtdisplay = (TextView)findViewById(
                R.id.txtdisplay);
        btnstart = (Button)findViewById(
                R.id.btnstart);

        //스레드를 사용하지 않고 작업을 수행
        //스레드를 사용하지 않고 화면 갱신하는 작업을 하면
        //작업이 모두 종료되고 화면 갱신이 수행됩니다.
        int val = 0;
        for(int i=0; i<20; i=i+1){
            try{
                txtdisplay.setText(val++ + "");
                Thread.sleep(1000);
            }catch(Exception e){}
        }

 

  4) 실행

    => 스레드를 이용하지 않아서 20초가 지나야만 화면에 UI가 출력

 

4.onCreate 에서 20초 동안 수행했던 작업을 스레드를 이용해서 수행하도록 코드를 변경하고 실행

//스레드 생성
Thread th = new Thread(){
//스레드로 수행할 내용을 작성하는 메소드
	public void run(){
		int val = 0;
		for(int i=0; i<20; i=i+1){
			try{
				txtdisplay.setText(val++ + "");
				Log.e("val" , val + "");
				Thread.sleep(1000);
			}catch(Exception e){}
		}
	}
};
//스레드 시작
th.start();

=>실행시 20초를 기다리지 않고 UI는 출력되었지만 작업 내용을 화면에 출력불가(Main thread 외에는 화면을 갱신불가)

 

**안드로이드에서 스레드를 이용한 화면 갱신

    => 일반 스레드로는 화면 갱신을 할 수 없음

    => 스레드가 작업을 전부 수행한 후 Main Thread에게 화면 갱신을 요청하는 신호를 보내야 함

    => 안드로이드는 Handler, AsyncTask, Lopper 3가지 방법을 이용하여 Main Thread Message Queue에 명령을 전달하여 화면 갱신

    => Thread + Handler, AsyncTask, Looper 형태로 사용

 

**Handler

    => 스레드 간의 메시지나 Runnable 객체를 주고 받는 클래스

    => 하나의 스레드와 같이 사용

    => 스레드가 메시지를 보내면 public void handleMessage(Message msg) 메소드가 호출됨

1. Message 클래스

    - int what : 동일한 메시지를 사용하는 핸들러를 구분하기 위한 값을 저장

    - int arg1 

    - int arg2

    - Object obj

    => 위의 4개는 전부 데이터를 전달하기 위한 속성(obj에 대입한 경우 형변환 하여 사용해야 함)

    -Messenger replyTo : 메시지에 대한 응답을 받을 객체를 지정

 

2. 핸들러에게 메시지를 전달하는 메소드

    - 핸들러.sendMessage(Message msg); : 메시지 큐레 저장하여 실행 - 다른 메시지가 있으면 처리 후 수행

    - 핸들러.sendAtFrontOfQueue(Message msg); : 메시지 큐의 첫번째 저장하여 실행 - 다른 메시지 존재여부과 관계없이 수행

    - 핸들러.sendMessageAtTime(Message msg, long uptimeMillis); : 두번째 매개변수 시간에 수행

    - 핸들러.sendMessageDelayes(Message msg, long delayMillis); : 현재시간에서 두번째 매개변수만큼 지난 다음 수행

 

3. 핸들러를 호출만 하는 메소드

    - 핸들러.sendEmptyMessage(int what);

 

4. send 대신에 post를 사용하면 다른 메시지가 전부 처리된 후에 처리해달라는 요청

 

5. Handler 객체와 Message 객체

    => Handler 객체는 Anonymous 로 만드는 public void handleMessage 메소드르르 오버라이딩 해야 함

        - 전에는 default constructor를 이용했는데 최근 API에서는 경고 발생

        - Looper를 대입하여 생성

    => Message객체는 default constructor로 직접 생성해도 되고, Handler의 obtain이라는 메소드를 이용하여 리턴 받아 사용해도됨

 

**실습

    => 이전 예제를 핸들러를 이용하여 주기적으로 값을 갱신

5. onCreate 메소드에서 스레드를 생성

        final Handler handler = new Handler();
        //스레드 생성
        Thread th = new Thread(){
            //스레드로 수행할 내용 작성 메소드
            public void run(){
                for(int i=0; i<20; i+=1){
                    try {
                        //별도의 스레드에서 직접 출력하는 것은 안됨
                        //txtdisplay.setText(i + "");

                        //메시지 객체 생성
                        Message message = new Message();
                        //메시지에 데이터를 저장
                        message.what = i;
                        handler.sendMessage(message);

                        Thread.sleep(500);
                    }catch (Exception e){}
                }
            }
        };
        //스레드 시작
        th.start();

 

6. Activity클래스에 핸들러 객체를 생성

        //핸들러 객체 생성
        final Handler handler = new Handler(Looper.getMainLooper()){
            //핸들러에게 메시지를 보내면 이 메소드가 호출되어 메인스레드에게 작업 처리 요청
            //이 메소드에서 데이터 출력을
            @Override
            public void handleMessage(Message msg){
                //전송된 데이터 가져오기
                int data = msg.what;
                //텍스트 뷰에 출력
                txtdisplay.setText(data + "");
            }
        };

    => Thread에서 데이터를 만들고 Thread가 Handler를 호출하여 데이터를 출력

 

**PostMessage

    => SendMessage 메소드는 Message를 매개변수로 받아서 메시지 큐에 저장하고 순서대로 처리

    => post메소드는 Runnable 인터페이스의 객체를 매개변수로 받아서 다른 작업이 없으면 Runnable의 내용을 수행

    => 이 경우에는 Runnable 인터페이스의 run메소드에 UI갱신 코드를 작성해도 됨

1. handler의 handleMessage 메소드를 삭제

 

2. Thread 생성 코드 수정

public void run(){
	for(int i=0; i<20; i+=1){
		try {
			//별도의 스레드에서 직접 출력하는 것은 안됨
			//txtdisplay.setText(i + "");

			//메시지 객체 생성
			Message message = new Message();
			//메시지에 데이터를 저장
			message.what = i;
			//handler.sendMessage(message);

			//다른 작업이 없을 때 처리하도록 전송
			handler.post(new Runnable() {
			@Override
			public void run() {
				txtdisplay.setText(val++ + "");
				}
			});
			Thread.sleep(500);
		}catch (Exception e){}
	}
}

 

**작업 스케쥴링

    => 일정한 시간 후나 정해진 시간에 작업을 수행하도록 하는 것

        - sendMessageAtTime(Message msg, long uptimeMillis);

        - sendMessageDelayed(Message msg, long delayMillis);

 

        - postAtTime(Runnable r, long uptimeMillis);

        - postDelayed(Runnable r, long delayMillis);

 

**진행상황 출력

    => 스레드에서 오랜 시간이 걸리는 작업 수행시, 작업의 진행상황을 출력해주는 것이 좋음

        - 작업의 진행 상황은 ProgressDialog나 ProgressBar를 이용하여 출력

        - 안드로이드 8.0부터는 ProgressDialog가 deprecated

    => 대화상자의 구분

        - Modal : 화면에 출력되면 다른 UI로의 전환 불가(다른 UI출력, 사용을 위해서는 대화상자 종료)

        - Modeless : 화면에 출력이 된 상태에서도 다른 UI로 전환이 가능한 대화상자

    => ProgressDialog가 Modal Dialog

        - Modal Dialog는 사용자와의 인터럭션이 좋지 않다고 함

        - 8.0 이후에서는 ProgressDialog를 사용하지 않고 ProgressBar를 사용하는 것을 권장

    => 버튼을 눌러서 ProgressDialog 출력하여 진행률을 표시

1. HandlerActivity에 인스턴스 2개 선언

    => 대화상자와 대화상자가 없어졌는지 확인 할 변수

//대화상자
ProgressDialog progressDialog;
//대화상자가 표시중인지 여부를 저장할 변수
boolean isQuit;
//대화상자의 값으로 사용할 변수
int value;

 

2. onCreate 메소드에서 버튼의 클릭 이벤트 작성

        //버튼의 클릭이벤트 처리
        btnstart.setOnClickListener(new Button.OnClickListener(){

            @Override
            public void onClick(View view) {
                value = 0;
                //대화상자 만들기
                progressDialog = new ProgressDialog(
                        MainActivity.this
                );
                progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                progressDialog.show();
                isQuit = false;
                progressHandler.sendEmptyMessage(0);
            }
        });

 

3. Activity 클래스에 핸들러 작성

 

 

**스레드와 핸들러 사용

Handler 핸들러 = new Handler(Looper.getMainLooper()){
	@Override
    public void handleMessage(Message msg){
    	//핸들러가 수행할 내용
        //msg를 이용하여 전달된 데이터를 저장
        
        //데이터를 출력
    }
};

Thread 스레드 = new Thread(){
	@Override
    public void run(){
    	//스레드가 수행할 내용
        
        //데이터 가져오기
        
        //데이터 파싱
        
        //핸들러 호출
        Message 메시지 = new Message();
        메시지.? = epdlxj;
        핸들러.sendMessage(메시지);
        
        //화면에 무엇인가를 출력하는 것은 안됨
    }
};

    => 버튼을 누르면 https://www.naver.com의 내용을 읽어서 텍스트뷰에 출력하기   

        - 다운로드 받는 코드는 스레드에 작성해야 하고, 다운로드 받은 코드를 출력하는 핸들러를 이용

1. 실행가능한 Activity 생성

 

2. 화면 디자인

    => 버튼 1개와 텍스트뷰 1개

<?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"
    tools:context=".DownloadActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/naverdownload"
        android:text="네이버"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/naverhtml"/>
</LinearLayout>

 

3. Activity.java 파일에 뷰를 위한 인스턴스 변수 생성

public class DownloadActivity extends AppCompatActivity {
    private Button naverdownload;
    private TextView naverdisplay;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download);

        naverdownload = (Button)findViewById(R.id.naverdownload);
        naverdisplay = (TextView)findViewById(R.id.naverdisplay);
    }
}

 

4. Activity.java 파일에 스레드와 핸들러 작성

       final Handler handler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(Message obj){
                //데이터 읽어오기
                String html = (String)obj.obj;
                //데이터 출력
                naverhtml.setText((html));
            }
        };

        final Thread th = new Thread(){
            @Override
            public void run(){
                //핸들러에게 전달할 데이터를 저장할 객체
                Message message = new Message();
                //데이터 가져오기
                try {
                    URL url = new URL("https//www.naver.com");
                    HttpURLConnection con = (HttpURLConnection) url.openConnection();
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(con.getInputStream()));
                    StringBuilder sb = new StringBuilder();
                    while(true) {
                        String line = br.readLine();
                        if(line == null)
                            break;
                        sb.append(line + "\n");
                    }
                    String html = sb.toString();
                    //데이터 저장
                    message.obj = html;

                    br.close();
                    con.disconnect();

                }catch (Exception e) {}
                handler.sendMessage(message);
            }
        };

 

5. Activity.java 파일의 onCreate 메소드에서 버튼 클릭시 스레드를 시작하는 코드 작성

naverdownload = (Button)findViewById(R.id.naverdownload);
naverhtml = (TextView)findViewById(R.id.naverhtml);
        
naverdownload.setOnClickListener(new Button.OnClickListener() {
	@Override
	public void onClick(View view) {
		th.start();
	}
});

 

6. AndroidManifest.xml 파일에 인터넷 사용 권한을 추가

<uses-permission android:name="android.permission.INTERNET"/>

 

**AsyncTask

    => Thread와 Handler의 역할을 합친 클래스

    => 이 클래스를 이용한 작업은 짧은 시간의 작업에만 추천, 장시간 작업은 Thread(Runnable or Callable) + Handler 조합을 추천

1. 클래스 생성

    - class 클래스 extend AsyncTask<T1, T2, T3>으로 생성

  1) T1은 백그라운드 작업을 위한 doInBackground() 메소드의 매개변수 자료형

  2) T2는 백그라운드 작업을 위한 doInBackground() 메소드에서 발생한 데이터를 publishProgress() 메소드를 이용해서 전달하는데 이 때 전달하는 데이터의 자료형

  3) T3는 doInBackground()의 리턴 타입이면서 onPostExecute()의 매개변수 자료형

 

2. 재정의하는 메소드

  1) doInBackground(): 필수

    => 스레드로 동작할 코드를 작성

  2) onPreExecute()

    => doInBackground가 호출되기 전에 실행되는 메소드

    => 프로그래스 바 나 대화상자를 출력하고 데이터를 초기화

  3) onProgressUpdate()

    => doInBackground 에서 publishProgress()를 호출하면 호출되는 메소드

    => 주기적으로 호출해서 프로그래스 바나 대화상자를 업데이트

  4) onPostExecute()

    => doInBackground 의 실행 종료 후 호출되는 메소드로 doInBackground의 리턴 값을 매개변수로 받습니다.

  5) onCancelled(): doInBackground() 수행 중 작업이 취소되면 호출되는 메소드

  6) doInBackground를 제외하고는 전부 메인 스레드에서 수행됩니다.

    => doInBackground를 제외하고는 UI를 갱신해도 됩니다.

 

3. 객체 생성 및 실행

    - new 클래스(매개변수).execute(매개변수);

 

4. 실습

    => 2개의 버튼과 1개의 ProgressBar를 배치하여 버튼을 누를 시 ProgressBar의 값을 순차적으로 증가, 다른버튼 입력시 작업 중지

    => 이 작업을 Thread + Handler 조합으로 해도 됨

  1) 실행 가능한 Activity 추가

 

  2) 레이아웃 설정

    => TextView 1개, PrograssBar 1개, Button 2개

<?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"
    tools:context=".AsyncActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="스레드 상태"
        android:id="@+id/state"/>

    <ProgressBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:id="@+id/progress"
        style="?android:attr/progressBarStyleHorizontal"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="스레드 시작"
            android:id="@+id/threadstart"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="스레드 중지"
            android:id="@+id/threadcancel"/>

    </LinearLayout>
</LinearLayout>

 

  3) Activity.java 파일에 스레드로 작업 수행, 작업 중이나 종료시 UI를 갱신할 수 있는 AsyncTask 클래스 생성하고 인스턴스 변수 선언

    private TextView state;
    private ProgressBar progress;
    private Button threadstart;
    private Button threadcancel;

    //프로그래스의 값을 저장할 변수
    int value;

    //비동기적으로 실행하는 클래스를 생성
    //첫번째 제너릭은 백그라운드 작업을 위한 doInBackground()의 매개변수
    //두번째 제너릭은 doInBackground에서 중간중간에 호출하는 publishProgress 함수의 매개변수 자료
    //세번째 제너릭은 doInBackground의 리턴 타입으로 onPostExecute()
    //메소드의 매개변수 자료형

    class BackgroundTask extends AsyncTask<Integer, Integer, Integer>{
        //맨 처음 한번만 호출되는 메소드
        @Override
        public void onPreExecute(){
            //초기화 작업 수행
            value = 0;
            progress.setProgress(value);
        }
        //백그라운드에서 작업하는 메소드
        @Override
        //execute 메소드를 호출할 때 대입하는 값이 values에 대입
        protected Integer doInBackground(Integer... integers) {
            while(isCancelled() == false){
                //value의 값을 1씩 증가 - 100이 되면 중지
                //100이 안되면 publishProgress를 호출하여 UI를 갱신
                Random rand = new Random();

                value += rand.nextInt(10);
                if(value >= 100)
                    break;
                else {
                    //이 메소드 호출시 onProgressUpdate가 호출
                    //진행율을 표시할 수 있게 해줌
                   publishProgress(value);
                }
                try{
                    Thread.sleep(300);
                }catch (Exception e){}
            }
            return value;
        }

        @Override
        //doInBackground에서 publishProgress를 호출하면
        //호출되는 메소드 - 주기적인 UI 갱신
        public void onProgressUpdate(Integer ... values){
            //프로그래스 바 설정
            progress.setProgress((values[0]));
            state.setText("값: " + values[0]);
        }

        @Override
        //doInBackground 수행이 정상 종료되었을 때 호출되는 메소드
        //매개변수는 doInBackground의 리턴 값
        public void onPostExecute(Integer result){
            progress.setProgress(0);
            state.setText("스레드 정상 종료");
        }

        @Override
        //정상 종료가 되지 않고 강제 종료가 되었을 때 호출되는 메소드
        public void onCancelled(){
            progress.setProgress(0);
            state.setText("스레드 강제 종료");
        }
    }

    //AsyncTask 변수
    private BackgroundTask task;

 

  4) onCreate 메소드에 뷰를 찾아오고 버튼을 눌렀을 때 이벤트 처리를 수행

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async);

        state = (TextView)findViewById(R.id.state);
        progress = (ProgressBar) findViewById(R.id.progress);
        threadstart = (Button) findViewById(R.id.threadstart);

        threadstart.setOnClickListener(new Button.OnClickListener(){
            public void onClick(View view){
                //AsyncTask 객체를 생성하고 시작
                //BackgroundTask의 onPreExecute() -> doInBackground(100)으로 호출
                task = new BackgroundTask();
                task.execute(100);
            }
        });
        threadcancel = (Button)findViewById(R.id.threadcancel);
        threadcancel.setOnClickListener(new Button.OnClickListener(){

            @Override
            public void onClick(View view) {
                //AsyncTask 중지
                task.cancel(true);
            }
        });
    }

 

  5) Thread와 Handler를 이용하여 구현한 경우의 중지

    => Thread클래스의 run 메소드에 InterruptedException이 발생시 return 하도록하고, 중지하려면 스레드 객체가 interrupt()를 호출

    => daemon 스레드가 아닌 스레드는 자신의 수행코드를 전부 수행하고 종료

    => 앱은 화면에서 제거 되었는데 스레드는 계속 작업하는 경우가 발생할 수 있음

Thread th = new Thread(){
	public void run(){
    	try{
        
        }catch(InterruptedException e){
        	return
        }catch(Exception e){
        
        }
    }
}

 

**Looper

    => 애플리케이션 내의 MessageQueue를 감시, 필요시 메시지 추출 및 추출 메시지를 핸들러의 handleMessage 메소드를 호출하여 전달

    => 안드로이드 애플리케이션에는 내부적으로 1개의 Looper가 할당

        - 화면 갱신을 하고자 하는 경우 미리 할당된 Looper를 이용

    => 개발자가 만든 스레드끼리 통신을 하고자 하는 경우 별도의 Looper를 생성하여 수행해야 함

1. 사용

    - 스레드 내부에서 Looper.prepare 라는 메소드를 호출하여 준비하고, Looper.loop()라는 메소드를 호출하여 구동

 

2. 주의 할 점

    - Looper는 무한 루프로 반복하여 수행되므로 반드시 quit()를 호출하여 종료시켜 주어야 함

    => Activity가 파괴 될때(onDestroy) 메소드에서 루프를 포함한 핸들러.getLooper().quit()를 호출

 

3. 사용

    => 이전 : Thread와 Handler를 별도로 만들어 Thread에서 핸들러에게 메시지를 보내는 구조를 사용

        - 현재 : Thread 안에 Handler 제작, 핸들러 객체 생성전 Looper.prepare() 호출 및 핸들러 작성을 끝내고 Looper가 loop()를 호출

 

4. Looper 실습

    => 랜덤하게 정수 10개를 생성하여 2개의 ListView에 출력

        - 1초에 1개씩 생성하여 출력

  1) 실행가능한 Activity를 생성

 

  2) 레이아웃 작성

<?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="horizontal"
    tools:context=".LooperActivity">

    <ListView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:textAlignment="center"
        android:id="@+id/lv_left"/>

    <ListView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:textAlignment="center"
        android:id="@+id/lv_right"/>
    
</LinearLayout>

 

  3) Activity 파일에 필요한 인스턴스 변수 선언

    //뷰 객체
    private ListView lv_left;
    private ListView lv_right;
    //ListView는 MVC 패턴을 구현하기 위해서
    //List와 ListAdapter를 이용하여 출력

    //ListView에 출력할 데이터 변수
    ArrayList<String> leftlv, rightlv;
    //List와 ListView를 연결해 줄 컨트롤러 변수
    ArrayAdapter<String> leftAdapter, rightAdapter;

    //핸들러 변수
    Handler handler;

    //화면을 갱신하는 스레드
    class OneThread extends Thread{
        //핸들러를 인스턴스 변수로 만든이유는 다른 스레드에서
        // 이 핸들러에 메시지 전달하기 위해
        Handler oneHandler;
        public void run(){
            Looper.prepare();
            oneHandler = new Handler(Looper.getMainLooper()){
                @Override
                public void handleMessage(Message msg){
                    //안드로이드에서 예외처리없이 대기
                    SystemClock.sleep(500);
                    //anonymous class에서 사용하기 위해 final 변수로 변환
                    //못고치게 하는 게 목적이면 final int DATA로 표기
                    final int data = msg.arg1;
                    if(msg.what == 0){
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                rightlv.add("right: " + data);
                                //리스트 뷰를 재출력
                                rightAdapter.notifyDataSetChanged();
                            }
                        });
                    }else{
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                leftlv.add("left: " + data);
                                //리스트 뷰를 재출력
                                leftAdapter.notifyDataSetChanged();
                            }
                        });
                    }
                }
            };
            Looper.loop();
        }
    }

    //위의 스레드에 대한 변수 생성
    OneThread oneThread;
    TwoThread twoThread;

    //데이터를 생성해주는 스레드
    //0.1초마다 랜덤한 정수를 생성하여 OneThread의 oneHandler에게 메시지를 전송하는 스레드
    class TwoThread extends Thread{

        public void run(){
            Random rand = new Random();
            for(int i=0; i<10; i +=1){
                int data = rand.nextInt(100);
                SystemClock.sleep(100);

                //UI갱신을 위해 핸들러에게 메시지를 전송
                Message message = new Message();
                //what은 구분하기 위해서 주로 사용
                if(data % 2 == 0)
                    message.what = 0;
                else
                    message.what = 1;

                //arg는 데이터 전달을 위해 주로 사용됨
                message.arg1 = data;
                message.arg2 = i;

                //핸들러에게 메시지를 전송
                oneThread.oneHandler.sendMessage(message);
            }
        }
    }

 

  4) onCreate 메소드에 스레드 객체들을 생성하고 실행하는 코드 작성

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_looper);

        lv_left = (ListView)findViewById(R.id.lv_left);
        lv_right = (ListView)findViewById(R.id.lv_right);

        //뷰에 연결할 데이터를 생성 - Model
        leftlv = new ArrayList<>();
        rightlv = new ArrayList<>();

        //뷰와 모델 연결
        leftAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, leftlv);
        rightAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, rightlv);
        lv_left.setAdapter(leftAdapter);
        lv_right.setAdapter(rightAdapter);

        //핸들러와 스레드 객체를 생성하고 시작
        handler = new Handler();
        oneThread = new OneThread();
        twoThread = new TwoThread();
        oneThread.start();
        twoThread.start();
    }

 

**비동기적으로 실행하고 그 결과를 가지고 UI 갱신하는 방법

1. Thread(비동기적 실행) + Handler(UI갱신)

2. AsyncTask(내부의 메소드들을 이용해서 비동기적 실행과 UI 갱신을 수행)

3. Looper(스레드, 핸들러를 만들어 그 안에 별도의 메시지 큐를 만들어 사용)

    => 아주 많은 스레드가 별도로 동작해야 하는 경우 사용 - 게임

 

Tip!

1. 파일 입출력시 FileInputStream -> BufferedInputStream이 좋음

    - FileInputStream -> FilePrintStream(모아서 작업)

2. 동시수행, 데드락, 생산자와 소비자 문제 개념 이해

3. 데몬스레드 - 다른 스레드가 동작중일 때만 동작(누군가에게 종속적으로 만드는 경우)