본문 바로가기

아옳옳의 코딩공부/아옳옳의 안드로이드스튜디오

2021-05-6안드로이드 스튜디오(Thread 2, AsyncTask )

반응형

이제 까지는 다른 스레드에서 메인스레드로 데이터를 보내서 작업을 처리하는 단방향만 했었다 

오늘 배운 내용들은 메인에서 다른 쓰레드로 데이터를 보내고 그 쓰레드에서 다시 메인으로 보내 작업 하는 방법을 배울것이다. 

 

이때 중요한것이 저번 시간에 간단하게 설명한 루퍼가 나온다 . 메세지큐에 데이터가 들어오면 루퍼에서 그것을 감지하고 핸들러에 알려준다고 했었는데 루퍼는 메인에만 있는 것이라 다른 쓰레드핸들러에 따로 만들어 줘야한다 .

 

그럼 간단한 코드로 살펴 보도록 하자 

public class MainActivity extends AppCompatActivity {


    EditText editText1, editText2;
    MainHandler mainHandler = new MainHandler();
    NewThread newthread = new NewThread();


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

        editText1 = findViewById(R.id.editText);
        editText2 = findViewById(R.id.editText2);

        Button button = findViewById(R.id.button);
        
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String str = editText1.getText().toString();
                Message message = Message.obtain();
                message.obj = str;
                //1. 에디트 택스트에서 받은 택스트를 다른 스레드에 넘겨 준다 
                newthread.newHandler.sendMessage(message);
            }
        });
        //1 -2 스레드 시작 함과 동시에 run 메소드 시작 
        newthread.start();
    }

    class NewThread extends Thread {
        //2. 스레드를 만들면서 동시에 핸들러를 만들기 때문에 
        //메인에서 이 스레드속 핸들러에게 보내줄수 있다. 
        NewHandler newHandler = new NewHandler();

        public void run() {
            //2-2 루퍼를 장착해주어서 여기서도 핸들러에 뭔가 들어오면 받아서 작업가능
            Looper.prepare();
            Looper.loop();
        }
    }
    //스레드의 핸들러
    class NewHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            //다시 메인에 보내주어야하기 때문에 메세지 생성하고 
            Message message = Message.obtain();//빈 메세지 생성후 
            message.obj = (String) msg.obj + "Android~!"; // 받은데이터 + 새로운데이터 담아주기 
            // 메인 핸들러로 보내서 작업시작 
            mainHandler.sendMessage(message);

        }
    }
    //메인의 핸들러 
    class MainHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            //메인 핸들러이기때문에 직접 에디트 텍스트 작업 가능 
            editText2.setText((String)msg.obj);

        }

    }
}

일단 스레드전용 핸들러도 필요하다 그리고 스레드에도   Looper.prepare();  Looper.loop();이렇게 루퍼를 달아준다 . 

루퍼가 있어야 메세지큐에 들어오는 정보들을 루퍼가 핸들러에 알려준다 . 주석으로 순서가 있으니 나중에라도 순서따라서 보면 될거 같다 . 

 

메소드들

이러한 함수들이 있으니 입맛에 맞게 골라서 사용하면 될거 같다 . 


AsyncTask

앱은 메인스레드와 작업 스레드의 협력으로 원활이 돌아가는데 이렇게 스레드마다 처리해야하는 일이 엄격히 구분되는 것은 개발에 있어 부담감이 크다. 그래서 이러한 복잡성을 줄이기위한 도우미클래스가 AsyncTask 이다. 

 

실행의 흐름을 보자 

4가지 메소드에 대해서 알아보자 doInBackground , onPreExecute , onProgressUpdate ,onPostExecute

doInBackground
작업스레드영역
-새로 만든스레드에서 백그라운드 작업 수행 
-excute() 메소드호출할 때 사용된 파라미터를 배열로 전달받음 
onPreExecute
메인스레드영역
- 백그라운드 작업 수행 전 호출
- 메인 스레드에서 실행되며 초기화 작업에 사용
onProgressUpdate 
메인스레드영역
- 백그라운드 작업 진행 상태를 표시하기 위해 호출
- 작업 수행 중간 중간에 UI 객체에 접근하는 경우 사용
- 이 메소드가 호출되도록 하려면 백그라운드 작업 중간에 publishProgress() 메소드를 호출
onPostExecute
메인스레드영역
- 백그라운드 작업이 끝난 후 호출
- 메인 스레드에서 실행되며 메모리 리소스를 해제하는 등의 작업에 사용
- 백그라운드 작업의 결과는 Result 타입의 파라미터로 전달

다음 예제를 보자

 제일 중요한것은 1 2 3 번으로 시작 중간 끝 이있는데 제네릭으로 내가 사용할 자료형을 내가 정하는 것이다. 

public class MainActivity extends AppCompatActivity {

    TextView textView;
    ProgressBar progressBar;
    int value;
    BackgroundTask task;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        textView= findViewById(R.id.textView);
        progressBar=findViewById(R.id.progressBar);
        Button btn_Start = findViewById(R.id.button3);
        Button btn_Cancel = findViewById(R.id.button4);
        
        btn_Start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //AsyncTask를 인스턴스화
                task = new BackgroundTask();
                //AsyncTask시작
                task.execute();
            }
        });
        btn_Cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                value = 0;
                task.cancel(true);
            }
        });
    }
    //AsyncTask 클래스 상속 받아주고
    class BackgroundTask extends AsyncTask<Void, Integer, Integer>{
        @Override //2. 여기서 작업 한다.
        protected Integer doInBackground(Void... voids) {
            while (!isCancelled()){
                value++;
                if(value>=100){
                    break;
                }else {
                    publishProgress(value);
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return value;
        }

        @Override //1.  첫 시작에 여기 들려서 초기화 시키고
        protected void onPreExecute() {
            value=0;
            progressBar.setProgress(0);
        }
        @Override //4. 마지막 값 찍는곳
        protected void onPostExecute(Integer integer) {
          progressBar.setProgress(0);
          textView.setText("finished : " + integer+"%");
        }
        @Override   //3.중간 
        protected void onProgressUpdate(Integer... values) {
            progressBar.setProgress(values[0]);
            textView.setText("current Value : "+values[0]+"%");
        }
        @Override // 취소가 되었을때
        protected void onCancelled(Integer integer) {
            progressBar.setProgress(0);
            textView.setText("Cancelled");
        }
    }

}

 

기본적은 설명은 이정도이고 약간 응용하여 전에 실습한 내용을  AsyncTask로 변경해보자

public class MainActivity extends AppCompatActivity {


    TextView textView;
    int value = 10;
    BackgroundTask task;
    Boolean run = false;
    Boolean first = true;
    Boolean loop = true;

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

        textView=findViewById(R.id.main_textView);
        textView.setText(String.valueOf(value));
        //AsyncTask를 인스턴스화
        task = new BackgroundTask();
    }

    public void startView(View view) {
        if(first) {
            run =true;
            first = false;
            //AsyncTask시작
            task.execute();
        }else {
           loop = true;

        }
    }

    public void pauseView(View view) {
        loop = false;
    }

    class BackgroundTask extends AsyncTask<Void, Integer, Integer>{

        @Override //2. 직접 작업영역
        protected Integer doInBackground(Void... voids) {

                while (run) {
                    if (loop) {
                        if (value > 0) {
                            value--;
                            //3으로 보내줌
                            publishProgress(value);
                        } else if (value == 0) {
                            run = false;
                            break;
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
           return value;
        }

        @Override //1. 초기화 영역 여기서는 밖에서 초기화했음 없어도 무관
        protected void onPreExecute() {
        }
        @Override  //3. 2작업영역에서 작업하는거 거치는 영역
        protected void onPostExecute(Integer integer) {
           textView.setText(value>0?value+"":"finish~~!!");
        }

        @Override//4. 완료했을때의 영역
        protected void onProgressUpdate(Integer... values) {
            textView.setText(String.valueOf(values[0]));
        }

        @Override
        protected void onCancelled(Integer integer) {
            super.onCancelled(integer);
        }

    }

}

저번에 실습한것을 AsyncTask로 변경해서 작동해도 잘 작동 되는것을 볼수 있다 . 

 

이렇게 쓰레드를 마치기전 마지막으로 실습하나를 더 해볼것이다.

 

짝수와 홀수 각각을 출력해줄 리스트뷰 2개가 있으며 랜덤으로 나오는 숫자를 짝수와 홀수 별로 나눠 리스트뷰에 찍어 줄것이다. 그냥 하면 의미가 없기에 따로 쓰레드 2개를 더 만들어서 1번 쓰레드에서 랜덤한 숫자를 생성하여 2번쓰레드로 보내줄것이고 보내준 것을 메인으로 메인에서 분류 하여 리스트뷰에 작성해주는 프로그램을 짜보도록 하겠다. 

 

public class MainActivity extends AppCompatActivity {

    ListView listView1_even, listView2_odd;
    MainHandler mainHandler = new MainHandler(); // 메인 핸들러
    
    Thread_one thread_one = new Thread_one();
    Thread_Two thread_two = new Thread_Two();
    
    ArrayAdapter arrayAdapter_even, arrayAdapter_odd;
    ArrayList<String> list_even, list_odd;

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

        listView1_even = findViewById(R.id.list1);
        listView2_odd = findViewById(R.id.list2);
        //어레이리스트 생성 
        list_even = new ArrayList<>();
        list_odd = new ArrayList<>();
        //어레이리스트 어댑터 설정 
        arrayAdapter_even = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list_even);
        arrayAdapter_odd = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list_odd);

        listView1_even.setAdapter(arrayAdapter_even);
        listView2_odd.setAdapter(arrayAdapter_odd);
        
        //스레드 시작 
        thread_one.start();
        thread_two.start();
    }
    //랜덤쓰레드
    class Thread_one extends Thread {
        Random random = new Random();

        public void run() {
            //10번을 돌면서 2번스레드로 던저준다
            for (int i = 0; i < 10; i++) {
                //빈 메세지함 생성
                Message message = Message.obtain();
                //0~10 사이의 난수를 받아서 메세지에 넣어줌
                message.obj = random.nextInt(10);
                //2번 쓰레드로 넘겨줌
                thread_two.twoHandler.sendMessage(message);
                try {
                    //0.5초 간격으로 실행
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //받아서 메인으로 1
    class Thread_Two extends Thread {
        TwoHandler twoHandler = new TwoHandler();
        public void run() {
            //루퍼를 통해 메세지가 오면 핸들러로 받아줌
            Looper.prepare();
            Looper.loop();
        }
    }
    //작업쓰레드 핸들러 1
    class TwoHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            //쓰레드1에서 보낸 메시지를 루퍼를 통해 인지하고
            //받아 데이터를 다시 메인으로 보내준다 
            Message message = Message.obtain();
            Integer b = (Integer) msg.obj;
            message.obj = b;
            mainHandler.sendMessage(message);
        }
    }
    
    //메인 핸들러 2
    class MainHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            //받아온 데이터를 숫자로 만들어서 
            Integer a = (Integer) msg.obj;
            //2로 나눈 나머지가 0 이면 짝수 1이면 홀수 
            if (a % 2 == 0) {
                //어레이리스트에 담아주고 어댑터에 알려주기 
                list_even.add("짝수 : " + a);
                arrayAdapter_even.notifyDataSetChanged();
            } else if (a % 2 == 1) {
                list_odd.add("홀수 : " + a);
                arrayAdapter_odd.notifyDataSetChanged();
            }
        }
    }


}

 이렇게까지 할 필요는 쓰레드를 많이 쓸 필요는 없지만 배운 내용들을 한번씩 더 사용해보면서 다시한번 이해하는 시간이였다 . 

잘 나눠지고 출력되는것을 볼수 있다. 

반응형