#えっと
こんがらがるので、ちょっとまとめ的に、マルチスレッド処理のメモ
<マルチスレッドとスレッド間通信>
■ Threadクラスの拡張 | Runnableインターフェイスの継承
UIスレッドとの通信
view#post(Runnable)
View#postDelayed(Runnable.long)
runOnUiThread(Runnable)
■ AsyncTaskクラス
処理とUIスレッド通信
execute(),doInBackground(),postExecute()などで
■ Handlerクラス
マルチスレッド間の通信を実現(UIスレッドでなくてもOK)
Message
Runnable
##■Thread(Thread/Runnable)
Androidは、1つのLinuxプロセス上で、1スレッドを使って稼働するよう。このスレッドをシングルスレッドと呼び、MainActivity.javaはシングルスレッドで実行します。ここでは、MainActivityが稼働するスレッドをメインスレッドと呼んで記述です。UI部品を直接扱えるのはメインスレッドだけで、他のスレッドからUI部品を操作するには、専用のメソッドを使うとのこと。
MainActivityは、ユーザーが操作するインターフェイス的な役割を担っているので、メインスレッドで時間の掛かる処理をすると、操作画面の反応が悪くなったり、全体的な処理が遅くなったりと、使いづらくなってしまう。
なので、アプリの使いやすさを保つために、ユーザー操作に関係のない処理は別のスレッドを使って別処理するようにデザインします(マルチスレッド)。別スレッド処理は、ThreadクラスかRunnableインターフェイスのどっちかを使て行うみたい。
<ThreadとRunnableの違い>
Threadは「public class Thread extends Object implements Runnable」で、Runnableを継承したクラスです。Runnableは「public interface Runnable」です。Threadクラスの定数やメソッドを使いたいのならThreadクラスを拡張してね!そうでなければRunnableを継承してみたら?ってことなのでしょうか?
別スレッドもメインスレッドと同じLinuxプロセス上で処理されるので、シングルプロセスでは不都合がある場合など、必要に応じて別のLinuxプロセスを立ち上げて(マルチプロセス)、別スレッドを生成したりも検討してもよいかもです(Manifest.java)。
###□Thread
public void onCreate(){
SubThread subThread = new SubThread();
subThread.start();
}
class SubThread extends Thread{
public void run(){
// todo
}
}
###□Runnable
Runnableの記述ですが、よく見る記述方法が3つあります。
new Thread(new Runnable(){
@Override
public void run(){
// todo
}
}).start();
public void onCreate(){
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
Thread.start();
}
class MyRunnable implement Runnable{
@Override
public void run(){
// todo
}
}
public void onCreate(){
Thread thread = new Thread(MyRunnable());
}
class MyRunnable implement Runnable{
@Override
public void run(){
// todo
}
}
###Threadの停止処理
- Serviceでスレッド稼働
- ServiceがあればServiceは重複起動しない
- 停止は cancel()で
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// スタートとストップボタンのオブジェクトを取得ね!
Button btn_start = findViewById(R.id.btn_start);
Button btn_stop = findViewById(R.id.btn_stop);
final Intent intent = new Intent(this,MyService.class);
btn_start.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
// MyServiceが起動されてなければ起動!
if(!MyService.serviceState) {
startForegroundService(intent);
}
}
});
btn_stop.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
// stopService() で、MyService の onDestroy を呼ぶよ~
stopService(intent);
}
});
}
public class MyService extends Service {
// serviceState = true なら MyServiceは既に起動
// threadState = true なら Threadは稼働し続ける
static boolean serviceState;
boolean threadState;
public MyService() {
serviceState = true;
threadState = true;
}
@Override
public int onStartCommand(Intent intent,int flags,int startId){
// Notification の コンテンツを設定するよ!
Notification notification;
NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"id");
notification = builder.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("sanple")
.setContentText("text")
.build();
// Notification の チャンネルと重要度 を設定するよ!
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel("id","name",importance);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
startForeground(1,notification);
// スレッドをスタートです!
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
// threadState が true の間、2秒毎にカウントログを流します。
while(threadState){
Log.d("msg",String.valueOf(count));
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}).start();
return START_NOT_STICKY;
}
@Override
public void onDestroy(){
Log.d("msg","onDestroy");
// MainActivity で stopService() を呼ぶと onDestroy()が呼ばれる
// MyService は停止、Threadも 停止状態に設定
serviceState = false;
threadState = false;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
###Threadの破棄、強制終了
Threadの破棄(stop())はAPI15から非推奨です。ということは、それ以前から、Threadを破棄したいとか、強制終了したいとか、そういうコードニーズがあって、破棄できるコードも推奨でした。でもAPI15からは非推奨になりました。Thread破棄が色々と悪いゴミを残してしまうからです。
Threadが正常に稼働してる時に、保護されていたオブジェクトが、Thread破棄によって不安定な状態になってしまい、予期せぬ動作をしたりする可能性が生が生じます。Threadから遊離してしまったオブジェクトなどを見出したり特定することは難しいので、Threadは破棄せずに、interrupt()でプログラムを設計できないか検討しましょう、って感じでAPI15からはスレッド強制終了は非推奨になりました。可愛い可愛いアプリたちのために(それとあなたのアプリを可愛がってくれるユーザのために)、Thread.stop()は使わずに、interrupt()な方法でコードデザインお願いです!
もし、それでもstop()がいいんだ!ってことなら、行方不明者(オブジェクトとか)が出ないように使うのが吉なのですかね?
##■Thread間通信
□ UIスレッド <- ワーカースレッド
□ ワーカースレッド <- ワーカースレッド
####□スレッド間通信(Handler:LooperとQueue)
Handleはスレッド間で通信するクラスで、Handleインスタンスを生成したスレッドに、通信に必要なQueue(キュー)とLooperをという機能を保持する。他のスレッドから送られた情報はキューに保管されて、保管された情報をLooperが取り出してメインスレッドで処理します(メインスレッドで処理するので、UI部品を操作することもできるのです)。
####□ ワーカースレッドからUIスレッドに通信
ワーカースレッドからUI部品を直接は操作できないようなので、UI部品を操作するためのメソッドやクラスを使って、UI部品の操作処理をUIスレッドに渡す的に作用するよう。
- View.post(Runnable)
- View.postDelayed(Runnable,long)
- Activity.runOnUiThread(Runnable)
- Handlerクラス
- AsyncTaskクラスの非同期処理
View.postを使ってViewを操作する方法は、操作が複雑になるとメインテナンスが難しくなります。ワーカースレッドでHandleを使う方法もありますが、リファレンス的にはAsyncTaskが推奨なのです。
View.post()とView.postDelayed()ですが、どちらもViewオブジェクトにあるメソッドです。異なるViewからのpost()でも(TextView.post()だったり、Button.Post()だったり)、受け取るキュー側では同じView.post()として受け取ります(送り手がTextViewとかEditTextとかを識別していない)。
####・View.post()
public boolean post(Runnable action)
Viewクラスのメソッド:TextView.post,TextEdit.post(),Button.post()みたいに、view部品はpost(),postDelayed()メソッドが使える。
postはメッセージキューにRunnableを追加し、UIスレッドでRunnableを処理する。Runnable処理が成功すればtrueを返してくれます。falseの場合は、メッセージキューが終了してしまっているケースが多いみたい。
######Threadクラスでpost
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/textView"
/>
public class MainActivity extends AppCompatActivity {
public static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
// Threadクラスをインスタンス化してスタート
ThreadSample threadSample = new ThreadSample();
threadSample.start();
}
}
class ThreadSample extends Thread{
public void run(){
// メインアクティビティのUI部品を操作
// post()メソッドはRunnableを引数にするので以下のように
MainActivity.textView.post(new Runnable(){
@Override
public void run() {
MainActivity.textView.setText("From ThreadSample");
}
});
}
}
######無名クラスでpost()
備忘のため、無名クラスでも書いておきます。
public class MainActivity extends AppCompatActivity {
public static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
new Thread(new Runnable() {
@Override
public void run() {
textView.post(new Runnable() {
@Override
public void run() {
textView.setText("From Another Thread");
}
});
}
}).start();
}
}
####・View.postDelayed
public boolean postDelayed(Runnable action,long delayMillis)
delay...遅延;Millis...ミリ秒
postDelayedは、メッセージキューにRunnableを追加し、指定時間後に、UIスレッドでRunnableを処理します。
下記のコードを実行すると、textviewには初期値の”Hello World!”で出迎えてくれますが、5秒経過すると、”From ThreadSample”と表示が変わります。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/textView"
/>
public class MainActivity extends AppCompatActivity {
public static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
ThreadSample threadSample = new ThreadSample();
threadSample.start();
}
}
class ThreadSample extends Thread{
public void run(){
MainActivity.textView.postDelayed(new Runnable(){
@Override
public void run() {
MainActivity.textView.setText("From ThreadSample");
}
},5000);
}
}
####・Activity.runOnUiThread
public final void runOnUiThread(Runnable action)
Activityクラスのメソッド:ActivityはrunOnUiThread()が使える。UI画面を提供するのがActivity。
runOnUiThreadは、指定したアクションをUIスレッドで実行します。もし現在のスレッドがUIスレッドなら、アクションは即実行!そうでなければ、アクションはUIスレッドのイベントキューに追加されるのです。動作はpost()メソッドとほぼ同じように機能するみたいですね!。
public class MainActivity extends AppCompatActivity {
public static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
new Thread(new Runnable(){
@Override
public void run() {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText("runOnUiThread");
}
});
}
}).start();
}
}
##■Handler
詳しくはこちらの「Hanlerまとめ的な」
Handlerを使うと、スレッドのメッセージキューに関連付けられたRunnableオブジェクトやプロセスメッセージを送信できます。Handlerのインスタンスはシングルスレッドとメッセージキューに関連付けられてます。メッセージキューに追加されるとtrueを戻します。処理が成功したかどうかではありません。指定時間経過前にルーパーが終了するとRunnableは放棄されてしまうのです。falseの場合は、メッセージキューが終了してしまってるケースを疑え!
##■android.os.AsyncTask
public abstract class AsyncTask extends Object
android.os.AsyncTask
AsyncTaskは非同期処理。特に短時間の非同期処理に適してます。**長時間実行するような処理は他のAPI(Executorとか、ThreadPoolExecutorとか)**を使ってくださいね!