Android Kotlin Fundamentals 09.2: WorkManager

Codelab นี้เป็นส่วนหนึ่งของหลักสูตรพื้นฐานเกี่ยวกับ Kotlin ใน Android คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้ หากทํางานผ่าน Codelab ตามลําดับ Codelab ของหลักสูตรทั้งหมดจะแสดงอยู่ในหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals

บทนำ

แอปในชีวิตจริงส่วนใหญ่จะต้องทํางานในเบื้องหลังที่ยาวนาน เช่น แอปอาจอัปโหลดไฟล์ไปยังเซิร์ฟเวอร์ ซิงค์ข้อมูลจากเซิร์ฟเวอร์ และบันทึกลงในฐานข้อมูล Room ส่งบันทึกไปยังเซิร์ฟเวอร์ หรือดําเนินการกับข้อมูลราคาแพง การดําเนินการดังกล่าวควรดําเนินการในเบื้องหลัง แยกจากชุดข้อความ UI (ชุดข้อความหลัก) งานในเบื้องหลังจะใช้ทรัพยากรที่จํากัดของอุปกรณ์ เช่น RAM และแบตเตอรี่ ซึ่งอาจส่งผลให้ผู้ใช้ได้รับประสบการณ์ใช้งานที่ไม่ดี

ใน Codelab นี้ คุณดูวิธีใช้ WorkManager เพื่อตั้งเวลางานในเบื้องหลังอย่างเหมาะสมและมีประสิทธิภาพ หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับโซลูชันอื่นๆ ที่ใช้ได้สําหรับการประมวลผลในเบื้องหลังใน Android โปรดดูคําแนะนําเกี่ยวกับการประมวลผลในเบื้องหลัง

สิ่งที่ควรทราบอยู่แล้ว

  • วิธีใช้คอมโพเนนต์สถาปัตยกรรม Android ViewModel, LiveData และ Room
  • วิธีเปลี่ยนรูปแบบชั้นเรียน LiveData
  • วิธีสร้างและเปิดตัวโครูทีน
  • วิธีใช้อะแดปเตอร์การเชื่อมโยงในการเชื่อมโยงข้อมูล
  • วิธีโหลดข้อมูลที่แคชไว้โดยใช้รูปแบบที่เก็บ

สิ่งที่คุณจะได้เรียนรู้

  • วิธีสร้าง Worker ซึ่งแสดงถึงหน่วยงาน
  • วิธีสร้าง WorkRequest เพื่อขอให้ดําเนินการ
  • วิธีเพิ่มข้อจํากัดลงใน WorkRequest เพื่อกําหนดเวลาและผู้ปฏิบัติงานควรทํางาน
  • วิธีใช้ WorkManager เพื่อตั้งเวลางานในเบื้องหลัง

สิ่งที่คุณจะทํา

  • สร้างผู้ปฏิบัติงานเพื่อทํางานเบื้องหลังเพื่อดึงเพลย์ลิสต์วิดีโอ DevBytes ล่วงหน้าจากเครือข่าย
  • กําหนดเวลาให้ผู้ปฏิบัติงานทํางานเป็นระยะๆ
  • เพิ่มข้อจํากัดในWorkRequest
  • ตั้งเวลาWorkRequestเป็นระยะๆ ซึ่งดําเนินการวันละครั้ง

ใน Codelab นี้ คุณจะใช้แอป DevBytes ที่คุณพัฒนาใน Codelab ก่อนหน้านี้ได้ (หากไม่มีแอปนี้ คุณสามารถดาวน์โหลดรหัสเริ่มต้นสําหรับบทเรียนนี้)

แอป DevBytes จะแสดงรายการวิดีโอ DevByte ซึ่งเป็นบทแนะนําสั้นๆ จากทีมนักพัฒนาซอฟต์แวร์ Android ของ Google วิดีโอเหล่านี้นําเสนอฟีเจอร์สําหรับนักพัฒนาซอฟต์แวร์และแนวทางปฏิบัติแนะนําในการพัฒนา Android

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

ในงานนี้ คุณจะดาวน์โหลดและตรวจสอบโค้ดเริ่มต้น

ขั้นตอนที่ 1: ดาวน์โหลดและเรียกใช้แอปเริ่มต้น

คุณยังคงทํางานผ่านแอป DevBytes ที่สร้างไว้ใน Codelab ก่อนหน้านี้ได้ (หากมี) หรือคุณจะดาวน์โหลดแอปเริ่มต้นก็ได้

ในงานนี้ คุณจะดาวน์โหลดและเรียกใช้แอปเริ่มต้นและตรวจสอบโค้ดเริ่มต้นได้

  1. หากยังไม่มีแอป DevBytes โปรดดาวน์โหลดโค้ดเริ่มต้น DevBytes สําหรับ Codelab นี้จากโปรเจ็กต์ DevBytesRepository จาก GitHub
  2. แตกโค้ดแล้วเปิดโครงการใน Android Studio
  3. เชื่อมต่ออุปกรณ์ทดสอบหรือโปรแกรมจําลองกับอินเทอร์เน็ตหากยังไม่ได้เชื่อมต่อ สร้างและเรียกใช้แอป แอปจะดึงข้อมูลรายการวิดีโอ DevByte จากเครือข่ายและแสดง
  4. แตะวิดีโอในแอปเพื่อเปิดวิดีโอในแอป YouTube

ขั้นตอนที่ 2: สํารวจโค้ด

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

  1. ใน Android Studio ให้ขยายแพ็กเกจทั้งหมด
  2. สํารวจแพ็กเกจ database แพ็กเกจนี้มีเอนทิตีฐานข้อมูลและฐานข้อมูลภายใน ซึ่งมีการใช้งานโดยใช้ Room
  3. สํารวจแพ็กเกจ repository แพ็กเกจนี้ประกอบด้วยคลาส VideosRepository ที่แสดงนามชั้นข้อมูลจากส่วนที่เหลือของแอป
  4. สํารวจโค้ดเริ่มต้นที่เหลือด้วยตัวเองและรับความช่วยเหลือจาก Codelab ก่อนหน้า

WorkManager เป็นหนึ่งใน Android Architecture Components และเป็นส่วนหนึ่งของ Android Jetpack WorkManager มีไว้สําหรับการทํางานในเบื้องหลังที่หน่วงเวลาได้และต้องมีการดําเนินการที่รับประกันการแสดงผล

  • เลื่อนได้หมายความว่าไม่จําเป็นต้องทํางานทันที เช่น การส่งข้อมูลวิเคราะห์ไปยังเซิร์ฟเวอร์หรือซิงค์ฐานข้อมูลในเบื้องหลังเป็นข้อมูลที่เลื่อนได้
  • การดําเนินการที่รับประกันการแสดงผลหมายความว่างานจะทํางานแม้ว่าแอปออกหรืออุปกรณ์รีสตาร์ท

แม้ว่า WorkManager จะทํางานในเบื้องหลัง ระบบนี้ก็แก้ปัญหาความเข้ากันได้และแนวทางปฏิบัติแนะนําเกี่ยวกับแบตเตอรี่และความปลอดภัยของระบบ WorkManager มีความเข้ากันได้กลับไปเป็น API ระดับ 14 WorkManager เลือกวิธีที่เหมาะสมในการกําหนดเวลางานในเบื้องหลัง โดยขึ้นอยู่กับระดับ API ของอุปกรณ์ โดยอาจใช้ JobScheduler (ใน API 23 ขึ้นไป) หรือใช้ AlarmManager และ BroadcastReceiver ร่วมกัน

WorkManager ยังช่วยให้คุณกําหนดเกณฑ์และเวลาที่งานในเบื้องหลังจะทํางานได้อีกด้วย ตัวอย่างเช่น คุณอาจต้องการให้งานทํางานเฉพาะเมื่อสถานะแบตเตอรี่ สถานะเครือข่าย หรือสถานะการชาร์จตรงตามเกณฑ์บางอย่างเท่านั้น ดูวิธีตั้งข้อจํากัดใน Codelab นี้ได้ในภายหลัง

ใน Codelab นี้ คุณกําหนดเวลางานเพื่อดึงเพลย์ลิสต์วิดีโอ DevBytes ล่วงหน้าจากเครือข่ายวันละครั้ง หากต้องการกําหนดเวลางานนี้ คุณจะใช้ไลบรารี WorkManager

  1. เปิดไฟล์ build.gradle (Module:app) และเพิ่มทรัพยากร Dependency ใน WorkManager ไปยังโปรเจ็กต์

    หากคุณใช้ไลบรารี เวอร์ชันล่าสุด แอปโซลูชันควรรวบรวมตามที่คาดไว้ หากไม่ใช่ ให้ลองแก้ปัญหาหรือเปลี่ยนกลับเป็นเวอร์ชันไลบรารีที่แสดงด้านล่าง
// WorkManager dependency
def work_version = "1.0.1"
implementation "android.arch.work:work-runtime-ktx:$work_version"
  1. ซิงค์โปรเจ็กต์และตรวจสอบว่าไม่มีข้อผิดพลาดในการคอมไพล์

ทําความคุ้นเคยกับคลาสต่อไปนี้ในไลบรารี WorkManager ก่อนเพิ่มโค้ดลงในโปรเจ็กต์

  • Worker
    ชั้นเรียนนี้คือที่ที่คุณกําหนดงานจริง (งาน) ให้ทํางานในเบื้องหลัง คุณจะขยายคลาสนี้และลบล้างเมธอด doWork() ได้ วิธี doWork() คือที่ที่คุณนําโค้ดไปใช้ในเบื้องหลัง เช่น การซิงค์ข้อมูลกับเซิร์ฟเวอร์หรือการประมวลผลรูปภาพ คุณนํา Worker มาใช้ในงานนี้
  • WorkRequest
    ชั้นเรียนนี้แสดงคําขอเพื่อเรียกใช้ผู้ปฏิบัติงานในเบื้องหลัง ใช้ WorkRequest เพื่อกําหนดค่าวิธีการและเวลาที่จะมอบหมายงานของผู้ปฏิบัติงาน โดยรับความช่วยเหลือจาก Constraints เช่น ที่เสียบปลั๊กหรือเชื่อมต่อ Wi-Fi โดยคุณจะใช้ WorkRequest ในงานถัดไป
  • WorkManager
    ชั้นเรียนนี้จะกําหนดเวลาและเรียกใช้ WorkRequest ของคุณ WorkManager กําหนดเวลาคําของานในลักษณะที่กระจายภาระทรัพยากรของระบบ พร้อมทั้งทําตามข้อจํากัดที่คุณระบุไว้ โดยคุณจะใช้ WorkManager ในงานถัดไป

ขั้นตอนที่ 1: สร้างผู้ปฏิบัติงาน

ในงานนี้ คุณจะได้เพิ่ม Worker เพื่อดึงเพลย์ลิสต์วิดีโอ DevBytes ไว้ล่วงหน้า

  1. ในแพ็กเกจ devbyteviewer ให้สร้างแพ็กเกจใหม่ที่ชื่อว่า work
  2. ในแพ็กเกจ work ให้สร้างคลาส Kotlin ใหม่ที่ชื่อว่า RefreshDataWorker
  3. ขยายคลาส RefreshDataWorker จากชั้นเรียน CoroutineWorker ส่งใน context และ WorkerParameters เป็นพารามิเตอร์เครื่องมือสร้าง
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {
}
  1. หากต้องการแก้ไขข้อผิดพลาดคลาสนามธรรม ให้ลบล้างเมธอด doWork() ภายในคลาส RefreshDataWorker
override suspend fun doWork(): Result {
  return Result.success()
}

ฟังก์ชันการระงับคือฟังก์ชันที่หยุดชั่วคราวหรือหยุดชั่วคราวได้ในภายหลัง ฟังก์ชันการระงับสามารถเรียกใช้การดําเนินการที่ใช้เวลานาน และรอให้การทํางานเสร็จสมบูรณ์โดยไม่ต้องบล็อกชุดข้อความหลัก

ขั้นตอนที่ 2: ใช้งาน doWork()

มีการเรียกเมธอด doWork() ภายในคลาส Worker ในเทรดพื้นหลัง วิธีการนี้จะทํางานพร้อมกัน และควรแสดงออบเจ็กต์ ListenableWorker.Result ระบบ Android ให้เวลา Worker สูงสุด 10 นาทีเพื่อดําเนินการให้เสร็จสิ้นและส่งคืนออบเจ็กต์ ListenableWorker.Result หลังจากหมดเวลาดังกล่าว ระบบจะบังคับให้ Worker หยุดทํางาน

หากต้องการสร้างออบเจ็กต์ ListenableWorker.Result ให้เรียกหนึ่งในวิธีคงที่ต่อไปนี้เพื่อระบุสถานะความสมบูรณ์ของการทํางานเบื้องหลัง

  • Result.success() - ทํางานเสร็จแล้ว
  • Result.failure() - งานที่ดําเนินการเสร็จแล้วและมีความล้มเหลวอย่างถาวร
  • Result.retry() - งานประสบปัญหาชั่วคราว และต้องลองดําเนินการอีกครั้ง

ในงานนี้ คุณจะได้ใช้วิธี doWork() เพื่อดึงข้อมูลเพลย์ลิสต์วิดีโอ DevBytes จากเครือข่าย คุณนําวิธีการที่มีอยู่ในชั้นเรียน VideosRepository มาใช้ซ้ําเพื่อเรียกข้อมูลจากเครือข่ายได้

  1. ในคลาส RefreshDataWorker ให้สร้างและสร้างอินสแตนซ์ออบเจ็กต์ VideosDatabase และออบเจ็กต์ VideosRepository ภายใน doWork()
override suspend fun doWork(): Result {
   val database = getDatabase(applicationContext)
   val repository = VideosRepository(database)

   return Result.success()
}
  1. ในคลาส RefreshDataWorker ภายใน doWork() เหนือคําสั่ง return ให้เรียกใช้เมธอด refreshVideos() ภายในบล็อก try เพิ่มบันทึกเพื่อติดตามเมื่อผู้ปฏิบัติงานทํางาน
try {
   repository.refreshVideos( )
   Timber.d("Work request for sync is run")
   } catch (e: HttpException) {
   return Result.retry()
}

หากต้องการแก้ไขข้อผิดพลาด "ยังไม่ได้แก้ไข &" ให้นําเข้า retrofit2.HttpException

  1. ต่อไปนี้คือชั้นเรียน RefreshDataWorker ที่สมบูรณ์สําหรับการอ้างอิงของคุณ
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {

   override suspend fun doWork(): Result {
       val database = getDatabase(applicationContext)
       val repository = VideosRepository(database)
       try {
           repository.refreshVideos()
       } catch (e: HttpException) {
           return Result.retry()
       }
       return Result.success()
   }
}

Worker กําหนดหน่วยงาน และ WorkRequest กําหนดวิธีและเวลาที่ควรทํางาน การใช้งานคลาส WorkRequest ที่เป็นรูปธรรมมีอยู่ 2 อย่าง ได้แก่

  • ชั้นเรียน OneTimeWorkRequest มีไว้สําหรับงานแบบครั้งเดียว (งานที่ทําครั้งเดียวจะเกิดขึ้นเพียงครั้งเดียวเท่านั้น)
  • ชั้นเรียน PeriodicWorkRequest มีไว้สําหรับการทํางานตามรอบเวลา ซึ่งเป็นงานที่เกิดซ้ําเป็นระยะ

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

ในงานนี้ คุณจะได้กําหนดและกําหนดเวลา WorkRequest เพื่อเรียกใช้ผู้ปฏิบัติงานที่คุณสร้างไว้ในงานก่อนหน้า

ขั้นตอนที่ 1: ตั้งค่างานที่เกิดซ้ํา

ในแอป Android ระดับ Application คือคลาสพื้นฐานที่มีคอมโพเนนต์อื่นๆ ทั้งหมด เช่น กิจกรรมและบริการ เมื่อสร้างกระบวนการสําหรับแอปพลิเคชันหรือแพ็กเกจแล้ว จะมีการดําเนินการคลาส Application (หรือคลาสย่อยของ Application) ก่อนชั้นเรียนอื่นๆ

ในแอปตัวอย่างนี้ คลาส DevByteApplication เป็นคลาสย่อยของ Application ชั้นเรียนDevByteApplicationเป็นวิธีที่ดีในการตั้งเวลาWorkManager

  1. ในชั้นเรียน DevByteApplication ให้สร้างเมธอดชื่อ setupRecurringWork() เพื่อตั้งค่าการทํางานในเบื้องหลังที่เกิดขึ้นซ้ํา
/**
* Setup WorkManager background job to 'fetch' new network data daily.
*/
private fun setupRecurringWork() {
}
  1. ภายในเมธอด setupRecurringWork() ให้สร้างและเริ่มต้นคําของานเป็นระยะเพื่อเรียกใช้วันละครั้งโดยใช้เมธอด PeriodicWorkRequestBuilder() ส่งในชั้นเรียน RefreshDataWorker ที่คุณสร้างในงานก่อนหน้า ผ่านช่วง 1 ซ้ําโดยมีหน่วยเวลา TimeUnit.DAYS
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .build()

หากต้องการแก้ไขข้อผิดพลาด ให้นําเข้า java.util.concurrent.TimeUnit

ขั้นตอนที่ 2: กําหนดเวลา WorkRequest ด้วย WorkManager

หลังจากกําหนด WorkRequest แล้ว คุณจะกําหนดเวลาด้วย WorkManager ได้โดยใช้เมธอด enqueueUniquePeriodicWork() วิธีนี้จะช่วยให้คุณเพิ่ม PeriodicWorkRequest ที่ไม่ซ้ํากันลงในคิวได้ โดยที่จะมีเพียง PeriodicWorkRequest ชื่อที่ใช้งานอยู่ได้เพียงรายการเดียวเท่านั้น

เช่น คุณอาจต้องการให้การดําเนินการซิงค์เพียง 1 รายการทํางาน หากการดําเนินการซิงค์ 1 รายการรอดําเนินการอยู่ คุณสามารถเลือกอนุญาตให้เรียกใช้หรือแทนที่ด้วยงานใหม่ได้โดยใช้ existingPeriodicWorkPolicy

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีนัดหมายเวลา WorkRequest ในเอกสารประกอบเกี่ยวกับ WorkManager

  1. ในชั้นเรียน RefreshDataWorker เพิ่มออบเจ็กต์ที่แสดงร่วมกันเมื่อเริ่มต้นชั้นเรียน กําหนดชื่องานเพื่อระบุผู้ปฏิบัติงานคนนี้ไม่ซ้ํากัน
companion object {
   const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
  1. ในชั้นเรียน DevByteApplication ที่ท้ายวิธี setupRecurringWork() ให้กําหนดเวลางานโดยใช้เมธอด enqueueUniquePeriodicWork() ส่งในรูปแบบ enum ของ KEEP สําหรับที่มีอยู่เดิม ส่งใน repeatingRequest เป็นพารามิเตอร์ PeriodicWorkRequest
WorkManager.getInstance().enqueueUniquePeriodicWork(
       RefreshDataWorker.WORK_NAME,
       ExistingPeriodicWorkPolicy.KEEP,
       repeatingRequest)

หากมีงานที่รอดําเนินการ (ยังไม่เสร็จ) ที่มีชื่อเดียวกัน พารามิเตอร์ ExistingPeriodicWorkPolicy.KEEP จะทําให้ WorkManager เก็บงานก่อนหน้าก่อนหน้านี้ไว้และทิ้งคําของานใหม่ไป

  1. ในตอนต้นของคลาส DevByteApplication ให้สร้างออบเจ็กต์ CoroutineScope ส่งใน Dispatchers.Default เป็นพารามิเตอร์เครื่องมือสร้าง
private val applicationScope = CoroutineScope(Dispatchers.Default)
  1. ในชั้นเรียน DevByteApplication ให้เพิ่มเมธอดใหม่ที่ชื่อ delayedInit() เพื่อเริ่มต้นโครูทีน
private fun delayedInit() {
   applicationScope.launch {
   }
}
  1. ในเมธอด delayedInit() ให้เรียก setupRecurringWork()
  2. ย้ายการเริ่มต้น Timber จากเมธอด onCreate() ไปยังเมธอด delayedInit()
private fun delayedInit() {
   applicationScope.launch {
       Timber.plant(Timber.DebugTree())
       setupRecurringWork()
   }
}
  1. ในคลาส DevByteApplication ให้เพิ่มการเรียกไปยังเมธอด delayedInit() ที่ท้ายวิธี onCreate()
override fun onCreate() {
   super.onCreate()
   delayedInit()
}
  1. เปิดแผง Logcat ที่ด้านล่างของหน้าต่าง Android Studio ตัวกรองใน RefreshDataWorker
  2. เรียกใช้แอป WorkManager จะกําหนดเวลาการทํางานที่เกิดซ้ําทันที

    ในแผง Logcat ให้สังเกตคําสั่งบันทึกที่แสดงว่ามีการกําหนดเวลาคําของาน จากนั้นทํางานสําเร็จ
D/RefreshDataWorker: Work request for sync is run
I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

บันทึก WM-WorkerWrapper จะแสดงจากไลบรารี WorkManager ดังนั้นคุณจึงไม่สามารถเปลี่ยนข้อความในบันทึกนี้

ขั้นตอนที่ 3: (ไม่บังคับ) กําหนดเวลา WorkRequest เป็นระยะเวลาขั้นต่ํา

ในขั้นตอนนี้ คุณจะลดช่วงเวลาจาก 1 วันเป็น 15 นาที โดยทําไว้เพื่อดูบันทึกของคําขอทํางานเป็นระยะ

  1. ในคลาส DevByteApplication ให้แสดงความคิดเห็นของคําจํากัดความ repeatingRequest ปัจจุบันในเมธอด setupRecurringWork() เพิ่มคําขอทํางานใหม่โดยตั้งช่วงเวลาซ้ําเป็น 15 นาที
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
//        .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
       .build()
  1. เปิดแผง Logcat ใน Android Studio และกรองบน RefreshDataWorker หากต้องการล้างบันทึกก่อนหน้า ให้คลิกไอคอนล้าง Logcat
  2. เรียกใช้แอป และWorkManagerกําหนดเวลาทํางานที่เกิดซ้ําทันที ในแผงบันทึก ให้สังเกตบันทึกที่ระบบจะเรียกใช้งานทุกๆ 15 นาที รอ 15 นาทีเพื่อดูบันทึกคําขอทํางานอีกชุด คุณสามารถปล่อยให้แอปทํางานหรือปิดแอปก็ได้ ผู้จัดการงานควรยังคงทํางานอยู่

    ให้สังเกตว่าระยะเวลาน้อยกว่า 15 นาที และบางครั้งอาจมากกว่า 15 นาที (เวลาที่แน่นอนจะขึ้นอยู่กับการเพิ่มประสิทธิภาพแบตเตอรี่ของระบบปฏิบัติการ)
12:44:40 D/RefreshDataWorker: Work request for sync is run
12:44:40 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
12:59:24 D/RefreshDataWorker: Work request for sync is run
12:59:24 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:15:03 D/RefreshDataWorker: Work request for sync is run
13:15:03 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:29:22 D/RefreshDataWorker: Work request for sync is run
13:29:22 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:44:26 D/RefreshDataWorker: Work request for sync is run
13:44:26 I/WM-WorkerWrapper: Worker result SUCCESS for Work
 

ยินดีด้วย คุณสร้างผู้ปฏิบัติงานและกําหนดเวลาคําของานด้วย WorkManager แต่ปัญหาก็เกิดขึ้น คุณยังไม่ได้ระบุข้อจํากัดใดๆ WorkManager จะกําหนดเวลางานวันละครั้ง แม้ว่าอุปกรณ์จะมีแบตเตอรี่เหลือน้อย นอนหลับอยู่ หรือไม่ได้เชื่อมต่อเครือข่าย เนื่องจากจะส่งผลต่อแบตเตอรี่และประสิทธิภาพของอุปกรณ์ ซึ่งอาจทําให้ผู้ใช้ได้รับประสบการณ์ที่ไม่ดี

การแก้ไขปัญหาถัดไปโดยการเพิ่มข้อจํากัดนี้

ในงานก่อนหน้า คุณใช้ WorkManager เพื่อกําหนดเวลาคําของาน ในงานนี้ คุณจะได้เพิ่มเกณฑ์ว่าจะดําเนินงานเมื่อใด

เมื่อกําหนด WorkRequest คุณสามารถกําหนดข้อจํากัดที่จะให้ Worker ทํางาน เช่น คุณอาจต้องการระบุว่างานควรทํางานเฉพาะเมื่ออุปกรณ์ไม่มีการใช้งานเท่านั้น หรือเฉพาะเมื่ออุปกรณ์เสียบปลั๊กและเชื่อมต่อ Wi-Fi คุณจะระบุนโยบาย Backoff สําหรับการลองงานอีกครั้งก็ได้ ข้อจํากัดที่รองรับคือวิธีการที่ตั้งไว้ใน Constraints.Builder ดูข้อมูลเพิ่มเติมได้ที่การกําหนดคําขอสําหรับที่ทํางาน

ขั้นตอนที่ 1: เพิ่มออบเจ็กต์ข้อจํากัดและกําหนดข้อจํากัด 1 รายการ

ในขั้นตอนนี้ คุณจะสร้างออบเจ็กต์ Constraints และตั้งค่าข้อจํากัด 1 รายการบนออบเจ็กต์ดังกล่าว ซึ่งเป็นข้อจํากัดประเภทเครือข่าย (คุณจะเห็นบันทึกที่มีข้อจํากัดเพียงจุดเดียวได้ง่ายขึ้น ขั้นตอนต่อไปคือเพิ่มข้อจํากัดอื่นๆ)

  1. ในชั้นเรียน DevByteApplication ต้น setupRecurringWork() ให้กําหนด val ของประเภท Constraints ใช้เมธอด Constraints.Builder()
val constraints = Constraints.Builder()

หากต้องการแก้ไขข้อผิดพลาด ให้นําเข้า androidx.work.Constraints

  1. ใช้วิธีการ setRequiredNetworkType() เพื่อเพิ่มข้อจํากัดประเภทเครือข่ายในออบเจ็กต์ constraints ใช้ EUNMETERED enum เพื่อให้คําของานทํางานเฉพาะเมื่ออุปกรณ์อยู่ในเครือข่ายที่ไม่มีการวัดปริมาณอินเทอร์เน็ต
.setRequiredNetworkType(NetworkType.UNMETERED)
  1. ให้ใช้เมธอด build() เพื่อสร้างข้อจํากัดจากเครื่องมือสร้าง
val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .build()

ตอนนี้คุณต้องตั้งค่าออบเจ็กต์ Constraints ที่สร้างใหม่เป็นคําของาน

  1. ในคลาส DevByteApplication ภายในเมธอด setupRecurringWork() ให้ตั้งค่าออบเจ็กต์ Constraints เป็นคําของานเป็นระยะ repeatingRequest เพิ่มข้อจํากัด setConstraints() เหนือการเรียกใช้เมธอด build() เพื่อกําหนดข้อจํากัด
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
               .setConstraints(constraints)
               .build()

ขั้นตอนที่ 2: เรียกใช้แอปและสังเกตการบันทึก

ในขั้นตอนนี้ คุณจะเรียกใช้แอปและสังเกตเห็นว่าคําขอทํางานที่ถูกจํากัดทํางานอยู่เบื้องหลังเป็นระยะๆ

  1. ถอนการติดตั้งแอปจากอุปกรณ์หรือโปรแกรมจําลองเพื่อยกเลิกงานที่กําหนดเวลาไว้ก่อนหน้านี้
  2. เปิดแผง Logcat ใน Android Studio ในแผง Logcat ให้ล้างบันทึกก่อนหน้าโดยคลิกไอคอนล้าง Logcat ทางด้านซ้าย ตัวกรองใน work
  3. ปิด Wi-Fi ในอุปกรณ์หรือโปรแกรมจําลองเพื่อดูวิธีการทํางานของข้อจํากัด โค้ดปัจจุบันจะกําหนดข้อจํากัดเพียงข้อเดียวซึ่งบ่งชี้ว่าคําขอควรทํางานในเครือข่ายที่ไม่มีการวัดปริมาณอินเทอร์เน็ตเท่านั้น เนื่องจาก Wi-Fi ปิดอยู่ อุปกรณ์จึงไม่ได้เชื่อมต่อกับเครือข่าย มีการวัดปริมาณอินเทอร์เน็ตหรือไม่มีการวัดปริมาณอินเทอร์เน็ต ดังนั้นเพื่อให้เป็นไปตามข้อกําหนดนี้
  4. เรียกใช้แอปและสังเกตแผง Logcat WorkManager จะกําหนดเวลางานในเบื้องหลังทันที เนื่องจากข้อจํากัดด้านเครือข่าย งานจึงไม่ทํางาน
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
  1. เปิด Wi-Fi ในอุปกรณ์หรือโปรแกรมจําลองแล้วดูแผง Logcat ขณะนี้งานในเบื้องหลังที่กําหนดไว้จะทํางานทุก 15 นาที ตราบใดที่เป็นไปตามข้อจํากัดเกี่ยวกับเครือข่าย
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
11:31:47 D/RefreshDataWorker: Work request for sync is run
11:31:47 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]
11:46:45 D/RefreshDataWorker: Work request for sync is run
11:46:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:03:05 D/RefreshDataWorker: Work request for sync is run
12:03:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:16:45 D/RefreshDataWorker: Work request for sync is run
12:16:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:31:45 D/RefreshDataWorker: Work request for sync is run
12:31:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:47:05 D/RefreshDataWorker: Work request for sync is run
12:47:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
13:01:45 D/RefreshDataWorker: Work request for sync is run
13:01:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

ขั้นตอนที่ 3: เพิ่มข้อจํากัด

ในขั้นตอนนี้ คุณจะเพิ่มข้อจํากัดต่อไปนี้ใน PeriodicWorkRequest

  • แบตเตอรี่เหลือน้อย
  • กําลังชาร์จอุปกรณ์
  • อุปกรณ์ไม่มีการใช้งาน ใช้ได้เฉพาะใน API ระดับ 23 (Android M) ขึ้นไป

ใช้สิ่งต่อไปนี้ในชั้นเรียน DevByteApplication

  1. ในชั้นเรียน DevByteApplication ภายในเมธอด setupRecurringWork() ให้ระบุว่าคําของานควรทํางานเฉพาะเมื่อแบตเตอรี่เหลือน้อย เพิ่มข้อจํากัดก่อนการเรียกเมธอด build() และใช้เมธอด setRequiresBatteryNotLow()
.setRequiresBatteryNotLow(true)
  1. อัปเดตคําของานเพื่อให้ทํางานเฉพาะเมื่อชาร์จอุปกรณ์อยู่ เพิ่มข้อจํากัดก่อนการเรียกเมธอด build() และใช้เมธอด setRequiresCharging()
.setRequiresCharging(true)
  1. อัปเดตคําของานเพื่อให้ทํางานเฉพาะเมื่อไม่มีการใช้งานอุปกรณ์เท่านั้น เพิ่มข้อจํากัดก่อนการเรียกเมธอด build() และใช้เมธอด setRequiresDeviceIdle() ข้อจํากัดนี้จะเรียกใช้คําขอทํางานเมื่อผู้ใช้ไม่ได้ใช้อุปกรณ์อยู่เท่านั้น ฟีเจอร์นี้ใช้ได้เฉพาะใน Android 6.0 (Marshmallow) ขึ้นไปเท่านั้น โปรดเพิ่มเงื่อนไขสําหรับ SDK เวอร์ชัน M ขึ้นไป
.apply {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       setRequiresDeviceIdle(true)
   }
}

นี่คือคําจํากัดความที่สมบูรณ์ของออบเจ็กต์ constraints

val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresBatteryNotLow(true)
       .setRequiresCharging(true)
       .apply {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
               setRequiresDeviceIdle(true)
           }
       }
       .build()
  1. ภายในเมธอด setupRecurringWork() ให้เปลี่ยนช่วงเวลาของคําขอกลับไปเป็นวันละครั้ง
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .setConstraints(constraints)
       .build()

นี่คือการใช้งานเมธอด setupRecurringWork() ที่สมบูรณ์ โดยมีบันทึกเพื่อให้คุณติดตามได้เมื่อมีการกําหนดเวลางานตามคําขอ

private fun setupRecurringWork() {

       val constraints = Constraints.Builder()
               .setRequiredNetworkType(NetworkType.UNMETERED)
               .setRequiresBatteryNotLow(true)
               .setRequiresCharging(true)
               .apply {
                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                       setRequiresDeviceIdle(true)
                   }
               }
               .build()
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
               .setConstraints(constraints)
               .build()
       
       Timber.d("Periodic Work request for sync is scheduled")
       WorkManager.getInstance().enqueueUniquePeriodicWork(
               RefreshDataWorker.WORK_NAME,
               ExistingPeriodicWorkPolicy.KEEP,
               repeatingRequest)
   }
  1. หากต้องการนําคําของานที่กําหนดเวลาไว้ก่อนหน้านี้ออก ให้ถอนการติดตั้งแอป DevBytes จากอุปกรณ์หรือโปรแกรมจําลอง
  2. เรียกใช้แอปและ WorkManager จะกําหนดเวลาคําของานทันที คําของานจะทํางานวันละครั้งเมื่อเป็นไปตามข้อจํากัดทั้งหมด
  3. คําของานจะทํางานในเบื้องหลังตราบเท่าที่แอปยังติดตั้งไว้ แม้ว่าแอปไม่ได้ทํางานอยู่ ด้วยเหตุนี้ คุณควรถอนการติดตั้งแอปจากโทรศัพท์

เก่งมาก คุณได้ใช้งานและกําหนดเวลาคําของานที่เหมาะกับแบตเตอรี่สําหรับการดึงข้อมูลวิดีโอล่วงหน้าล่วงหน้าในแอป DevBytes แล้ว WorkManager จะกําหนดเวลาและทํางานโดยเพิ่มประสิทธิภาพทรัพยากรของระบบ ผู้ใช้และแบตเตอรี่จะพอใจมาก

โปรเจ็กต์ Android Studio: DevBytesWorkManager

  • WorkManager API ช่วยให้กําหนดเวลางานแบบเรียลไทม์ที่ยืดเวลาได้และล่าช้าซึ่งจะต้องเรียกใช้อย่างเสถียรได้โดยง่าย
  • แอปในชีวิตจริงส่วนใหญ่จะต้องทํางานในเบื้องหลังที่ยาวนาน หากต้องการกําหนดเวลางานในเบื้องหลังในแบบที่เพิ่มประสิทธิภาพและมีประสิทธิภาพ ให้ใช้ WorkManager
  • คลาสหลักในไลบรารีของ WorkManager คือ Worker, WorkRequest และ WorkManager
  • ชั้นเรียน Worker แสดงถึงหน่วยงาน หากต้องการใช้งานในเบื้องหลัง ให้ขยายคลาส Worker และลบล้างเมธอด doWork()
  • ชั้นเรียน WorkRequest แสดงถึงคําขอให้ดําเนินการ WorkRequest คือคลาสพื้นฐานสําหรับการระบุพารามิเตอร์สําหรับงานที่คุณกําหนดเวลาไว้ใน WorkManager
  • การใช้งาน WorkRequest คลาสมีการทํางานที่เป็นรูปธรรม 2 อย่าง ได้แก่ OneTimeWorkRequest สําหรับงานแบบครั้งเดียว และ PeriodicWorkRequest สําหรับคําของานเป็นระยะ
  • เมื่อกําหนด WorkRequest คุณจะระบุ Constraints เพื่อระบุว่า Worker ควรทํางานเมื่อใด ข้อจํากัดรวมถึงสิ่งต่างๆ เช่น การเสียบปลั๊กอุปกรณ์ อุปกรณ์ไม่มีการใช้งาน หรือการเชื่อมต่อ Wi-Fi
  • หากต้องการเพิ่มข้อจํากัดใน WorkRequest ให้ใช้เมธอดที่ตั้งค่าไว้ในเอกสารประกอบของ Constraints.Builder เช่น หากต้องการระบุว่า WorkRequest ไม่ควรทํางานหากแบตเตอรี่ของอุปกรณ์เหลือน้อย ให้ใช้เมธอดชุด setRequiresBatteryNotLow()
  • หลังจากกําหนด WorkRequest แล้ว ให้มอบหมายงานไปยังระบบ Android หากต้องการดําเนินการ ให้กําหนดเวลางานโดยใช้ WorkManager enqueue วิธี
  • เวลาที่แน่นอนที่ใช้ในการเรียกใช้ Worker จะขึ้นอยู่กับข้อจํากัดที่ใช้ใน WorkRequest และการเพิ่มประสิทธิภาพระบบ WorkManager ออกแบบมาเพื่อให้ลักษณะการทํางานที่ดีที่สุดเท่าที่จะเป็นไปได้ตามข้อจํากัดเหล่านี้

หลักสูตร Udacity:

เอกสารประกอบสําหรับนักพัฒนาซอฟต์แวร์ Android

อื่นๆ:

ส่วนนี้จะอธิบายการบ้านและรายงานสําหรับนักเรียนที่ทํางานผ่าน Codelab นี้ซึ่งเป็นส่วนหนึ่งของหลักสูตรที่นําโดยผู้สอน สิ่งที่ผู้สอนต้องทํามีดังนี้

  • มอบหมายการบ้านหากจําเป็น
  • สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานทําการบ้าน
  • ตัดเกรดการบ้าน

ผู้สอนจะใช้คําแนะนําเหล่านี้เท่าใดก็ได้หรือตามที่ต้องการก็ได้ และสามารถกําหนดให้การบ้านอื่นๆ ที่ตนคิดว่าเหมาะสมได้

หากคุณใช้ Codelab ด้วยตัวเอง ก็ให้ใช้การบ้านเพื่อทดสอบความรู้ของคุณได้

คำถามที่ 1

การใช้คลาส WorkRequest ที่เป็นรูปธรรมมีอะไรบ้าง

OneTimeWorkPeriodicRequest

OneTimeWorkRequest และ PeriodicWorkRequest

OneTimeWorkRequest และ RecurringWorkRequest

OneTimeOffWorkRequest และ RecurringWorkRequest

คำถามที่ 2

WorkManager คลาสใดต่อไปนี้ใช้กําหนดเวลางานในเบื้องหลังใน API 23 ขึ้นไป

▢ เฉพาะ JobScheduler

BroadcastReceiver และ AlarmManager

AlarmManager และ JobScheduler

Scheduler และ BroadcastReceiver

คำถามที่ 3

คุณใช้ API ใดเพื่อเพิ่มข้อจํากัดใน WorkRequest

setConstraints()

addConstraints()

setConstraint()

addConstraintsToWorkRequest()

เริ่มบทเรียนถัดไป: 10.1 รูปแบบและธีม

สําหรับลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ โปรดดูหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals