LoginSignup
1
1

More than 3 years have passed since last update.

51歳からのプログラミング 備忘 マルチスレッド 迷走したので自分的にちょっとまとめ

Last updated at Posted at 2019-09-11

迷走しました!
過去の関連記事もなんだか分からなかくなってるので、自分用の整理まとめメモ!なので他の記事と内容やコードが重複してます。

Threadの概説

Androidは、基本的に1つのLinuxプロセスに、一つのスレッドを作って稼働するシングルスレッドモデルです(Manifestで定義すれば、別のプロセスに別のスレッドを作ってプログラムを稼働さたりも!)。

Androidでは、ユーザーエクスペリエンスを確保するため、ユーザー操作に影響を与えるような処理は、別スレッドで処理すべき、ってことになってて、あとUI部品はUIスレッドからしか操作できない、というルールもあります(UIはユーザーインターフェイス)。別スレッドはワーカースレッド(ユーザーインターフェイスを持たない)と呼ばれたりします。

別スレッドで処理させたりすると、UIスレッドとワーカースレッドとか、ワーカースレッド同士で通信する必要が生じます。で、UI部品はUIスレッドからしか操作できないので、ワーカースレッドからUI部品を更新する方法も知っとく必要があったりします。

通信のために、メインスレッド(UIスレッド)には、Looperが備わっていて、Looper内にMessageQueueを持ってます。MessageQueueは、他のスレッドから送られるMessageやRunnableを積み上げ保持し、Looperが取り出して処理します。

<注意>
ワーカースレッドの処理結果を、UIスレッドに渡して、UI部品を更新する作業は、ThreadクラスやRunnableインターフェイスを使ったマルチスレッド処理だと、複雑になりやすく、見通しが悪くなり、メンテナンスも困難になるって皆んなが言ってる。ハイコストなコードになってしまうのですね。

リファレンス的推奨はAsncTaskクラスです

マルチスレッド間で結果をやり取りできる通信手段はHandlerクラス!

Threadの生成の概説

詳しくは

ThreadはThreadクラスとRunnableインターフェイスのどっちを使う?

ThreadクラスはRunnableインターフェイスを継承したクラスで、Runnableと違って色んな定数やメソッドを持ってます。Threadクラスの定数やメソッドを使うのなら、Threadクラスを拡張して、特に使うメソッドとかがないのならRunnableを承継して、ワーカースレッドを作ればいいのかな?

ワーカースレッドとUIスレッドの通信

ThreadクラスやRunnableインターフェースで作成したワーカースレッドから、処理の結果をUIスレッド(MainActivity)に引き渡すには、Viewクラスのpost(Runnable)メソッド、postDelayed(Runnable,long)とかActivityクラスのrunOnUiThread(Runnable)を使います。HandlerクラスでもOK。

AnsyTaskとかHandlerとか

ThreadクラスやRunnableクラス以外の方法では、UIスレッドとの通信パッケージとなるAsyncTaskクラスがあります。またスレッド(UIスレッド以外も)間の通信を実現するHandlerクラスがあります。

AsyncTask

詳しくは!

ThreadクラスやRunnableインターフェイス以外の方法で、別スレッドからUI部品を操作するのなら、リファレンス推奨なAsyncTask!いやむしろAsyncTask

AsyncTaskは短時間処理に適してます。長時間スレッドで処理をさせたいのならExesutor,ThreadPoolExecuter,FutureTaskなど、java.util.concurrentパッケージで提供されるAPIsを検討してね!

AsyncTaskは並行処理によるエラーを避けるため、シングルスレッドで処理されます。並行処理をするのならTHREAD_POOL_EXECUTORとともに、executeOnExecutor(java.util.concurrent.Executor,java.lang.Object[])を検討しましょう

Andorid.os.AsyncTaskの用法

□ UIスレッド側
  AsyncTask継承クラスをインスタンス化してExecute()を実行する

□ AsyncTask継承クラス(青字は必須)
  onPreExecute()
  doInBackground()
    // todo
    publishProgress()
  onProgressUpdate()
  onPostExecute()

各メソッド
Execute() doInBackground()を呼び出すdoInBackgorundが処理するタスクに必要な引数を渡す
onPreExecute() Execute()の実行前にしたい処理を実行
doInBackgorund() Execute()で起動し、Execute()から引き受ける引数で処理を実行
publichProgress doInBackground()でタスクを処理してる間に、onProgressUpdate()でユーザーに表示したい文字やProgressを定義
onProgressUpdate UI部品の操作をUIスレッドに渡す。
onPostExecute doInBackground()から処理結果を受け取り、UI部品の操作をUIスレッドに渡す

android.os.Handler スレッド間の通信手段

詳しくはこちらの 「Handlerまとめ的な」

Handlerはマルチスレッドで(UIスレッドやワーカースレッド同士)で処理結果を送れます。

Handlerクラスをインスタンス化(handler)すると、インスタンス化したスレッドに、LooperとMessageQueueを備えます。

handlerは他のスレッドから使えるので、post()、handle.obtainMessage()、handle.sendMessage()などを使って、handlerを生成したメソッドにメッセージやRunnableのようなオブジェクトを送って、生成スレッドで処理を行います。

サンプルコード

Thread

こちら

AsyncTask

サンプルコード:execute()とdoInBackground()

  • MainActivity.java
    AsyncTask<>をサブクラス化し、execute()を実行する
  • AsyncTask<>クラスを拡張して、MyAsyncTaskクラスを生成
    doInBackground()は、MainActivity.javaのexecute()によって処理を実行する(todo)。doInBackground()はexecute()から引数を受け取るって処理に使うよ。
public class MainActivity extends AppCompatActivity {

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

        // タスクインスタンスを生成。execute()で引数をonInBackground()に渡す
        // new MyAsyncTask<>.execute();  でもOK
        MyAsyncTask myAsyncTask = new MyAsyncTask<>;
        myAsyncTask.execute();

    }
}

class MyAsyncTask extends AsyncTask<Void,Void,Void> {
    @Override
    protected Void doInBackground(Void... aVoid) {
        // todo
        Log.d("msg","In MyAsyncTask");
        return null;
    }
}

サンプルコード:幾つかメソッドを入れてみる

3秒毎にtextViewの表示を変えるコード

  • MyActivity.onCreate()
    myAsncTask.excute("In doInBackground")
  • class MyAsyncTask
    • onPreExecute()
      textView.setText("onPreExecute")
    • doInBackground()
      sleep(1000*3) -> publishProgress(string[0]) -> sleep(1000*3) -> return "End doInBackground"
    • onProgressUpdate()
      textView.setText(string[0])
    • onPostExecute()
      textView.setText(s)
public class MainActivity extends AppCompatActivity {

    TextView textView;

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

        textView = findViewById(R.id.textView);

        // タスクインスタンスを生成。execute()で引数をonInBackground()に渡す
        // new MyAsyncTask<>.execute();  でもOK
        MyAsyncTask myAsyncTask = new MyAsyncTask<>;
        // doInBackground()に文字列("In doInBackground")を渡して
        // onPreExecute()の処理後に、doInBackground()を実行
        myAsyncTask.execute("In doInBackground");

    }

    class MyAsyncTask extends AsyncTask<String,String, String> {

        // execute("In doInBackground")処理前に実行。UIスレッドのtextViewに表示
        @Override
        protected void onPreExecute(){
            textView.setText("onPreExecute");
        }

        // execute()から文字列"In doInBackground"を受け取る
        @Override
        protected String doInBackground(String... string) {

            // 3秒待機
            try {
                sleep(1000*3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // doInBackground処理中に、textViewを表示させるonPublishUpdate()を呼ぶ
            publishProgress(string[0]);

            // 3秒待機
            try {
                sleep(1000*3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // onPostExecute()に文字列"End doInBackground"を渡す
            return "End doInBackground";
        }

        // doInBackground内のpublishProgress()から文字列を受け取り、UIスレッドのtextViewに表示
        protected void onProgressUpdate(String... string){
            textView.setText(string[0]);
        }

        // doInBackground()からreturn値("End doInBackground")を受け取り、UIスレッドのtextViewに表示
        @Override
        protected void onPostExecute(String s){
            textView.setText(s);
        }
    }
}

Handler

HandlerとMessageの事例1

Message
public class MainActivity extends AppCompatActivity {

    TextView textView;

    // UIスレッド(MainActivity)でHandlerインスタンスを生成します
    // これでMessageQueueもLooperもUIスレッドの物だね
    // インスタンス handler は別スレッドから参照可能なので
    // 別スレッドで handler を参照して、messageを載せれば
    // UIスレッド(MainActivity)でLooperが処理してくれるんだぜ!
    // カッコいいじゃん!!
    // では、handler さんに登場願います!
    Handler handler = new MyHandler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView1);

        MyLovelyMessage();
    }

    private void MyLovelyMessage() {
        // UIスレッド(MainActivity)で生成した handler にアクセスし
        // handler に Messageを載せて sendMessage!
        // これでUIスレッドのLooperが上手くやってくれるはず!
        Message message = handler.obtainMessage(1);
        handler.sendMessage(message);
    }

    class MyHandler extends Handler{
        @Override
        public void handleMessage(Message message){
            // handle が UIスレッドで何をするかを定義する
            // Looperによって自動的に実行されるよ!
            String msg = String.valueOf(message.what);
            textView.setText(msg);
        }
    }
}

上のコードを内部関数的にシンプルに

Message
public class MainActivity extends AppCompatActivity {

    TextView textView;

    Handler handler = new Handler(){
      @Override
      public void handleMessage(Message message){
          String msg = String.valueOf(message.what);
          textView.setText(msg);
      }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView1);

        MyLovelyRunnable();
    }

    private void MyLovelyRunnable() {
        Message message = handler.obtainMessage(1);
        handler.sendMessage(message);
    }
}

HandlerとRunnableを使った事例

public class MainActivity extends AppCompatActivity {

    TextView textView;

    // HandlerをUIスレッド(MainAvtivity.class)で生成します
    // これで、MessageQueue も Looper も、このスレッド(UIスレッド)に関連付けれます
    //
    // Handlerインスタンスは、他のスレッドからも参照できるので
    // 他のスレッドで、インスタンス handler を使って
    // Runnableをこのスレッド(UIスレッド)に渡せば
    // このスレッド(UIスレッド側)でRunnableを実行できるようになります
    // 素敵ですね!
    // ではHandlerのインスタンスを生成しましょう!!
    Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView1);

        MyLovelyHandler();

    }

    private void MyLovelyHandler(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                // UIスレッド(MainActivity)で生成したHandlerのインスタンスは
                // 他のスレッドからでも参照可能です
                // インスタンスhandlerにRunnableを載せて
                // UIスレッド(MainActivity)に渡して処理してもらいましょう!
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("I Love Handler");
                    }
                });
            }
        }).start();
    }
}
1
1
0

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
1
1