Làm cách nào để chạy một chuỗi Runnable trong Android theo các khoảng thời gian xác định?


351

Tôi đã phát triển một ứng dụng để hiển thị một số văn bản theo các khoảng thời gian xác định trong màn hình giả lập Android. Tôi đang sử dụng Handlerlớp học. Đây là một đoạn trong mã của tôi:

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

Khi tôi chạy ứng dụng này, văn bản chỉ được hiển thị một lần. Tại sao?


109
Tôi không bao giờ có thể nhớ làm thế nào để chạy được, vì vậy tôi luôn ghé thăm bài viết của bạn về cách thực hiện :))
Adrian Sicaru

2
haha tương tự ở đây bạn đời rất đúng
NoXSaeeD

1
lambdas là cách để đi bây giờ hầu hết thời gian;)
Xerus

@AdrianSicaru: tương tự
Sovandara LENG

Câu trả lời:


534

Cách khắc phục đơn giản cho ví dụ của bạn là:

handler = new Handler();

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

handler.postDelayed(r, 1000);

Hoặc chúng ta có thể sử dụng chủ đề bình thường chẳng hạn (với Người chạy ban đầu):

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

thread.start();

Bạn có thể coi đối tượng có thể chạy của mình giống như một lệnh có thể được gửi đến hàng đợi tin nhắn để thực thi và xử lý như một đối tượng trợ giúp được sử dụng để gửi lệnh đó.

Thông tin chi tiết có tại đây http://developer.android.com/reference/android/os/Handler.html


Alex, tôi có một nghi ngờ nhỏ. Bây giờ chủ đề đang chạy hoàn hảo và hiển thị văn bản liên tục, nếu tôi muốn dừng việc này có nghĩa là tôi phải làm gì? Xin hãy giúp tôi.
Rajapandian

11
Bạn có thể xác định biến boolean _stop và đặt nó là 'true' khi bạn muốn dừng. Và thay đổi 'while (true)' thành 'while (! _ Stop)' hoặc nếu mẫu đầu tiên được sử dụng, chỉ cần thay đổi thành 'if (! _ Stop) handler.postDelayed (this, 1000)'.
alex2k8

Nếu tôi muốn khởi động lại tin nhắn thì sao?
Sonhja

và nếu tôi cần một runnable để đặt 8 ImageView khác nhau hiển thị lần lượt, sau đó đặt tất cả chúng vô hình theo cùng một cách và cứ thế (để tạo một hình ảnh động "nhấp nháy"), làm thế nào tôi có thể làm điều đó?
Droidman

1
Nếu bạn muốn chắc chắn rằng Handler sẽ được gắn vào luồng chính, bạn nên khởi tạo nó như thế này: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka

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

2
Nếu bạn muốn chắc chắn rằng Handler sẽ được gắn vào luồng chính, bạn nên khởi tạo nó như thế này: Handler mới (Looper.getMainLooper ());
Yair Kukielka

1
Không phải là giải pháp này tương đương với bài viết gốc? Nó sẽ chỉ chạy Runnable một lần sau 100 mili giây.
tronman

@YairKukielka trả lời là giải pháp! bạn cần đính kèm MainLooper. Thật là một vị cứu tinh!
Houssem Chlegou

40

Tôi nghĩ có thể cải thiện giải pháp đầu tiên của Alex2k8 để cập nhật chính xác từng giây

1. Mã gốc:

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

2.Phân tích

  • Trong chi phí trên, giả sử tv.append("Hello Word")chi phí T mili giây, sau khi hiển thị 500 lần thời gian trễ là 500 * T mili giây
  • Nó sẽ tăng chậm khi chạy thời gian dài

3. Giải pháp

Để tránh điều đó, chỉ cần thay đổi thứ tự của postDelayed (), để tránh bị trì hoãn:

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

6
-1 bạn đang giả sử tác vụ bạn thực hiện trong run () là chi phí không đổi mỗi lần chạy, nếu đây là hoạt động trên dữ liệu động (thường là như vậy) thì bạn sẽ kết thúc với nhiều hơn một lần chạy () xảy ra tại Một lần. Đây là lý do tại sao postDelayed thường được đặt ở cuối.
Jay

1
@Jay Thật không may bạn đã sai. Một Handler được liên kết với một Thread duy nhất (và Looper là phương thức chạy của Thread đó) + MessageQueue. Mỗi khi bạn đăng một Tin nhắn bạn sẽ ghi lại nó và lần tiếp theo, kẻ lừa đảo kiểm tra hàng đợi, nó sẽ thực thi phương thức chạy của Runnable mà bạn đã đăng. Vì đó là tất cả xảy ra chỉ trong một Chủ đề, bạn không thể thực hiện nhiều hơn 1 Chủ đề cùng một lúc. Ngoài ra, việc thực hiện postDelayed trước sẽ giúp bạn tiến gần hơn tới 1000ms cho mỗi lần thực thi vì bên trong nó sử dụng thời gian hiện tại + 1000 làm thời gian thực hiện. Nếu bạn đặt mã trước bài viết, bạn thêm độ trễ bổ sung.
zapl

1
@zapl cảm ơn vì lời khuyên về trình xử lý, tôi giả sử nó sẽ thực thi nhiều runnables và do đó, nhiều luồng. Mặc dù bên trong, một điều kiện như nếu ((thời gian hiện tại - lần cuối)> 1000) sẽ hoạt động tốt khi thời lượng chạy nhỏ hơn hoặc bằng 1000ms, tuy nhiên, khi vượt quá điều này, chắc chắn bộ định thời sẽ xảy ra ở các khoảng thời gian phi tuyến tính phụ thuộc hoàn toàn vào thời gian thực hiện của phương thức chạy (do đó quan điểm của tôi về chi phí tính toán không thể đoán trước)
Jay

Nếu bạn muốn có một khoảng thời gian cố định, loại bỏ xung đột, đo thời gian bắt đầu trước khi thực hiện công việc và điều chỉnh độ trễ cho phù hợp. Bạn vẫn sẽ thấy một chút độ trễ nếu cpu đang bận, nhưng nó có thể cho phép bạn có một khoảng thời gian chặt chẽ hơn và để phát hiện xem hệ thống có bị quá tải hay không (có lẽ là để báo hiệu những thứ ưu tiên thấp để tắt).
Ajax

27

Để lặp lại tác vụ, bạn có thể sử dụng

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

gọi nó như thế

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

            }
        },500,1000);

Đoạn mã trên sẽ chạy lần đầu tiên sau nửa giây (500) và lặp lại sau mỗi giây (1000)

Ở đâu

nhiệm vụ là phương thức được thực thi

sau thời gian thực hiện ban đầu

( khoảng thời gian để lặp lại việc thực hiện)

Thứ hai

Và bạn cũng có thể sử dụng CountDownTimer nếu bạn muốn thực thi số lần Nhiệm vụ.

    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

Và bạn cũng có thể làm điều đó với runnable. tạo một phương thức runnable như

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

        }
    };

Và gọi nó theo cả hai cách

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

HOẶC LÀ

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

Đối với tùy chọn # 3, làm thế nào tôi có thể tạm dừng / tiếp tục và cũng dừng vĩnh viễn?
Si8

tạo một thể hiện của Handler như Handler handler = new Handler () và xóa nó như handler.removeCallbacksAndMessages (null);
Zar E Ahmer

24

Tôi tin cho trường hợp điển hình này, tức là chạy một cái gì đó với một khoảng thời gian cố định, Timerlà phù hợp hơn. Đây là một ví dụ đơn giản:

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

Sử dụng Timercó một vài lợi thế:

  • Độ trễ ban đầu và khoảng thời gian có thể được chỉ định dễ dàng trong các scheduleđối số hàm
  • Có thể dừng bộ hẹn giờ bằng cách gọi đơn giản myTimer.cancel()
  • Nếu bạn muốn chỉ có một luồng đang chạy, hãy nhớ gọi myTimer.cancel() trước khi lên lịch cho một luồng mới (nếu myTimer không null)

7
Tôi không tin rằng một bộ đếm thời gian phù hợp hơn vì nó không xem xét vòng đời của Android. Khi bạn tạm dừng và tiếp tục, không có đảm bảo rằng bộ hẹn giờ sẽ chạy chính xác. Tôi sẽ tranh luận rằng một runnable là sự lựa chọn tốt hơn.
Janpan

1
Điều đó có nghĩa là khi một ứng dụng được đặt trong nền, Trình xử lý sẽ bị tạm dừng? và khi nó lấy lại được sự tập trung, nó sẽ tiếp tục (ít nhiều) như thể không có gì xảy ra?
Andrew Gallasch

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

5
Điều này sẽ cung cấp cho một lỗi. Trong dòng thứ hai của bạn, bạn đang gọi biến rđược xác định.
seoul

Nếu bạn muốn chắc chắn rằng Handler sẽ được gắn vào luồng chính, bạn nên khởi tạo nó như thế này: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka

Chỉ cần trả lời lặp đi lặp lại!
Hamid

Làm cách nào tôi có thể tạm dừng / tiếp tục chạy được với một lần nhấp hình ảnh?
Si8

4

Nếu tôi hiểu chính xác tài liệu của phương thức Handler.post ():

Làm cho r Runnable được thêm vào hàng đợi tin nhắn. Runnable sẽ được chạy trên luồng mà trình xử lý này được đính kèm.

Vì vậy, các ví dụ được cung cấp bởi @ alex2k8, mặc dù đang hoạt động chính xác, không giống nhau. Trong trường hợp, nơi Handler.post()được sử dụng, không có chủ đề mới được tạo ra . Bạn chỉ cần đăng Runnablelên chủ đề Handlerđể được thực hiện bởi EDT . Sau đó, EDT chỉ thực thi Runnable.run(), không có gì khác.

Ghi nhớ : Runnable != Thread.


1
Đúng là. Đừng tạo ra một chủ đề mới mọi lúc, mọi nơi. Toàn bộ điểm của Handler và các nhóm thực thi khác là có một hoặc hai luồng kéo các tác vụ ra khỏi hàng đợi, để tránh tạo luồng và GC. Nếu bạn có một ứng dụng thực sự bị rò rỉ, thì GC bổ sung có thể giúp che giấu các tình huống OutOfMemory, nhưng giải pháp tốt hơn trong cả hai trường hợp là tránh tạo ra nhiều công việc hơn bạn cần.
Ajax

Vì vậy, cách tốt hơn để làm điều này là bằng cách sử dụng chuỗi bình thường dựa trên câu trả lời của alex2k8?
Compaq LE2202x

4

Kotlin

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)
}

Java

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

Một ví dụ thú vị là bạn có thể liên tục thấy một bộ đếm / đồng hồ bấm giờ đang chạy trong luồng riêng biệt. Cũng hiển thị GPS-Location. Trong khi hoạt động chính Giao diện người dùng đã có.

Trích đoạn:

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();
}

Để xem mã xem tại đây:

Ví dụ về luồng hiển thị Vị trí GPS và Thời gian hiện tại có thể chạy cùng với Giao diện người dùng của hoạt động chính


1
Gợi ý: nếu bạn muốn làm cho câu trả lời của mình hữu ích - tìm hiểu cách định dạng đầu vào tại đây. Cửa sổ xem trước đó tồn tại vì một lý do.
GhostCat

0

bây giờ trong Kotlin bạn có thể chạy các chủ đề theo cách này:

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 với quân đoàn

Trong Kotlin, sử dụng coroutines bạn có thể làm như sau:

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

Hãy thử nó ở đây !

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.