ตัวอย่างการใช้ Timer และ TimerTask ในแอนดรอยด์


Timer และ TimerTask เป็นคลาสในไลบรารีมาตรฐานของภาษาจาวา ซึ่งในแอนดรอยด์ก็มีให้ใช้งานเช่นกัน (ไลบรารีในแอนดรอยด์นั้น บางส่วนจะดึงมาจากของจาวาตรงๆ บางส่วนเอาของจาวามาดัดแปลงวิธีใช้งาน และบางส่วนเป็นไลบรารีเฉพาะของแอนดรอยด์เอง)
สองคลาสนี้อยู่ในแพคเกจ java.util หน้าที่ของมันก็ตามชื่อแหละครับ คือเป็นไทเมอร์หรือตัวตั้งเวลาการทำงาน ที่ช่วยให้เราทำงานหนึ่ง ณ เวลาที่ต้องการ หรือทำงานหนึ่งซ้ำๆกันทุกช่วงเวลาที่ต้องการได้ เช่น ตั้งเวลาให้อ่านข้อมูลจากเซิร์ฟเวอร์ทุกๆ 10 นาที เป็นต้น
สาเหตุที่ผมเขียนบทความนี้ เนื่องจากมีผู้อ่านหนังสือของผมท่านหนึ่งถามมาว่า “ภายในหน้าเดียวกัน ถ้ามีภาพ 3 ภาพ และต้องการไม่ให้ภาพปรากฏพร้อมกันจะทำได้อย่างไร เช่น ภาพที่ 1 มาก่อน ต่อมาค่อยปรากฏภาพที่ 2 แล้วสุดท้ายค่อยปรากฏภาพที่ 3” ผมลองนึกๆดูและหาข้อมูลเพิ่มจากกูเกิล ก็พบว่ามีหลายวิธีที่น่าจะทำได้ และหนึ่งในนั้นก็คือการตั้งเวลาเปลี่ยนภาพใน ImageView โดยใช้ Timer ร่วมกับ TimerTask นี่แหละครับ

หลักการใช้งาน Timer และ TimerTask

วิธีใช้งาน Timer และ TimerTask ก่อนอื่นให้สร้างคลาสใหม่โดยสืบทอดจาก TimerTask (มักสร้างเป็น Inner Class ภายในคลาสหลัก) ในที่นี้ตั้งชื่อคลาสว่า MyTask และภายในคลาสนี้ ให้ Override เมธอด run โดยระบุการทำงานที่เราต้องการทำทุกๆช่วงเวลาหนึ่งลงไป ยกตัวอย่างเช่น ถ้าต้องการเปลี่ยนภาพใน ImageView ทุกๆ 2 วินาที ก็ให้ใส่โค้ดที่ใช้เปลี่ยนภาพลงในเมธอด run นี้
จากนั้นภายในคลาสหลัก (กรณีของแอนดรอยด์ก็คือ Activity) ให้สร้างออบเจ็ค Timer ขึ้นมา แล้วเรียกเมธอด schedule เพื่อตั้งเวลาที่จะเรียกไปยังเมธอด run ใน MyTask
  • อาร์กิวเมนต์ตัวแรกคืออินสแทนซ์ (ออบเจ็ค) ของคลาส MyTask ที่เราสร้างขึ้น ซึ่งมีเมธอด run ที่เราระบุการทำงานที่ต้องการไว้
  • อาร์กิวเมนต์ตัวที่สองคือ delay หรือเวลาที่ต้องการให้หน่วง (ถ่วง) ไว้ก่อนเริ่มรัน ในที่นี้ระบุค่า 0 เพราะต้องการให้รันโค้ดในเมธอด run ทันที
  • อาร์กิวเมนต์ตัวที่สามคือ period หรือช่วงเวลาที่จะรันโค้ดในเมธอด run ซ้ำๆกัน หน่วยเป็นมิลลิวินาที (1/1000 ของวินาที) ในที่นี้ระบุ 1000 หมายถึงให้รันทุกๆ 1 วินาที
หลักการใช้งาน Timer และ TimerTask มีแค่นี้เอง อย่างไรก็ตาม ถ้าเราเขียนโค้ดในเมธอด run เพื่อเปลี่ยนภาพที่แสดงใน ImageView เช่น สมมติเขียนโค้ดเพื่อแสดงภาพตัวเลข 0 ถึง 9 ออกมาทีละตัวเลข ดังนี้
เมื่อรันแอพจะเกิดข้อผิดพลาด โดยแอพจะถูกปิดไปเลย ดังรูป

และใน LogCat จะมีบรรทัดหนึ่งแจ้งข้อผิดพลาดว่า
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
สาเหตุของข้อผิดพลาด เนื่องมาจากเมธอด run ที่เราเตรียมไว้ใน MyTask นั้นจะถูกรันแยกออกมาในเธรด (Thread) ใหม่ ซึ่งเป็นลักษณะการทำงานตามปกติของ TimerTask อยู่แล้ว แต่แอนดรอยด์มีกฎข้อหนึ่งที่สำคัญมากซึ่งมือใหม่มักไม่ค่อยรู้ นั่นก็คือ โค้ดที่จะสามารถเข้าถึง UI ได้ ต้องเป็นโค้ดในเธรดหลัก (เธรดที่สร้าง UI ขึ้นมา) เท่านั้น ในที่นี้เราพยายามเข้าถึง ImageView จากโค้ดในเธรดอื่น แอนดรอยด์ก็เลยไม่อนุญาต
แต่ปัญหานี้แก้ได้ไม่ยากครับ แอนดรอยด์เตรียมทางออกไว้แล้ว โดยให้เรียกเมธอด runOnUiThread ของแอคทิวิตี แล้วระบุอาร์กิวเมนต์เป็นออบเจ็ค Runnable ที่คุณทำการ Override เมธอด run ของมัน
จากนั้นคุณก็ใส่โค้ดที่มีการเข้าถึง UI ลงในเมธอด run ของ Runnable
แล้วนำโค้ดทั้งหมดข้างต้นไปใส่ลงในเมธอด run ของ MyTask อีกทีหนึ่ง
เพียงเท่านี้คุณก็สามารถเข้าถึง ImageView จากเธรดอื่น (เมธอด run ใน MyTask) ได้แล้ว

โปรเจ็ค TestTimer

ที่อธิบายไปข้างต้นคือหลักการคร่าวๆในการใช้ Timer ร่วมกับ TimerTask ทีนี้มาดูโค้ดเต็มๆบ้างครับ โปรเจ็ค TestTimer นี้เป็นแอพที่จะแสดงภาพตัวเลข 0 ถึง 9 ออกมาตามลำดับทีละตัวเลข โดยเปลี่ยนตัวเลขทุกๆ 1 วินาที การทำงานของ Timer จะเริ่มเมื่อคลิกปุ่ม Start Timer และจะสิ้นสุดเมื่อแสดงถึงเลข 9 แล้ว
ดาวน์โหลดโปรเจ็ค TestTimer

Layout File (ไฟล์ activity_main.xml)

แอคทิวิตี (ไฟล์ MainActivity.java)

ผลการรัน


ขอบคุณภาพตัวเลขสวยๆจาก OpenClipArt.org
Image Courtesy of OpenClipArt.org
 
ที่มา่ http://promlert.com/2013/04/using_timer_and_timertask/
Previous
Next Post »