관리 메뉴

막내의 막무가내 프로그래밍 & 일상

[안드로이드] asynctask 사용법 예제(이해하기쉽게 정리) + 병렬 실행 본문

안드로이드/자바 & Previous

[안드로이드] asynctask 사용법 예제(이해하기쉽게 정리) + 병렬 실행

막무가내막내 2019. 2. 21. 23:13
728x90

[2021-04-17 업데이트]

 

안드로이드에서는 기본 메인(UI) 스레드가 아닌 다른 스레드(Woker Thread, 작업스레드)에서 UI객체에 직접 접근하는것은 불가합니다. 또한 안드로이드에서는 서버 혹은 데이터베이스와의 비동기 통신은 백그라운드 스레드(Woker Thread, 작업스레드)를 사용해야한다는 특징이 있습니다.

 

그럼 비동기로 DB에서 값을 가져온 후 UI업데이트를 하려면 어떻게 해야할까요? 서버와의 통신에 백그라운드 스레드를 사용하고 비동기적인 결과를 메인스레드를 사용해 UI 업데이트하고 아주 복잡하겠죠...? 백그라운드 작업결과와 관련된 UI업데이트 작업이 여러개면 더더욱 복잡해지고요.. ㅠ

 

이를 위해 핸들러를 사용하기도 하지만 핸드러를 사용하면 코드가 복잡해지고 초보자가 사용하기 어렵다는 단점이 있습니다.

 

 

이러한 Background 작업과 UI 변경 작업 양쪽을 심플하게 관리하고 구현하기 위해 AsyncTask 가 등장했습니다.

[참고로 안드로이드에서 별도의 스레드를 생성하지 않고 기본으로 동작하는 스레드가 메인 스레드고 메인스레드만이 안드로이드 UI를 조작할 수 있습니다. 그리고 현재 AsyncTask 는 Deprecated 된 상태라 코루틴이나 RxJava를 공부해보시는 것도 추천드립니다 ! ]

 

AsynTask란 추상클래스를 상속하여 클래스를 만들면 해당 클래스안에 스레드를 위한 동작코드와 UI 접근 코드 구현 관리와 실행을 한번에 해결할 수 있습니다. 만약 AsyncTask를 안쓰면 서버 혹은 데이터베이스와의 비동기 통신은 백그라운드 스레드를 사용해야하는데 

 

 

예를들면 웹서버에서 고객 아이디를 가져오는 통신하는 작업, 즉 백그라운드에서 해야하는 작업과 가져온 정보를 UI에 업데이트 하고 싶다면 이에 대한  AsyncTask 상속 클래스를 만든 후 해당 로직을 구현 해주면 됩니다.


 

[AsyncTask 메소드]

 

이제 AsyncTask관련 메소드들을 간단하게 살펴보겠습니다.

 

1. excute() 메소드는 생성한 Asynctask 상속한 객체를 실행시켜서 이 객체는 이때부터 백그라운드 작업을 수행하기 시작하고 필요한 경우에는 그 결과를 메인 스레드에서 실행하므로 UI객체에 접근 할 수 있게됩니다. 

추가로 excute()와 반대로 cancel() 메소드를 호출하면 작업을 취소할 수 있습니다.

 

2. doInBackground()는 새로 만들어진 스레드, 즉 백그라운드 작업을 할 수 있습니다. 그리고 excute() 메소드를 호출할 때 사용도니 파라미터를 배열로 전달받을 수 있습니다. (밑 코드 참고바랍니다. 여기서는 사용하지는 않았습니다만..) 중간중간마다 UI객체에 접근할려면 메인스레드에서 해야하므로 publishProgress() 를 호출해 nProgressUpdate()를 불러와서 메인스레드에서 UI작업이 가능해집니다.

 

3. onPreExcute(), onProgressUpdate(), onPostExcute()는 메인 스레드에서 실행되므로 UI 객체에 자유롭게 접근이 가능합니다. (셋 모두)

조금더 설명을 덧붙이자면 onPreExcute()는 백그라운드 작업을 수행하기 전에 호출되며 메인 스레드에서 실행되고 초기화작업에 사용합니다.

 

onProgressUpdate()는 백그라운드 작업의 진행하면서 중간 중간에 UI객체에 접근하는 경우 사용됩니다. 즉 doInBackground()가 진행중에 사용된다고 보면 됩니다. 이 메소드가 호출될려면 doInBackground에서 publishProgress() 메소드를 호출해야합니다..!

 

마지막으로 onPostExcute()는 백그라운드 작업이 끝나면 호출이 되고 메모리 리소를 해체하는 작업을 주로합니다. 백그라운드 작업의 결과를 매개변수로 전달받을 수도 있습니다.

 

4. onCancelled()이라는 메소드도 있는데 이 메소드는 AsyncTask객체를 cancel()로 종료시키면 호출되는 메소드입니다.

 

 

 

----------------------

즉, doInBackground()에서 백그라운드 작업을 하면되고 이 메소드빼고 다 메인스레드작업이고 UI접근이 가능하다고 보면 되겠습니다.

 

 

 

<예제에 주석을 달아놔서 이해하기 쉽게 정리하였습니다.>

 

 

먼저 xml 레이아웃입니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="AsyncTask를 이용한 백그라운드 작업"
        android:textSize="20dp"
        />
    <ProgressBar
        android:id="@+id/progress"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:max="100"
        />
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >

        <Button
            android:id="@+id/executeButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:layout_marginRight="20dp"
            android:layout_weight="1"
            android:text="실행"
            android:textSize="16dp" />
        <Button
            android:id="@+id/cancelButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginTop="20dp"
            android:text="취소"
            android:textSize="16dp"
            />
    </LinearLayout>

</LinearLayout>

 

 

 

 

 

자바소스파일 (메인액티비티, 주석참고바랍니다.)

=> 주석에도 설명되어있지만 Asynctask의 제네릭타입 <>으로 매개변수를 설정할 수 있습니다. 만약 주고받을게 필요없다면 void로 해주면 됩니다.

public class MainActivity extends AppCompatActivity {

    TextView textView;
    ProgressBar progress;
    //백그라운드Task
    BackgroundTask task;
    int value;

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

        textView = (TextView) findViewById(R.id.textView);
        progress = (ProgressBar) findViewById(R.id.progress);

// 실행 버튼 이벤트
        Button executeButton = (Button) findViewById(R.id.executeButton);
        executeButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
// 백그라운드 task 생성
                task = new BackgroundTask();
//excute를 통해 백그라운드 task를 실행시킨다
//여기선 100을 매개변수로 보내는데 여기 예제에서는 이 매개변수를 doInBackGround에서 사용을 안했다.
                task.execute(100);
            }
        });

// 취소 버튼 이벤트
        Button cancelButton = (Button) findViewById(R.id.cancelButton);
        cancelButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
//task종료한다. BackgrounTask의 onCancled()호출될것이다.
//이미 스레드가 종료된 뒤에 cancel하면 아무일도 안일어난다. 이미 종료되었기 때문에
                task.cancel(true);
            }
        });
    }

    //새로운 TASK정의 (AsyncTask)
// < >안에 들은 자료형은 순서대로 doInBackground, onProgressUpdate, onPostExecute의 매개변수 자료형을 뜻한다.(내가 사용할 매개변수타입을 설정하면된다)
    class BackgroundTask extends AsyncTask<Integer , Integer , Integer> {
        //초기화 단계에서 사용한다. 초기화관련 코드를 작성했다.
        protected void onPreExecute() {
            value = 0;
            progress.setProgress(value);
        }

        //스레드의 백그라운드 작업 구현
//여기서 매개변수 Intger ... values란 values란 이름의 Integer배열이라 생각하면된다.
//배열이라 여러개를 받을 수 도 있다. ex) excute(100, 10, 20, 30); 이런식으로 전달 받으면 된다.
        protected Integer doInBackground(Integer ... values) {
//isCancelled()=> Task가 취소되었을때 즉 cancel당할때까지 반복
            while (isCancelled() == false) {
                value++;
//위에 onCreate()에서 호출한 excute(100)의 100을 사용할려면 이런식으로 해줘도 같은 결과가 나온다.
//밑 대신 이렇게해도됨 if (value >= values[0].intValue())
                if (value >= 100) {
                    break;
                } else {
//publishProgress()는 onProgressUpdate()를 호출하는 메소드(그래서 onProgressUpdate의 매개변수인 int즉 Integer값을 보냄)
//즉, 이 메소드를 통해 백그라운드 스레드작업을 실행하면서 중간중간 UI에 업데이트를 할 수 있다.

//백그라운드에서는 UI작업을 할 수 없기 때문에 사용
                    publishProgress(value);
                }

                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {}
            }

            return value;
        }

        //UI작업 관련 작업 (백그라운드 실행중 이 메소드를 통해 UI작업을 할 수 있다)
//publishProgress(value)의 value를 값으로 받는다.values는 배열이라 여러개 받기가능
        protected void onProgressUpdate(Integer ... values) {
            progress.setProgress(values[0].intValue());
            textView.setText("현재 진행 값 : " + values[0].toString());
        }


        //이 Task에서(즉 이 스레드에서) 수행되던 작업이 종료되었을 때 호출됨
        protected void onPostExecute(Integer result) {
            progress.setProgress(0);
            textView.setText("완료되었습니다");
        }

        //Task가 취소되었을때 호출
        protected void onCancelled() {
            progress.setProgress(0);
            textView.setText("취소되었습니다");
        }
    }
}

 

결과 에뮬레이터(초기화면은 처음에 올려놨으므로 생략)

(실행버튼눌렀을 때 -> 실행이완료되었을 때 -> 취소버튼 눌렀을 때)

 

 

 

 

 

+) 참고로 한번 종료된 AsyncTask는 재사용이 불가능합니다. 다시 사용하고싶으면 해당 AsyncTask를 재생성 해서 사용하시면 됩니다.

 

그리고 AsyncTask는 현재기준 병렬처리가 기본값이 아니어서 여러개의 AsyncTask를 동시에 실행할 수 가 없습니다. (동시에 실행한 경우 먼저 실행한걸 끝내고 다음께 실행됨) 

간단히 설명하자면 병렬실행을 위해서는 

spell12AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"100");

 이런식으로 excute대신 사용해주면 된다. 두번째 매개변수는 넘기는 값으로 excute 괄호안의 역활과 똑같다.

 

그러나 이것도 최대 3~4개 정도밖에 병렬실행이 안된다.

만약 더 많은 스레드를 사용하고싶다면,  (10개인 경우)

ExecutorService threadPool = Executors.newFixedThreadPool(10);

spell11AsyncTask.executeOnExecutor(threadPool, "100");

이런식으로 사용하면 된다. 다른 프로젝트에서 퍼온 소스이다.

 

 

참고 : Do it 안드로이드

728x90
Comments