การโอนข้อมูลที่เริ่มต้นโดยผู้ใช้

หากต้องการโอนข้อมูลที่อาจใช้เวลานาน คุณสามารถสร้างงาน JobScheduler และระบุว่าเป็นงานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้ (UIDT) งาน UIDT มีไว้สำหรับการโอนข้อมูลระยะยาว ซึ่งเริ่มต้นโดยผู้ใช้อุปกรณ์ เช่น การดาวน์โหลดไฟล์จากเซิร์ฟเวอร์ระยะไกล งาน UIDT เปิดตัวพร้อมกับ Android 14 (API ระดับ 34)

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

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

ตั้งเวลางานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้

หากต้องการเรียกใช้งานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้ ให้ทำดังนี้

  1. ตรวจสอบว่าแอปได้ประกาศ JobService และสิทธิ์ที่เกี่ยวข้อง ในไฟล์ Manifest แล้ว

    <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() {
    
        ....
    
    }
  2. ประกาศสิทธิ์ RUN_USER_INITIATED_JOBS ในไฟล์ Manifest

    <manifest ...>
        <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
        <application ...>
            ...
        </application>
    </manifest>
    
  3. เรียกใช้เมธอด setUserInitiated() เมื่อสร้างออบเจ็กต์ JobInfo (วิธีนี้ใช้ได้ตั้งแต่ 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();
  4. ขณะที่กำลังดำเนินการงาน ให้เรียกใช้ setNotification() ในออบเจ็กต์ JobService การเรียกใช้ 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
        }
    }
  5. อัปเดตการแจ้งเตือนเป็นระยะๆ เพื่อให้ผู้ใช้ทราบสถานะและความคืบหน้าของงาน หากไม่สามารถระบุขนาดการโอนก่อนกำหนดเวลา งาน หรือต้องการอัปเดตขนาดการโอนโดยประมาณ ให้ใช้ API ใหม่ updateEstimatedNetworkBytes() เพื่อ อัปเดตขนาดการโอนหลังจากที่ทราบขนาดแล้ว

คำแนะนำ

หากต้องการเรียกใช้ชื่องาน UIDT อย่างมีประสิทธิภาพ ให้ทำดังนี้

  1. กำหนดข้อจำกัดด้านเครือข่ายและข้อจำกัดการเรียกใช้งานอย่างชัดเจนเพื่อระบุ เวลาที่ควรเรียกใช้งาน

  2. เรียกใช้งานแบบอะซิงโครนัสใน onStartJob() เช่น คุณสามารถทำได้โดยใช้โครูทีน หากไม่เรียกใช้ Task แบบไม่พร้อมกัน งานจะทำงานในเทรดหลักและอาจบล็อกเทรดหลัก ซึ่งอาจทำให้เกิด ANR

  3. หากต้องการหลีกเลี่ยงการเรียกใช้งานนานเกินความจำเป็น ให้เรียกใช้ 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

ทั้งผู้ใช้และระบบสามารถหยุดงานการโอนที่ผู้ใช้เริ่มได้

โดยผู้ใช้จากตัวจัดการงาน

ผู้ใช้สามารถหยุดงานการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้ซึ่งปรากฏในตัวจัดการงานได้

ขณะผู้ใช้กดหยุด ระบบจะทำดังนี้

  • สิ้นสุดกระบวนการของแอปทันที รวมถึงงานหรือบริการอื่นๆ ทั้งหมดที่ทำงานอยู่เบื้องหน้า
  • ไม่เรียกใช้ 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

ดูเพิ่มเติม

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้ได้จากแหล่งข้อมูลเพิ่มเติมต่อไปนี้