본문 바로가기

카테고리 없음

74일차 공부 안드로이드 Thread(+Handler, AsyncTask, Looper)

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

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

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

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

서버에서는 빠르게 처리를 하지만 실제 안드로이드 폰까지 그 결과가 도착하는데 오래 걸리는 경우가 발생해서 벌어지는 현상

스마트 폰은 무선을 사용하는데 무선은 접속을 하는데 시간이 오래 걸리며 움직이면서 사용하기 때문에 네트워크 상황을 장담할 수 없음

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

=>네트워크 작업은 스레드를 이용하지 않으면 작업을 수행하지 않고 예외를 발생시킴

iOS는 강제는 아니지만 일정 시간동안 반응하지 않으면 앱 자체를 reject 시켜 버림

 

**Thread

=>Process: 실행 중인 프로그램, Process끼리는 자원을 공유할 수 없음, 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 파일에 화면 디자인

 

<?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 파일에서 이벤트 처리하는 코드를 작성

 

package com.example.android0721;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

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

        //스레드를 사용하지 않고 작업을 수행
        //스레드를 사용하지 않고 화면 갱신하는 작업을 하면 작업이 모두 죵료되고 갱신이 수행된다.
        //onCreate에서 시간이 오래걸리는 작업을 하면 안된다. 화면이 작업이 다 완료 될때까지 화면 출력이 안되기 때문
        int val = 0;
        for(int i=0; i<20; i=i+1){
            try{
                txtdisplay.setText(val++ + "");
                Thread.sleep(1000);
            }catch(Exception e){}
        }
        btnstart.setOnClickListener(new Button.OnClickListener(){
            @Override
            public void onClick(View view) {

            }
        });
    }
}

 

4) 실행 결과

=> 스레드를 이용하지 않아서 20초가 지날 때까지 화면이 출력되지 않는다.

 

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

 

package com.example.android0721;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

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

        //스레드를 사용하지 않고 작업을 수행
        //스레드를 사용하지 않고 화면 갱신하는 작업을 하면 작업이 모두 죵료되고 갱신이 수행된다.
        //onCreate에서 시간이 오래걸리는 작업을 하면 안된다. 화면이 작업이 다 완료 될때까지 화면 출력이 안되기 때문
        /*
        int val = 0;
        for(int i=0; i<20; i=i+1){
            try{
                txtdisplay.setText(val++ + "");
                Thread.sleep(1000);
            }catch(Exception e){}
        }
        */

        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();

        btnstart.setOnClickListener(new Button.OnClickListener(){
            @Override
            public void onClick(View view) {

            }
        });
    }
}

 

=> 실행을 하면 20초를 기다리지 않고 UI는 출력되었지만 작업 내용을 화면에 출력하는 것은 안된다.

Main Thread를 제외하고는 화면을 갱신할 수 없기 때문이다.

 

 

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

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

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

=> 안드로이드에서는 Handler, AsyncTask, Looper 3가지 방법을 이용해서 Main Thread에 Message Queue에 명령을 전달해서 화면 갱신을 할 수 있다.

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

 

** Handler

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

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

=> 스레드가 메세지를 보내면 publice 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이라는 메소드를 이용해서 리턴받아서 사용해도 된다.

 

** 실습

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

 

1. 실행 가능한 Activity를 생성(HandlerActivity)

 

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"
    tools:context=".HandlerActivity"
    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. HandlerActivity.java 파일에 대한 참조 변수를 생성

 

package com.example.android0721;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

    }
}

 

 

4. HandlerActivity.java 파일의 onCreate 메소드에 뷰를 찾아오는 코드를 작성

 

package com.example.android0721;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

        txtdisplay = (TextView)findViewById(R.id.txtdisplay);
        btnstart = (Button)findViewById(R.id.btnstart);
    }
}

 

 

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

 

package com.example.android0721;

        import androidx.appcompat.app.AppCompatActivity;

        import android.os.Bundle;
        import android.os.Message;
        import android.widget.Button;
        import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

        txtdisplay = (TextView)findViewById(R.id.txtdisplay);
        btnstart = (Button)findViewById(R.id.btnstart);

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

                        //메세지 객체 생성
                        Message message = new Message();
                        //메세지에 데이터를 저장
                        message.what = i;
                        //핸들러에게 메세지 전송
                        handler.sendMessage(message);

                        Thread.sleep(1000);
                    }catch(Exception e){}
                }
            }
        };
        th.start();

    }
}

 

 

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

 

package com.example.android0721;

        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 HandlerActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

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

        txtdisplay = (TextView)findViewById(R.id.txtdisplay);
        btnstart = (Button)findViewById(R.id.btnstart);

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

                        //메세지 객체 생성
                        Message message = new Message();
                        //메세지에 데이터를 저장
                        message.what = i;
                        //핸들러에게 메세지 전송
                        handler.sendMessage(message);

                        Thread.sleep(1000);
                    }catch(Exception e){}
                }
            }
        };
        th.start();

    }
}

 

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

 

 

 

** PostMessage

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

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

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

 

1. handler의 handleMessage 메소드를 삭제

 

package com.example.android0721;

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 HandlerActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

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

        txtdisplay = (TextView)findViewById(R.id.txtdisplay);
        btnstart = (Button)findViewById(R.id.btnstart);

        //스레드 생성
        Thread th = new Thread(){
            //스레드로 수행할 메소드
            int val = 0;
            public void run(){
                for(int i=0; i<20; i=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(1000);
                    }catch(Exception e){}
                }
            }
        };
        th.start();
    }
}

 

 

** 작업 스케쥴링

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

sendMessageAtTime(Message msg, long uptimeMillis);

sendMessageDelayed(Message msg, long dalayMillis);

 

postAtTime(Runnable r, long uptimeMillis);

postDelayed(Runnable, long dalayMillis);

 

** 진행 상황 출력

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

작업의 진행 상황은 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;

 

package com.example.android0721;

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;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

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

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

        txtdisplay = (TextView)findViewById(R.id.txtdisplay);
        btnstart = (Button)findViewById(R.id.btnstart);

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

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

                        //다른 작업이 없을 때 처리하도록 전송
                        long curtime = System.currentTimeMillis();
                        handler.postAtTime(new Runnable() {
                            @Override
                            public void run() {
                                txtdisplay.setText(val++ +"");
                            }
                        }, curtime + 30000);
                        Thread.sleep(1000);
                    }catch(Exception e){}
                }
            }
        };
        th.start();
    }
}

 

 

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

 

package com.example.android0721;

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.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

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

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

        txtdisplay = (TextView)findViewById(R.id.txtdisplay);
        btnstart = (Button)findViewById(R.id.btnstart);

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

            @Override
            public void onClick(View view) {
                value=0;
                //대화상자 만들기 - 진행률이 보여진다.
                progressDialog = new ProgressDialog(HandlerActivity.this);
                progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                progressDialog.show();
                isQuit = false;
                progressHandler.sendEmptyMessage(0);
            }
        });

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

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

                        //다른 작업이 없을 때 처리하도록 전송
                        long curtime = System.currentTimeMillis();
                        handler.postAtTime(new Runnable() {
                            @Override
                            public void run() {
                                txtdisplay.setText(val++ +"");
                            }
                        }, curtime + 30000);
                        Thread.sleep(1000);
                    }catch(Exception e){}
                }
            }
        };
        th.start();
    }
}

 

 

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

 

	Handler progressHandler = new Handler(){
        @Override
        public void handleMessage( Message msg) {
            value++;
            txtdisplay.setText(value +"");
            try{
                Thread.sleep(100);
            }catch(Exception e){}
            if(value <100 && isQuit == false){
                progressDialog.setProgress(value);
                progressHandler.sendEmptyMessage(0);
            }else{
                progressDialog.dismiss();
            }
        }
    };

 

package com.example.android0721;

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.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity {
    private TextView txtdisplay;
    private Button btnstart;

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

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

    Handler progressHandler = new Handler(){
        @Override
        public void handleMessage( Message msg) {
            value++;
            txtdisplay.setText(value +"");
            try{
                Thread.sleep(100);
            }catch(Exception e){}
            if(value <100 && isQuit == false){
                progressDialog.setProgress(value);
                progressHandler.sendEmptyMessage(0);
            }else{
                progressDialog.dismiss();
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        txtdisplay = (TextView)findViewById(R.id.txtdisplay);
        btnstart = (Button)findViewById(R.id.btnstart);

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

            @Override
            public void onClick(View view) {
                value=0;
                //대화상자 만들기 - 진행률이 보여진다.
                progressDialog = new ProgressDialog(HandlerActivity.this);
                progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                progressDialog.show();
                isQuit = false;
                progressHandler.sendEmptyMessage(0);
            }
        });

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

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

                        //다른 작업이 없을 때 처리하도록 전송
                        long curtime = System.currentTimeMillis();
                        handler.postAtTime(new Runnable() {
                            @Override
                            public void run() {
                                txtdisplay.setText(val++ +"");
                            }
                        }, curtime + 30000);
                        Thread.sleep(1000);
                    }catch(Exception e){}
                }
            }
        };
        th.start();
    }
}

 

 

 

 

** 스레드와 핸들러 사용

Handler 핸들러 = new Handler(Looper.getMainLooper(){

    @Override

    public void handleMaesage(Message msg){

        //핸들러가 수행할 내용

        //msg를 이용해서 전달된 데이터를 저장

 

        //데이터를 출력

    }

};

 

Thread 스레드 = new Thread(){

    @Override

    public void run(){

    //스레드가 수행할 내용

 

    //데이터 가져오기

 

    //데이터 파싱 - 핸들러에서 해도 됨

 

    //핸들러 호출

    Message 메시지 = new Message();

    메시지.? = 데이터;

    핸들러.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"
    tools:context=".DownloadActivity"
    android:orientation="vertical">

    <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 파일에 뷰를 위한 인스턴스 변수를 생성

 

package com.example.android0721;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download);
        
        naverdownload = (Button)findViewById(R.id.naverdownload);
        naverhtml = (TextView)findViewById(R.id.naverhtml);
        
    }
}

 

 

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

 

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);
        }
    };

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

 

 

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

 

package com.example.android0721;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

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

    //스레드
    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);
        }
    };

    //핸들러
    Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message obj){

        }
    };

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

        naverdownload = (Button)findViewById(R.id.naverdownload);
        naverhtml = (TextView)findViewById(R.id.naverhtml);

        naverdownload.setOnClickListener(
                new Button.OnClickListener(){
                    public void onClick(View view){
                        th.start();
                    }
                });
    }
}

 

 

 

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

<uses-permission android:name="android.permission.INTERNET"/>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android0721">
<uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".DownloadActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".HandlerActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

 

** AsyncTask

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

=> 이 클래스를 이용한 작업은 짧은 시간이 걸리는 작업에만 추천하고 긴 시간이 걸리는 작업에는 Thread(Runnnable or Callable) + Handler 조합으로 하는 것을 추천

 

1. 클래스 생성

class 클래스 extends 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개, ProgressBar 1개, Button 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=".AsyncActivity"
    android:orientation="vertical">

    <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="wrap_content"
        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클래스 생성하고 인스턴스 변수 선언

 

package com.example.android0721;

import androidx.appcompat.app.AppCompatActivity;

import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class AsyncActivity extends AppCompatActivity {
    private TextView state;
    private ProgressBar progress;
    private Button threadstart, 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에 대입
        public Integer doInBackground(Integer ... values){
            //아직 종료되지 않았다면
            while(isCancelled() == false){
                //value의 값을 1씩 증가 - 100이 되면 중지
                //100이 안되면 publishProgress를
                //호출해서 UI를 갱신합니다.
                value = value + 1;
                if(value >= 100){
                    break;
                }else{
                    //이 메소드를 호출하면
                    //onProgressUpdate가 호출됩니다.
                    publishProgress(value);
                }
                try{
                    Thread.sleep(1000);
                }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;

    @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);
            }
        });
    }
}

 

 

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

 

 		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(){
            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

=> Application 내의 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) 레이아웃을 작성

ListView를 세로로 2개 배치 - 50%의 영역을 차지

 

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

    <ListView
        android:layout_width="match_parent"
        android:layout_height="odp"
        android:layout_weight="1"
        android:id="@+id/list_odd"/>
    <ListView
        android:layout_width="match_parent"
        android:layout_height="odp"
        android:layout_weight="1"
        android:id="@+id/list_even"/>

</LinearLayout>

 

 

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

 

package com.example.android0721;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.Random;

public class LooperActivity extends AppCompatActivity {
    //뷰 객체
    private ListView list_odd, list_even;
    //ListView는 MVC 패턴을 구현하기 위해서
    //List 와 ListAdapter를 이용해서 출력

    //ListView에 출력할 데이터 변수
    ArrayList<String> oddDatas, evenDatas;
    //List 와 ListView를 연결해 줄 컨트롤러 변수
    ArrayAdapter<String> oddAdapter, evenAdapter;

    //핸들러 변수
    Handler handler;

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

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

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

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

                //UI갱신을 위해서 핸들러에게 메시지를 전성
                Message message = new Message();
                if(data % 2 == 0){
                    //what은 구분하기 위해서 주로 사용
                    message.what = 0;
                }else{
                    message.what = 1;
                }
                //arg는 주로 데이터를 전달할 목적으로 사용
                message.arg1 = data;
                message.arg2 = i;
                //핸들러에게 메시지를 전송
                oneThread.oneHandler.sendMessage(message);
            }
        }
    }

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

        //뷰를 찾아오기
        list_odd = (ListView)findViewById(R.id.list_odd);
        list_even = (ListView)findViewById(R.id.list_even);

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

        //뷰와 모델 연결
        oddAdapter = new ArrayAdapter<>(
                this,
                android.R.layout.simple_list_item_1,
                oddDatas);
        evenAdapter = new ArrayAdapter<>(
                this,
                android.R.layout.simple_list_item_1,
                evenDatas);
        list_odd.setAdapter(oddAdapter);
        list_even.setAdapter(evenAdapter);

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

 

 

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

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

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

3. Looper (스레드를 만들고 핸들러를 만들어서 그 안에 별도의 메시지 큐를 만들어서 사용) 이용 : 아주 많은 스레드가 별도로 동작해야 하는 경우 사용 - 게임