DACエンジニアブログ:アドテクゑびす界

DACのエンジニアやマーケター、アナリストが執筆するアドテクの技術系ブログです。

Android 非同期処理についてまとめてみた

Androidには、UIに影響を与えないよういくつか非同期処理が用意されています。 今回は非同期処理の代表的な

・Service ・IntentService ・HandlerThread

について違いを踏まえながらまとめます! 非同期処理について(http://codezine.jp/article/detail/9746)

まずはServiceとIntentServiceについて説明します。

■ Service ・メリット - Activityに依存せずに長時間のバックグラウンド処理に向いている。 - Contextを持っている。

・デメリット - メインスレッド上(UIスレッド上)で動作するため、重い処理を行うと画面のレスポンスが遅くなったり、アプリが落ちる。

※Serviceの優位性 ServiceはActivityと同一のスレッドで動いているため、 Serviceを使わずにActivityからスレッドを生成すればいいのでは?と思った方がいるのではないでしょうか? 重要なのはServiceはContext(アプリの状態)を保持していることです。 Activityからでもスレッドは生成し処理を行うことは可能ですが、Activityが終了してしまうと、Activityに関連したContext(アプリの状態を保持した情報)は使用できなくなります。 そのため、Contextを利用したバックグラウンド処理にはServiceを使う必要があります。

■ IntentService ・メリット - Activityに依存せず非同期の処理に向いている。 - 内部にHandlerThreadを持っているため、メインスレッドとは別のスレッド上で逐次処理を行う。

ServiceとIntentServiceの違いを見てみる

下記にServiceを使った処理とIntentServiceを使った処理を記載します。 ActivityからIntentを使ってService/IntentServiceを呼び出します。 ・Activity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        // リスナーをセット
        findViewById(R.id.servicebutton).setOnClickListener(this);
        findViewById(R.id.intentServicebutton).setOnClickListener(this);


    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.servicebutton) {
            //Serviceクラスを起動させる。通知を表示するServiceを起動
            Intent intent = new Intent(this, HelloIntentService.class);
            //Intent intent = new Intent(this, MyService.class);
            intent.setAction("show");
            startService(intent);
        } else if (v.getId() == R.id.intentServicebutton) {
            //IntentServiceクラスを起動させる。通知を表示するIntentServiceを起動
            Intent intent = new Intent(this, MyIntentService.class);
            intent.setAction("show");
            startService(intent);
        }
    }
}

・Service(あえてfor文を使い重い処理を行う)

public class MyService extends Service {
    public MyService() {
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
       //重い処理を行う。
        return START_NOT_STICKY;
    }

→アプリが落ちる。

・IntentService(あえてfor文を使い重い処理を行う)

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("HelloIntentService");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
     //重い処理を行う。
        }
    }

→UIに影響を与えないため、アプリは落ちない。

次にHandlerとHandlerThreadについて説明します。

HandlerThreadについて

HandlerThreadは内部にLooperを持ち、Handlerによって送られてきたメッセージを逐次処理するための仕組みです。わからない用語が出てきたので、まずは”Looper”と"Handler"について説明します。

LooperとHandlerについて

Looperとは

Looperとは内部にMessageキューを持ち、順番にキューから取り出したメッセージを処理する仕組みです。 これはAndroidの基本的な仕組みとなっておりActivityやServiceなどもこのLooper上で動作しています(デバックしてみるとわかります。はい)。 そしてこのActivityやServiceは特別なメインルーパー(メインスレッド)で動作しており、画面に関するウィジェットの変更はメインルーパーで行わなければなりません。 ※別スレで行うとエラーが出ます。

Handlerとは

HandlerとはそのLooperにMessage(タスク)を届けるためのメッセンジャーです。

HandlerとHandlerThreadの違いを見てみる

Handlerはメインスレッド上で行われています。 HandlerThreadは新たにThreadを生成して、そこで処理を行います。

public class HandlerDefault extends AppCompatActivity {
    private String s;

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

        Handler handler = new Handler();
        handler.post(new Runnable() {
            @Override
            public void run() {
                ////重たい処理を記述
                s = Thread.currentThread().getName();
            }
        });
    }

}

→デバックして変数sの中身を確認する。 s = main メインスレッド上の処理のため、重たい処理を記載するとアプリは落ちる。

public class HandlerThreadDefault extends AppCompatActivity {
    private String s;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread_default);
        // 別スレ生成 -> 開始
        HandlerThread handlerThread = new HandlerThread("other");
        handlerThread.start();

        //作成したHandlerThread(別スレ)内部のLooperを引数として、HandlerThread(のLooper)にメッセージを送るHandlerを生成する。
        Handler handler = new Handler(handlerThread.getLooper());
        //Handlerのpostメソッドでメッセージ(タスク:重たい処理)を送信する。
        handler.post(new Runnable() {
            @Override
            public void run() {
                //重たい処理を記述
                s = Thread.currentThread().getName();
            }
        });
    }
}

→デバックして変数sの中身を確認する。 s = other 別スレッドを生成しているため、重たい処理を記載してもUIに影響を与えることはなくアプリが落ちる心配はない。 なるほどなるほど、ここで疑問。 Q 画面に関する操作はメインスレッド上で行わなければならないとのこと。もし、別スレッド上で行ったらどうなるのだろうか?

@Override
    public void run() {
         System.out.println(Thread.currentThread().getName());
         s = Thread.currentThread().getName();
         Log.v("hoge", "thread name:" + Thread.currentThread().getName());
         //変更箇所:別スレッド(other)からメインスレッドの画面を切り替える処理を行ってみる。できるのかな?
         View v = findViewById(R.id.button);
    }

・結果 問題なく、ボタンが読み込まれました。 Handlerの引数(メインスレッドのLooper)にHandlerThreadのLooper処理の結果を渡してるから、 別スレッドの処理結果をメインスレッドに渡しているからなんだ。

Handler handler = new Handler(handlerThread.getLooper());

(http://ichitcltk.hustle.ne.jp/gudon2/index.php? pageType=file&id=Android010_Handler) 手順

1.別スレッド(HandlerThread)を生成 2.別スレッド開始 3.HandlerThreadインスタンス から Looperインスタンス 取得 4.Handlerインスタンスを生成(3で取得した Looperインスタンス を 引数指定) 5.4 で作成した Handlerインスタンス を使用(Handler#post 等)

参考 ・http://daichan4649.hatenablog.jp/entry/20111004/1317724067https://realm.io/jp/news/android-thread-looper-handler/

UIスレッドからのみ画面操作が可能 ・http://dev.classmethod.jp/etc/22853/