定義された間隔でAndroidでRunnableスレッドを実行する方法は?


351

Androidエミュレータの画面に、定義された間隔でテキストを表示するアプリケーションを開発しました。私はHandlerクラスを使用しています。これが私のコードのスニペットです:

handler = new Handler();
Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");               
    }
};
handler.postDelayed(r, 1000);

このアプリケーションを実行すると、テキストが1回だけ表示されます。どうして?


109
私は
ランナブルの

2
笑同じここではその真の交尾
NoXSaeeD

1
ラムダはほとんどの場合、今行く方法です;)
Xerus

@AdrianSicaru:同じ
Sovandara LENG

回答:


534

あなたの例の簡単な修正は:

handler = new Handler();

final Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");
        handler.postDelayed(this, 1000);
    }
};

handler.postDelayed(r, 1000);

または、通常のスレッドを使用することもできます(元のランナーを使用)。

Thread thread = new Thread() {
    @Override
    public void run() {
        try {
            while(true) {
                sleep(1000);
                handler.post(this);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

thread.start();

実行可能オブジェクトは、メッセージキューに送信して実行できるコマンドと見なすことができ、ハンドラーは、そのコマンドの送信に使用されるヘルパーオブジェクトと見なすことができます。

詳細はこちらhttp://developer.android.com/reference/android/os/Handler.html


アレックス、少し疑問があります。スレッドが完全に実行され、テキストが継続的に表示されているので、これを停止したい場合は、何をしなければならないかを教えてください。
Rajapandian 2009

11
ブール変数_stopを定義し、停止する場合は「true」に設定できます。そして、「while(true)」を「while(!_ stop)」に変更します。または、最初のサンプルが使用されている場合は、「if(!_ stop)handler.postDelayed(this、1000)」に変更します。
alex2k8 2009

メッセージを再開したい場合はどうなりますか?
Sonhja、2011年

8つの異なるImageViewを次々に表示するようにランナブルが必要な場合は、それらすべてを同じ方法で非表示に設定して(「点滅」アニメーションを作成するなど)、どうすればよいですか?
ドロイドマン

1
Handlerがメインスレッドに確実にアタッチされるようにしたい場合は、次のように初期化する必要があります。handler = new Handler(Looper.getMainLooper());
Yair Kukielka、2015

47
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);

2
Handlerがメインスレッドに確実にアタッチされるようにするには、次のように初期化する必要があります。new Handler(Looper.getMainLooper());
Yair Kukielka、2015

1
このソリューションは元の投稿と同等ではありませんか?Runnableは、100ミリ秒後に一度だけ実行されます。
トロンマン2018年

@YairKukielkaの答えが解決策です!MainLooperをアタッチする必要があります。そんな救世主!
Houssem Chlegou

40

私は毎秒正しい更新のためにAlex2k8の最初のソリューションを改善できると思います

1.オリジナルコード:

public void run() {
    tv.append("Hello World");
    handler.postDelayed(this, 1000);
}

2.分析

  • 上記のコストで、500倍の遅延時間を表示した後のtv.append("Hello Word")コストをTミリ秒とすると、500 * Tミリ秒
  • 長時間実行すると遅れて増加します

3.ソリューション

これを回避するには、遅延を回避するために、postDelayed()の順序を変更します。

public void run() {
    handler.postDelayed(this, 1000);
    tv.append("Hello World");
}

6
-1 run()で実行するタスクのコストは、実行ごとに一定のコストがかかると想定しています。これが動的データ(通常はそれ)に対する操作である場合、複数のrun()が次の場所で発生することになります。一度。これが、通常postDelayedが最後に配置される理由です。
ジェイ

1
@ジェイ残念ながらあなたは間違っています。ハンドラーは、単一のスレッド(およびそのスレッドの実行メソッドであるルーパー)+ MessageQueueに関連付けられています。メッセージを投稿するたびに、メッセージをエンキューし、次にルーパーがキューをチェックするときに、投稿したRunnableのrunメソッドを実行します。これはすべて1つのスレッドで行われるため、同時に複数を実行することはできません。また、最初にpostDelayedを実行すると、内部的に現在の時間+ 1000が実行時間として使用されるため、実行あたり1000ミリ秒に近づきます。コードを投稿の前に配置すると、遅延が追加されます。
zapl 2012

1
@zaplハンドラーのヒントをありがとう、私はそれが複数の実行可能ファイル、したがって複数のスレッドを実行すると想定しました。内部的には、((currenttime-lastruntime)> 1000)などの条件は、実行時間が1000ミリ秒以下の場合に正常に機能しますが、これを超えると、タイマーが非線形の間隔で発生しますrunメソッドの実行時間に完全に依存します(したがって、予測できない計算コストについて私のポイント)
Jay

競合なしの一定期間が必要な場合は、作業を開始する前に開始時間を測定し、それに応じて遅延を調整します。cpuがビジーの場合でも少しのレイテンシが表示されますが、これにより、より厳密な期間が可能になり、システムが過負荷状態かどうかを検出できます(おそらく、優先度の低いものにバックオフするように通知します)。
Ajax

27

あなたが使用できる繰り返しのタスクのために

new Timer().scheduleAtFixedRate(task, runAfterADelayForFirstTime, repeaingTimeInterval);

のように呼ぶ

new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {

            }
        },500,1000);

上記のコードは、0.5秒(500)後に初めて実行され、毎秒(1000)後に自身を繰り返します

どこ

実行されるメソッドであるタスク

初期実行

(実行を繰り返す時間間隔

第二に

また、タスクを何度も実行する場合は、CountDownTimerを使用することもできます。

    new CountDownTimer(40000, 1000) { //40000 milli seconds is total time, 1000 milli seconds is time interval

     public void onTick(long millisUntilFinished) {
      }
      public void onFinish() {
     }
    }.start();

//Above codes run 40 times after each second

そして、runnableでそれを行うこともできます。次のような実行可能なメソッドを作成します

Runnable runnable = new Runnable()
    {
        @Override
        public void run()
        {

        }
    };

そして、これらの両方の方法でそれを呼び出します

new Handler().postDelayed(runnable, 500 );//where 500 is delayMillis  // to work on mainThread

または

new Thread(runnable).start();//to work in Background 

オプション#3の場合、一時停止/再開して永久に停止するにはどうすればよいですか?
Si8

Handler handler = new Handler()のようなHandlerのインスタンスを作成し、handler.removeCallbacksAndMessages(null);のように削除します。
Zar E Ahmer

24

私はこの典型的なケース、つまり一定の間隔で何かを実行することTimerがより適切であると信じています。以下に簡単な例を示します。

myTimer = new Timer();
myTimer.schedule(new TimerTask() {          
@Override
public void run() {
    // If you want to modify a view in your Activity
    MyActivity.this.runOnUiThread(new Runnable()
        public void run(){
            tv.append("Hello World");
        });
    }
}, 1000, 1000); // initial delay 1 second, interval 1 second

使用にTimerはいくつかの利点があります:

  • 初期遅延と間隔はschedule関数の引数で簡単に指定できます
  • タイマーを呼び出すだけで停止できます myTimer.cancel()
  • 実行するスレッドを1つだけにする場合は、新しいスレッドをスケジュールするmyTimer.cancel() 前に必ず呼び出してください(myTimerがnullでない場合)。

7
タイマーはAndroidのライフサイクルを考慮しないため、タイマーの方が適切であるとは思いません。一時停止して再開すると、タイマーが正しく実行される保証はありません。ランナブルの方が良い選択だと私は主張します。
Janpan

1
これは、アプリがバックグラウンドで実行されると、ハンドラーが一時停止されることを意味しますか?そして、フォーカスを取り戻すと、何も起こらなかったかのように(多かれ少なかれ)継続しますか?
Andrew Gallasch 2015年

17
Handler handler=new Handler();
Runnable r = new Runnable(){
    public void run() {
        tv.append("Hello World");                       
        handler.postDelayed(r, 1000);
    }
}; 
handler.post(r);

5
これはエラーになります。2行目rでは、まだ定義されている変数を呼び出しています。
ソウル2014

Handlerがメインスレッドに確実にアタッチされるようにしたい場合は、次のように初期化する必要があります。handler = new Handler(Looper.getMainLooper());
Yair Kukielka、2015

ちょうど繰り返しの答え!
Hamid

imageviewクリックでランナブルを一時停止/再開するにはどうすればよいですか?
Si8

4

Handler.post()メソッドのドキュメントを正しく理解している場合:

Runnable rをメッセージキューに追加します。実行可能オブジェクトは、このハンドラーが接続されているスレッドで実行されます。

したがって、@ alex2k8が提供する例は、正しく機能していても、同じではありません。Handler.post()が使用されている場合、新しいスレッドは作成されませんEDTで実行されるRunnableスレッドに投稿するだけです。その後、EDTはのみを実行し、それ以外は実行しません。HandlerRunnable.run()

覚えておいてください Runnable != Thread


1
そうだね。毎回新しいスレッドを作成しないでください。ハンドラーと他の実行プールの全体のポイントは、スレッドの作成とGCを回避するために、1つまたは2つのスレッドにキューからタスクをプルさせることです。本当にリークの多いアプリがある場合、追加のGCがOutOfMemoryの状況をカバーするのに役立つ可能性がありますが、どちらの場合もより良い解決策は、必要以上の作業を作成しないようにすることです。
Ajax

これを行うためのより良い方法は、alex2k8の答えに基づいて通常のスレッドを使用することですか?
Compaq LE2202x 2013

4

コトリン

private lateinit var runnable: Runnable
override fun onCreate(savedInstanceState: Bundle?) {
    val handler = Handler()
    runnable = Runnable {
        // do your work
        handler.postDelayed(runnable, 2000)
    }
    handler.postDelayed(runnable, 2000)
}

ジャワ

Runnable runnable;
Handler handler;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    handler = new Handler();
    runnable = new Runnable() {
        @Override
        public void run() {
            // do your work
            handler.postDelayed(this, 1000);
        }
    };
    handler.postDelayed(runnable, 1000);
}

1

興味深い例は、別のスレッドで実行されているカウンター/ストップウォッチを継続的に確認できることです。GPS-Locationも表示しています。メインアクティビティのユーザーインターフェーススレッドはすでに存在しています。

抜粋:

try {    
    cnt++; scnt++;
    now=System.currentTimeMillis();
    r=rand.nextInt(6); r++;    
    loc=lm.getLastKnownLocation(best);    

    if(loc!=null) { 
        lat=loc.getLatitude();
        lng=loc.getLongitude(); 
    }    

    Thread.sleep(100); 
    handler.sendMessage(handler.obtainMessage());
} catch (InterruptedException e) {   
    Toast.makeText(this, "Error="+e.toString(), Toast.LENGTH_LONG).show();
}

コードを確認するには、ここを参照してください:

メインアクティビティのユーザーインターフェイススレッドと一緒に実行可能なGPSの場所と現在時刻を表示するスレッドの例


1
ヒント:回答を役立てたい場合は、ここで入力のフォーマット方法をご覧ください。そのプレビューウィンドウは理由があります。
GhostCat 2017

0

Kotlinでは、次のようにスレッドを実行できます。

class SimpleRunnable: Runnable {
    public override fun run() {
        println("${Thread.currentThread()} has run.")
    }
}
fun main(args: Array<String>) {
    val thread = SimpleThread()
    thread.start() // Will output: Thread[Thread-0,5,main] has run.
    val runnable = SimpleRunnable()
    val thread1 = Thread(runnable)
    thread1.start() // Will output: Thread[Thread-1,5,main] has run
}

0

コルーチンとコトリン

Kotlinでは、コルーチンを使用して次のことができます。

CoroutineScope(Dispatchers.Main).launch { // Main, because UI is changed
    ticker(delayMillis = 1000, initialDelayMillis = 1000).consumeEach {
        tv.append("Hello World")
    }
}

こちらでお試しください!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.