วิธีรันเธรดที่เรียกใช้ได้ใน Android ตามช่วงเวลาที่กำหนด


351

ฉันพัฒนาแอพพลิเคชั่นเพื่อแสดงข้อความตามช่วงเวลาที่กำหนดในหน้าจอตัวจำลอง Android ฉันกำลังใช้Handlerชั้นเรียน นี่คือตัวอย่างจากรหัสของฉัน:

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

เมื่อฉันเรียกใช้แอปพลิเคชันนี้ข้อความจะปรากฏเพียงครั้งเดียว ทำไม?


109
ฉันไม่สามารถจำได้ว่าจะทำ runnable ดังนั้นฉันมักจะเยี่ยมชมโพสต์ของคุณเกี่ยวกับวิธีการทำ :))
Adrian Sicaru

2
ฮ่าฮ่าเหมือนกันที่นี่เพื่อนแท้
NoXSaeeD

1
lambdas เป็นหนทางที่จะไปเวลาส่วนใหญ่;)
Xerus

@AdrianSicaru: เหมือนกัน
Sovandara LENG

คำตอบ:


535

การแก้ไขตัวอย่างของคุณอย่างง่าย ๆ คือ:

handler = new Handler();

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

handler.postDelayed(r, 1000);

หรือเราสามารถใช้เธรดปกติตัวอย่างเช่น (พร้อม Runner ต้นฉบับ):

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

11
คุณอาจกำหนดตัวแปรบูลีน _stop และตั้งค่าเป็น 'จริง' เมื่อคุณต้องการหยุด และเปลี่ยน 'while (true)' เป็น 'while (! _ stop)' หรือถ้าตัวอย่างแรกที่ใช้เพียงแค่เปลี่ยนเป็น 'if (! _ stop) handler.postDelayed (นี่คือ 1,000)'
alex2k8

จะเป็นอย่างไรถ้าฉันต้องการเริ่มข้อความใหม่
Sonhja

และถ้าฉันต้องการ runnable เพื่อตั้งค่า ImageViews ที่แตกต่างกัน 8 ตัวที่มองเห็นได้หลังจากนั้นให้ตั้งค่าให้มองไม่เห็นทั้งหมดในวิธีเดียวกันและต่อไปเรื่อย ๆ (เพื่อสร้างภาพเคลื่อนไหว "กะพริบ") ฉันจะทำอย่างไร
Droidman

1
หากคุณต้องการให้แน่ใจว่าตัวจัดการจะเชื่อมต่อกับเธรดหลักคุณควรเริ่มต้นเช่นนี้: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka

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

2
หากคุณต้องการให้แน่ใจว่าตัวจัดการจะเชื่อมต่อกับเธรดหลักคุณควรเริ่มต้นเช่นนี้: ตัวจัดการใหม่ (Looper.getMainLooper ());
Yair Kukielka

1
โซลูชันนี้ไม่เทียบเท่ากับโพสต์ต้นฉบับหรือไม่ มันจะรัน Runnable เพียงครั้งเดียวหลังจาก 100 มิลลิวินาที
tronman

@ YairKukielka คำตอบคือทางออก! คุณต้องแนบ MainLooper ช่างเป็นผู้ช่วยชีวิต!
Houssem Chlegou

40

ฉันคิดว่าสามารถปรับปรุงทางออกแรกของ Alex2k8 สำหรับการอัพเดทให้ถูกต้องในแต่ละวินาที

1. รหัสเดิม:

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

2.Analysis

  • ในค่าใช้จ่ายข้างต้นสมมติว่าtv.append("Hello Word")ค่าใช้จ่ายTมิลลิวินาทีหลังจากการแสดง500ครั้งล่าช้าเป็น500 * Tมิลลิวินาที
  • มันจะเพิ่มความล่าช้าเมื่อใช้เวลานาน

3. โซลูชั่น

เพื่อหลีกเลี่ยงการที่เพียงแค่เปลี่ยนลำดับของ postDelayed () เพื่อหลีกเลี่ยงความล่าช้า:

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

6
-1 คุณกำลังสมมติว่างานที่คุณทำในการรัน () เป็นค่าใช้จ่ายจำนวนคงที่ในแต่ละการรันหากนี่เป็นการดำเนินการกับข้อมูลแบบไดนามิก ครั้งหนึ่ง นี่คือเหตุผลที่โพสต์ล่าช้ามักจะอยู่ที่ปลาย
เจย์

1
@ เจย์น่าเสียดายที่คุณผิด ตัวจัดการเกี่ยวข้องกับเธรดเดี่ยว (และ Looper ซึ่งเป็นวิธีการรันของเธรดนั้น) + a MessageQueue ทุกครั้งที่คุณโพสต์ข้อความที่คุณเข้าคิวและในครั้งถัดไปที่ Looper ตรวจสอบคิวมันจะรันวิธีการเรียกใช้ของ Runnable ที่คุณโพสต์ เนื่องจากทั้งหมดนี้เกิดขึ้นในเธรดเดียวคุณจึงไม่สามารถดำเนินการได้มากกว่า 1 รายการพร้อมกัน การโพสต์ล่าช้าก่อนอื่นคุณจะเข้าใกล้ 1,000ms ต่อการประหารชีวิตเพราะภายในจะใช้เวลาปัจจุบัน + 1000 เป็นเวลาดำเนินการ หากคุณใส่รหัสก่อนโพสต์คุณเพิ่มความล่าช้าเพิ่มเติม
zapl

1
@zapl ขอบคุณสำหรับเคล็ดลับเกี่ยวกับตัวจัดการฉันคิดว่ามันจะรันหลาย runnables และด้วยเหตุนี้หลายกระทู้ ภายในแม้ว่าเงื่อนไขเช่นถ้า ((ปัจจุบัน - ล่าสุด - เวลาล่าสุด)> 1,000) จะทำงานได้ดีเมื่อระยะเวลาการทำงานน้อยกว่าหรือเท่ากับ 1,000 มิลลิวินาทีอย่างไรก็ตามเมื่อเกินค่านี้ตัวจับเวลาจะเกิดขึ้นในช่วงเวลาที่ไม่ใช่เชิงเส้น ขึ้นอยู่กับเวลาดำเนินการของวิธีการเรียกใช้ทั้งหมด (ด้วยเหตุนี้จุดของฉันเกี่ยวกับค่าใช้จ่ายในการคำนวณที่คาดเดาไม่ได้)
Jay

หากคุณต้องการช่วงเวลาที่แน่นอนความขัดแย้งโดยไม่ต้องวัดเวลาเริ่มต้นก่อนที่จะทำงานและปรับความล่าช้าของคุณตามนั้น คุณจะยังคงเห็นความหน่วงเล็กน้อยหากซีพียูไม่ว่าง แต่จะช่วยให้คุณมีช่วงเวลาที่เข้มงวดมากขึ้นและตรวจสอบว่าระบบโอเวอร์โหลดหรือไม่
Ajax

27

สำหรับภารกิจการทำซ้ำคุณสามารถใช้

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

เรียกว่าชอบ

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

            }
        },500,1000);

รหัสข้างต้นจะทำงานเป็นครั้งแรกหลังจากครึ่งวินาที (500)และทำซ้ำตัวเองหลังจากแต่ละวินาที (1,000)

ที่ไหน

taskเป็นวิธีการที่จะดำเนินการ

หลังจากเวลาที่จะดำเนินการเริ่มต้น

( ช่วงเวลาสำหรับการดำเนินการซ้ำ)

ในประการที่สอง

และคุณยังสามารถใช้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 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 handler = new 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()
  • หากคุณต้องการให้มีเพียงหนึ่งเธรดที่ทำงานอยู่โปรดอย่าลืมโทรmyTimer.cancel() ก่อนกำหนดเวลาใหม่ (ถ้า myTimer ไม่ใช่โมฆะ)

7
ฉันไม่เชื่อว่าตัวจับเวลามีความเหมาะสมมากกว่าเพราะไม่ได้พิจารณาวงจรชีวิตของ Android เมื่อคุณหยุดชั่วคราวและเล่นต่อไม่มีการรับประกันว่าตัวจับเวลาจะทำงานอย่างถูกต้อง ฉันจะยืนยันว่า runnable เป็นตัวเลือกที่ดีกว่า
Janpan

1
หมายความว่าเมื่อวางแอปในพื้นหลังตัวจัดการจะหยุดชั่วคราวหรือไม่ และเมื่อมันกลับมาโฟกัสมันจะดำเนินต่อไป (มากหรือน้อยกว่า) ราวกับว่าไม่มีอะไรเกิดขึ้น?
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
สิ่งนี้จะทำให้เกิดข้อผิดพลาด ในบรรทัดที่สองของคุณคุณกำลังเรียกตัวแปรrที่กำหนดไว้
โซล

หากคุณต้องการให้แน่ใจว่าตัวจัดการจะเชื่อมต่อกับเธรดหลักคุณควรเริ่มต้นเช่นนี้: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka

เพียงแค่ทำซ้ำคำตอบ!
ฮามิด

ฉันจะหยุดชั่วคราว / ทำงาน runnable ต่อได้อย่างไรด้วยการคลิกที่ imageview?
Si8

4

ถ้าฉันเข้าใจเอกสาร Handler.post () วิธีการอย่างถูกต้อง:

ทำให้ Runnable r ถูกเพิ่มในคิวข้อความ runnable จะถูกรันบนเธรดที่ติดตั้ง handler นี้

ดังนั้นตัวอย่างที่มีให้โดย @ alex2k8 แม้ว่าจะทำงานอย่างถูกต้อง แต่ก็ไม่เหมือนกัน ในกรณีที่Handler.post()มีการใช้ไม่มีกระทู้ใหม่ถูกสร้างขึ้น คุณเพียงแค่โพสต์Runnableกระทู้ที่มีHandlerจะได้รับการดำเนินการโดยEDT หลังจากนั้น EDT จะดำเนินการเท่านั้นRunnable.run()ไม่มีอะไรอื่นอีก

จำเอาไว้: Runnable != Thread.


1
จริงที่ อย่าสร้างหัวข้อใหม่ทุกครั้ง จุดทั้งหมดของตัวจัดการและพูลการดำเนินการอื่นคือให้เธรดหนึ่งหรือสองเธรดดึงงานออกจากคิวเพื่อหลีกเลี่ยงการสร้างเธรดและ GC หากคุณมีแอพที่รั่วจริงๆ GC พิเศษอาจช่วยปกปิดสถานการณ์ OutOfMemory แต่ทางออกที่ดีกว่าในทั้งสองกรณีคือการหลีกเลี่ยงการสร้างงานมากกว่าที่คุณต้องการ
Ajax

ดังนั้นวิธีที่ดีกว่าในการทำเช่นนี้คือการใช้เธรดปกติตามคำตอบของ 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)
}

ชวา

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 ด้วย ในขณะที่กิจกรรมหลักเธรดส่วนต่อประสานกับผู้ใช้นั้นมีอยู่แล้ว

ข้อความที่ตัดตอนมา:

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

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 กับ Coroutines

ใน Kotlin การใช้ coroutines คุณสามารถทำสิ่งต่อไปนี้:

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.