ちょっと話題の記事

AsyncTaskを使った非同期処理のきほん

2012.03.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

非同期処理を行う方法にAsyncTaskを使う方法があります。
AsyncTaskを使うとThreadやRunnableを意識することなく、 メインスレッドとは別のスレッドで処理を行うことができます。
非同期処理中であってもメインスレッドでの処理が可能です。

非同期処理を行う際、Handlerクラスを使って画面更新処理を行うと 少しコードが煩雑になってしまいますが、
AsyncTaskクラスを使うとそのような事がなく、 非同期処理と画面更新処理を行うことができます。

■AsyncTaskに用意されている主なメソッド

onPreExecute()

doInBackgroundメソッドの実行前にメインスレッドで実行されます。
非同期処理前に何か処理を行いたい時などに使うことができます。

doInBackground()

メインスレッドとは別のスレッドで実行されます。
非同期で処理したい内容を記述します。 このメソッドだけは必ず実装する必要があります。

onProgressUpdate()

メインスレッドで実行されます。
非同期処理の進行状況をプログレスバーで 表示したい時などに使うことができます。

onPostExecute()

doInBackgroundメソッドの実行後にメインスレッドで実行されます。
doInBackgroundメソッドの戻り値をこのメソッドの引数として受け取り、
その結果を画面に反映させることができます。

実行順は以下になります。

  1. onPreExecute()
  2. doInBackground()
  3. onProgressUpdate() ※doInBackground()で、publishProgress()が呼ばれた場合に処理されます。
  4. onPostExecute()

■AsyncTaskの使い方

  1. AsyncTaskを継承するクラスを作成する。
  2. doInBackgroundメソッドを実装する。他にもメソッドは用意されていますが、必ず実装しなければならないメソッドはdoInBackgroundメソッドです。
    このメソッド中で非同期で行いたい処理を記述します。注意点として、doInBackgroundメソッドから画面を操作しようとすると例外が発生します。
  3. メインスレッドで、①のクラスをインスタンス化し、executeメソッドを呼びます。

■AsyncTaskを使った例その1

※この例では、事前準備と進捗の表示は省いています。

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:text="非同期処理開始"
    	android:id="@+id/button"
    />

    <Button
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:text="確認用"
    	android:id="@+id/countButton"
    />

	<TextView
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:id="@+id/textView"
	/>
</LinearLayout>

AsyncTaskTestActivity.java

package com.androidtest.sample;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class AsyncTaskTestActivity extends Activity {

	private TextView textView;
	private Button button;
	private Button countButton;
	private MyTask task;
	private Integer count = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //main.xmlに設定したコンポーネントをid指定で取得します。
        textView    = (TextView)findViewById(R.id.textView);
        button      = (Button)findViewById(R.id.button);
        countButton = (Button)findViewById(R.id.countButton);

        // タスクの生成
        task = new MyTask(textView);

        //buttonがクリックされた時の処理を登録します。
        button.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
                ((Button)v).setEnabled(false);
                // 非同期処理を開始する
                task.execute(1);
			}
		});

        //countButtonがクリックされた時の処理を登録します。
        countButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				count++;
				((Button)v).setText("確認用: " + count.toString());
			}
		});
    }
}

MyTask.java

package com.androidtest.sample;

import android.os.AsyncTask;
import android.widget.TextView;

public class MyTask extends AsyncTask<Integer, Integer, Integer> {

	private TextView textView;

	/**
	 * コンストラクタ
	 */
	public MyTask(TextView textView) {
		super();
		this.textView   = textView;
	}

	/**
	 * バックグランドで行う処理
	 */
	@Override
	protected Integer doInBackground(Integer... value) {
        try {
            //15秒停止します。
            Thread.sleep(15000);
        } catch (InterruptedException e) {
        }
		return value[0] + 2;
	}

	/**
	 * バックグランド処理が完了し、UIスレッドに反映する
	 */
	@Override
	protected void onPostExecute(Integer result) {
		textView.setText(String.valueOf(result));
	}
}

非同期処理開始後に、確認用ボタンをクリックしてください。
クリックする度に表示される数字がカウントアップされます。
非同期処理後には、「非同期処理終了しました。」と表示されます。
このことから、非同期処理中であってもメインスレッドでの処理が可能だということがわかります。

AsyncTaskTestActivity.java(28~29行目)

        // タスクの生成
        task = new MyTask(textView);

task = new MyTask(textView);
引数でMyTaskクラスで使用したいウィジェットを渡すことができます。 非同期処理後の画面更新の時に使用します。

AsyncTaskTestActivity.java(31~39行目)

        //buttonがクリックされた時の処理を登録します。
        button.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
                ((Button)v).setEnabled(false);
                // 非同期処理を開始する
                task.execute(1);
			}
		});

task.execute(1);
executeで渡される引数は、TextViewTask.javaのdoInBackgroundメソッドに配列となって渡されます。

MyTask.java(6行目)

public class MyTask extends AsyncTask<Integer, Integer, Integer> {

ジェネリクスの指定がInteger, Integer, Integerとなっていますが、 これはdoInBackgroundメソッドの引数の型, onProgressUpdateメソッドの引数の型, onPostExecuteメソッドの戻り値の型になります。

MyTask.java(22~29行目)

	protected Integer doInBackground(Integer... value) {
        try {
            //15秒停止します。
            Thread.sleep(15000);
        } catch (InterruptedException e) {
        }
		return value[0] + 2;
	}

バックグランドで実行したい処理を記述する為のdoInBackgroundメソッドです。
doInBackgroundメソッドの引数としてメインスレッドでのexecuteメソッドで渡された 1が配列として渡されます。
また、doInBackgroundメソッドで返却される値は、 onPostExecuteメソッドの引数として渡されます。

MyTask.java(35~37行目)

	protected void onPostExecute(Integer result) {
		textView.setText(String.valueOf(result));
	}

非同期処理後に実行したい処理を記述する為のonPostExecuteメソッドです。
引数は、doInBackgroundメソッドの返却値になります。
doInBackgroundメソッドでreturn value[0] + 2;を返却しているので3が引数として渡されています。
メインスレッドで画面更新処理を行います。

■AsyncTaskを使った例その2

事前準備用のメソッドと進捗の表示用メソッドも 実装した例になります。

非同期処理の内容は画像から1pxづつ取り出してアルファ値を変更して セットするという処理にしました。
あまりよくない例かもしれませんが 処理に時間がかかり、進捗を表示できるので今回はこのようにしました。

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:text="非同期処理開始"
    	android:id="@+id/startButton"
    />

    <TextView
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    	android:id="@+id/textView"
    />

    <ImageView
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    	android:id="@+id/imageView"
    />

</LinearLayout>

AsyncTaskTestActivity.java

package com.androidtest.asynctasktest;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

public class AsyncTaskTestActivity extends Activity {
	private Button startButton;
	private TextView textView;
    private ImageView imageView_;
    private Bitmap image_;
    private MyTask task_;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //main.xmlに設定したコンポーネントをid指定で取得します。
        startButton = (Button)findViewById(R.id.startButton);
        textView    = (TextView)findViewById(R.id.textView);

        // イメージの準備
        image_ = BitmapFactory.decodeResource(getResources(), R.drawable.a);

        // 変換前のイメージを表示
        imageView_ = (ImageView)findViewById(R.id.imageView);
        imageView_.setImageBitmap(image_);

        // タスクの生成
        task_ = new MyTask(this, imageView_, textView);

        //startButtonがクリックされた時の処理を登録します。
        startButton.setOnClickListener(new OnClickListener() {
        	@Override
			public void onClick(View v) {
                ((Button)v).setEnabled(false);
                // 非同期処理を開始する
                task_.execute(image_);
			}
        });
    }
}

MyTask.java

package com.androidtest.asynctasktest;

import android.app.Activity; import android.app.ProgressDialog; import android.graphics.Bitmap; import android.graphics.Color; import android.os.AsyncTask; import android.widget.ImageView; import android.widget.TextView;

/** * */ public class MyTask extends AsyncTask {

private ImageView imageView; private TextView textView; private ProgressDialog progressDialog; private Activity uiActivity;

/** * コンストラクタ */ public MyTask(Activity ac, ImageView iv, TextView tv) { super(); uiActivity = ac; imageView = iv; textView = tv; }

/** * 事前準備の処理を行うメソッド */ @Override protected void onPreExecute() { progressDialog = new ProgressDialog(uiActivity); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setIndeterminate(false); progressDialog.show(); }

/** * バックグランドで行う処理 */ @Override protected Bitmap doInBackground(Bitmap... bitMap) { Bitmap outBitmap = bitMap[0].copy(Bitmap.Config.ARGB_8888, true); //isMutable変更可能

int width_ = outBitmap.getWidth(); int height_ = outBitmap.getHeight(); int totalPixcel = width_ * height_; progressDialog.setMax(totalPixcel);

int i,j; for(j = 0; j < height_; j++) { for(i = 0; i < width_; i++) { int pixelColor = outBitmap.getPixel(i, j); outBitmap.setPixel(i, j, Color.argb(60, Color.red(pixelColor), Color.green(pixelColor), Color.blue(pixelColor))); } //publishProgressメソッドを呼ぶことで //onProgressUpdateメソッドが呼ばれ、進捗状況がUIスレッドで表示されます。 publishProgress(i+j); } return outBitmap; } /** * 進捗状況をUIスレッドで表示する */ @Override protected void onProgressUpdate(Integer... progress) { progressDialog.incrementProgressBy(progress[0]); } /** * バックグランド処理が完了し、UIスレッドに反映する */ @Override protected void onPostExecute(Bitmap result) { progressDialog.dismiss(); imageView.setImageBitmap(result); textView.setText("非同期処理が完了しました。"); } } [/java]

まとめ

  • 非同期処理ごとにクラスを作成するので、コードが簡潔になります。
  • 事前準備や処理後のメソッドが用意されています。
  • 進捗状況をUIスレッドで表示できるメソッドもあります。
  • AsyncTaskを継承したクラスで必ず実装しなければいけないdoInBackgroundメソッドがありますが、このメソッドでは直接メインスレッドに対しての処理を行うと例外が発生します。