LoginSignup
51

More than 5 years have passed since last update.

Android 非同期処理についてまとめる メモ

Last updated at Posted at 2017-01-08

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

・Service
・IntentService
・HandlerThread


について違いを踏まえながらまとめる。
非同期処理について()

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

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

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

※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/1317724067
https://realm.io/jp/news/android-thread-looper-handler/

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
51