AsyncTask란 무엇인가?
예제를 시작하기 전에 AsyncTask의 개념을 먼저 이해할 필요가 있습니다. AsyncTask는 백그라운드 스레드에서 작업을 수행한 뒤, 그 결과를 메인(UI) 스레드에 업데이트할 수 있도록 도와주는 안드로이드 클래스입니다. 네트워크 통신처럼 시간이 오래 걸리는 작업을 처리하면서도 UI 멈춤 현상을 방지할 수 있죠.
참고로 AsyncTask는 API 30부터 공식적으로 지원 중단(deprecated)되었으며, 최신 프로젝트에서는 코루틴(Coroutines)이나 WorkManager 사용이 권장됩니다. 하지만 레거시 코드 유지보수나 개념 학습을 위해 여전히 많이 활용되고 있습니다.
이 글에서는 여러 개의 AsyncTask를 병렬로 동시에 실행하는 방법을 알아보겠습니다.
Step 1 — 새 프로젝트 만들기
Android Studio에서 File ⇒ New Project를 선택하여 새 프로젝트를 생성하고, 필요한 모든 정보를 입력합니다.
Step 2 — 레이아웃 파일 작성
res/layout/activity_main.xml 파일에 아래 코드를 추가합니다.
<?xml version = "1.0" encoding = "utf-8"?>
<LinearLayout xmlns:android = "https://schemas.android.com/apk/res/android"
xmlns:tools = "https://schemas.android.com/tools"
android:id = "@+id/rootview"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:orientation = "vertical"
android:background = "#c1c1c1"
android:gravity = "center_horizontal"
tools:context = ".MainActivity">
<Button
android:id = "@+id/asyncTask"
android:text = "Download"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content" />
<ImageView
android:id = "@+id/image"
android:layout_width = "300dp"
android:layout_height = "300dp" />
<ImageView
android:id = "@+id/image2"
android:layout_width = "300dp"
android:layout_height = "300dp" />
</LinearLayout>
위 코드에는 두 개의 ImageView와 하나의 Button이 선언되어 있습니다. 사용자가 버튼을 클릭하면 서로 다른 인터넷 경로에서 두 개의 이미지를 다운로드하여 각각의 ImageView에 표시하게 됩니다.
Step 3 — MainActivity.java 작성
src/MainActivity.java 파일에 아래 코드를 추가합니다.
package com.example.andy.myapplication;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
URL ImageUrl = null;
InputStream is = null;
Bitmap bmImg = null;
ImageView imageView = null;
ImageView imageView2 = null;
AsyncTaskExample asyncTask = null;
AsyncTaskExample2 asyncTask2 = null;
ProgressDialog p;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.asyncTask);
imageView = findViewById(R.id.image);
imageView2 = findViewById(R.id.image2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
asyncTask2 = new AsyncTaskExample2();
asyncTask2.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "https://www.tutorialspoint.com/cprogramming/images/logo.png");
asyncTask = new AsyncTaskExample();
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "https://www.tutorialspoint.com/images/tp-logo-diamond.png");
}
});
}
private class AsyncTaskExample extends AsyncTask<String, String, Bitmap> {
@Override
protected void onPreExecute() {
super.onPreExecute();
p = new ProgressDialog(MainActivity.this);
p.setMessage("Please wait...It is downloading");
p.setIndeterminate(true);
p.setCancelable(false);
p.show();
}
@Override
protected Bitmap doInBackground(String... strings) {
try {
ImageUrl = new URL(strings[0]);
HttpURLConnection conn = (HttpURLConnection) ImageUrl
.openConnection();
conn.setDoInput(true);
conn.connect();
is = conn.getInputStream();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
bmImg = BitmapFactory.decodeStream(is, null, options);
} catch (IOException e) {
e.printStackTrace();
}
return bmImg;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (imageView ! = null) {
p.hide();
imageView.setImageBitmap(bitmap);
} else {
p.show();
}
}
}
private class AsyncTaskExample2 extends AsyncTask<String, String, Bitmap> {
@Override
protected Bitmap doInBackground(String... strings) {
try {
ImageUrl = new URL(strings[0]);
HttpURLConnection conn = (HttpURLConnection) ImageUrl
.openConnection();
conn.setDoInput(true);
conn.connect();
is = conn.getInputStream();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
bmImg = BitmapFactory.decodeStream(is, null, options);
} catch (IOException e) {
e.printStackTrace();
}
return bmImg;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (imageView2 ! = null) {
imageView2.setImageBitmap(bitmap);
} else {
}
}
}
}
핵심 포인트는 executeOnExecutor() 메서드입니다. 일반적인 execute() 호출은 기본적으로 직렬(순차) 실행되어 여러 AsyncTask가 순서대로 처리되지만, executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ...)를 사용하면 두 개 이상의 AsyncTask가 동시에 병렬 실행됩니다. 참고로 안드로이드는 최대 5개의 AsyncTask까지 병렬 실행을 지원합니다.
Step 4 — 매니페스트 설정
manifest.xml 파일에 아래 코드를 추가합니다.
<?xml version = "1.0" encoding = "utf-8"?>
<manifest xmlns:android = "https://schemas.android.com/apk/res/android" package = "com.example.andy.myapplication">
<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 = ".MainActivity">
<intent-filter>
<action android:name = "android.intent.action.MAIN" />
<category android:name = "android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
위 코드에서는 인터넷에서 이미지를 다운로드할 수 있도록 INTERNET 권한을 선언했습니다. 이 권한이 없으면 네트워크 작업 시 보안 예외(SecurityException)가 발생하므로 반드시 추가해야 합니다.
앱 실행 및 결과 확인
이제 애플리케이션을 실행해 보겠습니다. 실제 안드로이드 기기를 컴퓨터에 연결했다고 가정합니다. Android Studio에서 프로젝트의 액티비티 파일 중 하나를 연 뒤, 툴바에서 Run 아이콘을 클릭하세요. 실행 옵션으로 자신의 모바일 기기를 선택하면, 기본 화면이 표시됩니다.

버튼을 클릭하면 아래와 같이 프로그레스 바가 표시되면서 인터넷에서 이미지를 다운로드합니다.

다운로드가 완료되면 두 개의 이미지가 병렬로 동시에 내려받아져 화면에 표시됩니다.

마무리
이렇게 executeOnExecutor()와 THREAD_POOL_EXECUTOR를 활용하면 여러 AsyncTask를 병렬로 처리할 수 있습니다. 다만 신규 프로젝트라면 deprecated된 AsyncTask 대신 코루틴(Kotlin Coroutines)이나 RxJava 같은 현대적인 비동기 처리 방식을 채택하는 것을 추천합니다.