หากต้องการโอนข้อมูลที่อาจใช้เวลานาน คุณสามารถสร้าง งาน JobScheduler และระบุว่าเป็นงาน การโอนข้อมูลที่เริ่มต้นโดยผู้ใช้ (UIDT) งาน UIDT มีไว้สำหรับการโอนข้อมูลระยะยาวที่ผู้ใช้อุปกรณ์เป็นผู้เริ่มต้น เช่น การดาวน์โหลดไฟล์จากเซิร์ฟเวอร์ระยะไกล งาน UIDT เปิดตัวพร้อมกับ Android 14 (API ระดับ 34)
ผู้ใช้เป็นผู้เริ่มงานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้ งานเหล่านี้ต้องมีการแจ้งเตือน เริ่มทันที และอาจทำงานได้เป็นระยะเวลานานตามเงื่อนไขของระบบ คุณสามารถเรียกใช้งานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้หลายงานพร้อมกันได้
คุณต้องกำหนดเวลาให้งานที่เริ่มต้นโดยผู้ใช้ขณะที่แอปพลิเคชันปรากฏต่อผู้ใช้ (หรืออยู่ในเงื่อนไขที่อนุญาต) หลังจากเป็นไปตามข้อจำกัดทั้งหมดแล้ว ระบบปฏิบัติการจะดำเนินการงานที่เริ่มต้นโดยผู้ใช้ได้ โดยขึ้นอยู่กับข้อจำกัดด้านความสมบูรณ์ของระบบ นอกจากนี้ ระบบยังอาจใช้ขนาดเพย์โหลดโดยประมาณที่ระบุเพื่อกำหนดระยะเวลาที่งานจะทำงาน
ตั้งเวลางานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้
如需运行用户发起的数据传输作业,请执行以下操作:
确保您的应用已在其清单中声明
JobService和关联的 权限:<service android:name="com.example.app.CustomTransferService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"> ... </service>此外,还要为数据传输定义
JobService的具体子类:Kotlin
class CustomTransferService : JobService() { ... }
Java
class CustomTransferService extends JobService() { .... }
在清单中声明
RUN_USER_INITIATED_JOBS权限:<manifest ...> <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" /> <application ...> ... </application> </manifest>构建
JobInfo对象时,调用setUserInitiated()方法。(此方法从 Android 14 开始提供。) 我们还建议您在创建作业时通过调用setEstimatedNetworkBytes()提供载荷大小 估算值 。Kotlin
val networkRequestBuilder = NetworkRequest.Builder() // Add or remove capabilities based on your requirements. // For example, this code specifies that the job won't run // unless there's a connection to the internet (not just a local // network), and the connection doesn't charge per-byte. .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_METERED) .build() val jobInfo = JobInfo.Builder(jobId, ComponentName(mContext, CustomTransferService::class.java)) // ... .setUserInitiated(true) .setRequiredNetwork(networkRequestBuilder) // Provide your estimate of the network traffic here .setEstimatedNetworkBytes(1024 * 1024 * 1024, 1024 * 1024 * 1024) // ... .build()
Java
NetworkRequest networkRequest = new NetworkRequest.Builder() // Add or remove capabilities based on your requirements. // For example, this code specifies that the job won't run // unless there's a connection to the internet (not just a local // network), and the connection doesn't charge per-byte. .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_METERED) .build(); JobInfo jobInfo = JobInfo.Builder(jobId, new ComponentName(mContext, CustomTransferService.class)) // ... .setUserInitiated(true) .setRequiredNetwork(networkRequest) // Provide your estimate of the network traffic here .setEstimatedNetworkBytes(1024 * 1024 * 1024, 1024 * 1024 * 1024) // ... .build();
执行作业时,对
JobService对象调用setNotification()。调用setNotification()会在任务管理器和状态栏通知区域中告知用户作业正在运行。执行完成后,调用
jobFinished()以向系统表明作业已完成,或者应重新调度作业。Kotlin
class CustomTransferService: JobService() { private val scope = CoroutineScope(Dispatchers.IO) @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) override fun onStartJob(params: JobParameters): Boolean { val notification = Notification.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) .setContentTitle("My user-initiated data transfer job") .setSmallIcon(android.R.mipmap.myicon) .setContentText("Job is running") .build() setNotification(params, notification.id, notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH) // Execute the work associated with this job asynchronously. scope.launch { doDownload(params) } return true } private suspend fun doDownload(params: JobParameters) { // Run the relevant async download task, then call // jobFinished once the task is completed. jobFinished(params, false) } // Called when the system stops the job. override fun onStopJob(params: JobParameters?): Boolean { // Asynchronously record job-related data, such as the // stop reason. return true // or return false if job should end entirely } }
Java
class CustomTransferService extends JobService{ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @Override public boolean onStartJob(JobParameters params) { Notification notification = Notification.Builder(getBaseContext(), NOTIFICATION_CHANNEL_ID) .setContentTitle("My user-initiated data transfer job") .setSmallIcon(android.R.mipmap.myicon) .setContentText("Job is running") .build(); setNotification(params, notification.id, notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH) // Execute the work associated with this job asynchronously. new Thread(() -> doDownload(params)).start(); return true; } private void doDownload(JobParameters params) { // Run the relevant async download task, then call // jobFinished once the task is completed. jobFinished(params, false); } // Called when the system stops the job. @Override public boolean onStopJob(JobParameters params) { // Asynchronously record job-related data, such as the // stop reason. return true; // or return false if job should end entirely } }
定期更新通知,让用户了解作业的状态和进度。如果在安排作业之前无法确定传输大小,或者需要更新估算的传输大小,请在知道传输大小之后使用新的 API
updateEstimatedNetworkBytes()更新传输大小。
建议
如需有效地运行 UIDT 作业,请执行以下操作:
明确定义网络限制和作业执行限制,以指定应何时执行作业。
在
onStartJob()中异步执行任务;例如,您可以使用 协程 来执行此操作。如果您不异步运行任务,则工作会在主线程上运行,并且可能会阻塞主线程,从而导致 ANR。为避免作业运行时间超出必要时长,请在传输完成时(无论成功还是失败)调用
jobFinished()。这样,作业就不会运行超出必要时长。如需了解作业停止的原因,请实现onStopJob()回调方法并调用JobParameters.getStopReason()。
ความเข้ากันได้แบบย้อนหลัง
ปัจจุบันยังไม่มีไลบรารี Jetpack ที่รองรับงาน UIDT ด้วยเหตุนี้ เราจึงขอแนะนำให้คุณควบคุมการเปลี่ยนแปลงด้วยโค้ดที่ยืนยันว่าคุณกำลังใช้ Android 14 ขึ้นไป ใน Android เวอร์ชันที่ต่ำกว่า คุณ สามารถใช้ การใช้งานบริการที่ทำงานอยู่เบื้องหน้าของ WorkManager เป็น แนวทางสำรองได้
ตัวอย่างโค้ดที่ตรวจสอบเวอร์ชันระบบที่เหมาะสมมีดังนี้
Kotlin
fun beginTask() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { scheduleDownloadFGSWorker(context) } else { scheduleDownloadUIDTJob(context) } } private fun scheduleDownloadUIDTJob(context: Context) { // build jobInfo val jobScheduler: JobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler jobScheduler.schedule(jobInfo) } private fun scheduleDownloadFGSWorker(context: Context) { val myWorkRequest = OneTimeWorkRequest.from(DownloadWorker::class.java) WorkManager.getInstance(context).enqueue(myWorkRequest) }
Java
public void beginTask() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { scheduleDownloadFGSWorker(context); } else { scheduleDownloadUIDTJob(context); } } private void scheduleDownloadUIDTJob(Context context) { // build jobInfo JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); jobScheduler.schedule(jobInfo); } private void scheduleDownloadFGSWorker(Context context) { OneTimeWorkRequest myWorkRequest = OneTimeWorkRequest.from(DownloadWorker.class); WorkManager.getInstance(context).enqueue(myWorkRequest) }
หยุดงาน UIDT
Both the user and the system can stop user-initiated transfer jobs.
โดยผู้ใช้จากตัวจัดการงาน
ผู้ใช้สามารถหยุดงานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้ซึ่งปรากฏในตัวจัดการงานได้
ขณะผู้ใช้กดหยุด ระบบจะทำดังนี้
- สิ้นสุดกระบวนการของแอปทันที รวมถึงงานหรือบริการอื่นๆ ทั้งหมดที่ทำงานอยู่เบื้องหน้า
- ไม่เรียกใช้
onStopJob()สำหรับงานที่ทำงานอยู่ - ป้องกันไม่ให้กำหนดเวลางานซึ่งผู้ใช้มองเห็นใหม่
ด้วยเหตุนี้ เราจึงขอแนะนำให้ระบุการควบคุมในการแจ้งเตือนที่โพสต์สำหรับงานเพื่อให้หยุดงานและกำหนดเวลาใหม่ได้อย่างราบรื่น
โปรดทราบว่าในบางกรณี ปุ่มหยุดจะไม่ปรากฏข้างงานใน Task Manager หรืองานจะไม่แสดงใน Task Manager เลย
โดยระบบ
งานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้จะไม่ได้รับผลกระทบจากโควต้า App Standby Buckets ซึ่งแตกต่างจากงานปกติ อย่างไรก็ตาม ระบบจะยังคงหยุดงานหากมีเงื่อนไขต่อไปนี้เกิดขึ้น
- ไม่เป็นไปตามข้อจำกัดที่นักพัฒนาแอปกำหนดอีกต่อไป
- ระบบพิจารณาว่างานทำงานนานเกินความจำเป็นในการ ดำเนินงานการโอนข้อมูลให้เสร็จสมบูรณ์
- ระบบต้องให้ความสำคัญกับสถานะของระบบและหยุดงานเนื่องจากสถานะความร้อนเพิ่มขึ้น
- ระบบจะปิดกระบวนการของแอปเนื่องจากหน่วยความจำของอุปกรณ์เหลือน้อย
เมื่อระบบหยุดงานเนื่องจากเหตุผลอื่นๆ นอกเหนือจากหน่วยความจำของอุปกรณ์เหลือน้อย ระบบจะเรียกใช้ onStopJob() และระบบจะลองทำงานอีกครั้งในเวลาที่ระบบเห็นว่าเหมาะสมที่สุด ตรวจสอบว่าแอปสามารถรักษาสถานะการโอนข้อมูลได้แม้ว่าจะไม่ได้เรียกใช้ onStopJob() และแอปสามารถกู้คืนสถานะนี้ได้เมื่อมีการเรียกใช้ onStartJob() อีกครั้ง
เงื่อนไขที่อนุญาตสำหรับการตั้งเวลางานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้
แอปจะเริ่มงานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้ได้ก็ต่อเมื่อแอปอยู่ในกรอบเวลาที่เห็นได้ หรือเมื่อเป็นไปตามเงื่อนไขบางอย่างต่อไปนี้
- หากแอปเปิดกิจกรรมจากเบื้องหลังได้ ก็จะเปิดงานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้จากเบื้องหลังได้เช่นกัน
- หากแอปมีกิจกรรมในกองซ้อนด้านหลังของงานที่มีอยู่บนหน้าจอล่าสุด ก็ไม่ได้หมายความว่างานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้จะทำงานได้
หากกำหนดเวลาให้งานทำงานในช่วงเวลาที่ไม่เป็นไปตามเงื่อนไขที่จำเป็น งานจะดำเนินการไม่สำเร็จและแสดงรหัสข้อผิดพลาด RESULT_FAILURE
ข้อจํากัดที่อนุญาตสําหรับงานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้
Android มีความสามารถในการกำหนดข้อจำกัดให้กับประเภทงานแต่ละประเภทเพื่อรองรับงานที่ทำงานในจุดที่เหมาะสมที่สุด ข้อจำกัดเหล่านี้พร้อมใช้งานตั้งแต่ Android 13 เป็นต้นไป
หมายเหตุ: ตารางต่อไปนี้จะเปรียบเทียบเฉพาะข้อจํากัดที่แตกต่างกันระหว่างประเภทงานแต่ละประเภท ดูข้อจำกัดทั้งหมดได้ที่หน้าสำหรับนักพัฒนาซอฟต์แวร์ของ JobScheduler หรือข้อจำกัดของงาน
ตารางต่อไปนี้แสดงประเภทงานต่างๆ ที่รองรับข้อจำกัดของงานที่กำหนด รวมถึงชุดข้อจำกัดของงานที่ WorkManager รองรับ ใช้แถบค้นหาก่อนตารางเพื่อกรองตารางตามชื่อของเมธอดข้อจำกัดของงาน
ข้อจำกัดที่อนุญาตสำหรับงานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้มีดังนี้
setBackoffCriteria(JobInfo.BACKOFF_POLICY_EXPONENTIAL)setClipData()setEstimatedNetworkBytes()setMinimumNetworkChunkBytes()setPersisted()setNamespace()setRequiredNetwork()setRequiredNetworkType()setRequiresBatteryNotLow()setRequiresCharging()setRequiresStorageNotLow()
การทดสอบ
รายการต่อไปนี้แสดงขั้นตอนการทดสอบงานของแอปด้วยตนเอง
- หากต้องการรับรหัสงาน ให้รับค่าที่กำหนดไว้ในงานที่กำลังสร้าง
หากต้องการเรียกใช้งานทันที หรือหากต้องการลองงานที่หยุดไปแล้วอีกครั้ง ให้เรียกใช้คำสั่งต่อไปนี้ ในหน้าต่างเทอร์มินัล:
adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
เพื่อจำลองการบังคับให้ระบบหยุดงาน (เนื่องจากการทำงานของระบบหรือ เงื่อนไขนอกโควต้า) ให้เรียกใช้คำสั่งต่อไปนี้ในหน้าต่างเทอร์มินัล
adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID
ดูเพิ่มเติม
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้ได้จากแหล่งข้อมูลเพิ่มเติมต่อไปนี้
- กรณีศึกษาเกี่ยวกับการผสานรวม UIDT: Google Maps ปรับปรุงความน่าเชื่อถือในการดาวน์โหลดได้ 10% โดยใช้ User Initiated Data Transfer API