การทำโปรไฟล์ตามทริกเกอร์

ใน Android 16 (ระดับ API 36) ขึ้นไป ProfilingManager จะช่วยให้คุณบันทึกโปรไฟล์โดยอัตโนมัติเพื่อตอบสนองต่อทริกเกอร์ของระบบ เช่น ข้อผิดพลาดแอปพลิเคชันไม่ตอบสนอง (ANR), ข้อขัดข้องเกี่ยวกับหน่วยความจำไม่เพียงพอ (OutOfMemoryError) หรือเหตุการณ์การเริ่มต้นแอป เนื่องจากคุณไม่สามารถคาดเดาได้ว่าเหตุการณ์เหล่านี้จะเกิดขึ้นเมื่อใดในพื้นที่ทำงาน การเริ่มบันทึกด้วยตนเองล่วงหน้าจึงเป็นไปไม่ได้ในทางปฏิบัติ เมื่อใช้การสร้างโปรไฟล์ตามทริกเกอร์ ระบบจะจัดการการบันทึกและส่งโปรไฟล์ที่ได้ไปยังแอปเพื่อให้คุณทำสิ่งต่อไปนี้ได้

  • แก้ไขข้อบกพร่องเกี่ยวกับปัญหาด้านความเสถียร: วิเคราะห์ ANR และหน่วยความจำรั่วไหลที่คาดเดาไม่ได้
  • เพิ่มประสิทธิภาพเส้นทางของผู้ใช้ที่สำคัญ (CUJ): วิเคราะห์ขั้นตอนต่างๆ เช่น การเริ่มต้นแอป (TRIGGER_TYPE_COLD_START และ TRIGGER_TYPE_APP_FULLY_DRAWN)
  • ทำความเข้าใจพฤติกรรมของผู้ใช้: ตรวจสอบเหตุการณ์ต่างๆ เช่น การออกจากแอปที่ผู้ใช้เริ่ม

ดูรายการทริกเกอร์ที่ใช้ได้ทั้งหมดได้ที่ ProfilingTrigger

ประเภทของทริกเกอร์

ทริกเกอร์จะแตกต่างกันไปตามวิธีที่ระบบบันทึกโปรไฟล์เมื่อเกิดเหตุการณ์

ทริกเกอร์ที่ใช้การติดตามในเบื้องหลัง

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

ทริกเกอร์ที่เริ่มการจับภาพใหม่

ทริกเกอร์ต่อไปนี้ไม่ได้ขึ้นอยู่กับการติดตามในเบื้องหลัง แต่ระบบจะเริ่มบันทึกโปรไฟล์ใหม่เมื่อเกิดเหตุการณ์ขึ้น ดังนี้

ทริกเกอร์ที่ลักษณะการจับภาพแตกต่างกันไปตามความผิดปกติ

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

ตั้งค่าทริกเกอร์

โค้ดต่อไปนี้แสดงวิธีลงทะเบียนสำหรับทริกเกอร์ TRIGGER_TYPE_APP_FULLY_DRAWN และตั้งค่าระยะเวลาพักใช้งาน

Kotlin

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun recordWithTrigger() {
    val profilingManager =
        applicationContext.getSystemService(ProfilingManager::class.java) ?: return

    val triggers = ArrayList<ProfilingTrigger>()

    val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN)
        .setRateLimitingPeriodHours(1)

    triggers.add(triggerBuilder.build())

    val callbackExecutor: Executor = Executors.newSingleThreadExecutor()

    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.resultFilePath
            )
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage
            )
        }
    }

    profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

    Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
        // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
        reportFullyDrawn()
    }
}

Java

public void recordWithTrigger() {
  ProfilingManager profilingManager = getApplicationContext().getSystemService(
      ProfilingManager.class);
  List<ProfilingTrigger> triggers = new ArrayList<>();
  ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder(
      ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
  triggerBuilder.setRateLimitingPeriodHours(1);
  triggers.add(triggerBuilder.build());

  Executor callbackExecutor = Executors.newSingleThreadExecutor();
  Consumer<ProfilingResult> resultCallback =
      new Consumer<ProfilingResult>() {
        @Override
        public void accept(ProfilingResult profilingResult) {
          if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.getResultFilePath());
            setupProfileUploadWorker(profilingResult.getResultFilePath());
          } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode="
                    + profilingResult.getErrorCode()
                    + " errormsg="
                    + profilingResult.getErrorMessage());
          }
        }
      };
  profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

  Choreographer.getInstance().postFrameCallback((f) -> {
    // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
    reportFullyDrawn();
  });
}

โค้ดจะทำตามขั้นตอนต่อไปนี้

  1. รับผู้จัดการ: ดึงข้อมูลบริการ ProfilingManager
  2. กำหนดทริกเกอร์: สร้าง ProfilingTrigger สำหรับ TRIGGER_TYPE_APP_FULLY_DRAWN เหตุการณ์นี้จะเกิดขึ้นเมื่อแอปเรียกใช้ reportFullyDrawn() ระหว่างการเริ่มระบบใหม่ทั้งหมดที่เริ่มต้นโดยกิจกรรมเพื่อรายงานว่าแอปเริ่มต้นระบบเสร็จแล้วและโต้ตอบได้
  3. ตั้งค่าระยะเวลาพักใช้งานทริกเกอร์: ใช้ระยะเวลาพักใช้งาน 1 ชั่วโมงกับทริกเกอร์นี้โดยเฉพาะโดยใช้ setRateLimitingPeriodHours นอกเหนือจากตัวจำกัดอัตราของระบบ ระยะเวลาพักนี้จะป้องกันไม่ให้แอปบันทึกโปรไฟล์การเริ่มต้นทำงานมากกว่า 1 รายการต่อชั่วโมง
  4. ลงทะเบียน Listener: เรียก registerForAllProfilingResults เพื่อกำหนด Callback ที่จัดการผลลัพธ์ Callback นี้จะได้รับเส้นทางของโปรไฟล์ที่บันทึกไว้ผ่าน getResultFilePath()
  5. เพิ่มทริกเกอร์: ลงทะเบียนรายการทริกเกอร์กับ ProfilingManager โดยใช้ addProfilingTriggers
  6. เริ่มเหตุการณ์: โพสต์ Callback เฟรม Choreographer ที่เรียก reportFullyDrawn() เมื่อวาดเฟรมถัดไปแล้ว ซึ่งจะส่งเหตุการณ์ TRIGGER_TYPE_APP_FULLY_DRAWN ไปยังระบบเพื่อทริกเกอร์การรวบรวมโปรไฟล์ โดยสมมติว่ามีการติดตามเบื้องหลังของระบบอยู่และมีโควต้าการจำกัดอัตราคำขอ เพื่อให้ทริกเกอร์นี้เกิดขึ้น โปรดตรวจสอบว่าแอปเรียกใช้ reportFullyDrawn()

วงจรของทริกเกอร์

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

กรณีที่ควรยกเลิกการลงทะเบียนทริกเกอร์

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

ยกเลิกการลงทะเบียนทริกเกอร์ในสถานการณ์ต่อไปนี้

  • การกำหนดเป้าหมายกลุ่มประชากรตามรุ่นหรือการย้อนกลับแฟล็กระยะไกล: คุณอาจสร้างโปรไฟล์กลุ่มในกลุ่มประชากรตามรุ่นโดยใช้แฟล็กการกำหนดค่าระยะไกลหรือการทดสอบ A/B เมื่อใดก็ตามที่อุปกรณ์ออกจากกลุ่มประชากรตามรุ่นที่สุ่มตัวอย่างหรือมีการปิดแฟล็ก ให้ยกเลิกการลงทะเบียนทริกเกอร์
  • การสิ้นสุดแคมเปญการแก้ไขข้อบกพร่อง: เมื่อรวบรวมโปรไฟล์ได้มากพอที่จะวินิจฉัยปัญหาหรือส่งการแก้ไขแล้ว ให้นำทริกเกอร์ออก การยกเลิกการลงทะเบียนทริกเกอร์ที่ไม่จำเป็นจะช่วยประหยัดพื้นที่เก็บข้อมูลของอุปกรณ์และโควต้าตัวจำกัดอัตราของแอปสำหรับคำขอการทำโปรไฟล์อื่นๆ

วงจรของ Callback

โปรไฟล์ที่อิงตามทริกเกอร์จะส่งผลลัพธ์ไปยัง Listener ส่วนกลางที่ลงทะเบียนกับ registerForAllProfilingResults เท่านั้น ซึ่งต่างจากคำขอการทำโปรไฟล์โดยตรง ใน Callback ให้เรียกใช้ ProfilingResult.getTriggerType เพื่อระบุทริกเกอร์ที่เริ่มการบันทึก และเรียกใช้ ProfilingResult.getResultFilePath เพื่อค้นหาไฟล์โปรไฟล์ที่บันทึกไว้ ดูรายละเอียดเกี่ยวกับชื่อไฟล์โปรไฟล์ทริกเกอร์ การดึงไฟล์ด้วย adb และการอัปโหลดผลลัพธ์ในเบื้องหลังได้ที่หัวข้อเรียกข้อมูลและอัปโหลดโปรไฟล์

แม้ว่าทริกเกอร์จะยังคงอยู่ตลอดการรีสตาร์ทแอปและการรีบูตระบบ แต่ Callback ของผลลัพธ์จะไม่คงอยู่ ลงทะเบียน Callback ใน Executor เบื้องหลังหลังจากที่แอปเริ่มต้นเสร็จสมบูรณ์ (เพื่อไม่ให้ Cold Start ช้าลง) เพื่อรับโปรไฟล์ในทั้ง 2 สถานการณ์ต่อไปนี้

  • ขณะที่แอปทำงานอยู่: สำหรับทริกเกอร์ที่ไม่หยุดหรือทำให้แอปขัดข้อง ระบบจะเรียกใช้ Callback ใน Executor ที่ระบุเมื่อทริกเกอร์เกิดขึ้นและบันทึกโปรไฟล์ไว้
  • หลังจากสิ้นสุดการทำงานของแอป: สำหรับทริกเกอร์ที่มักเกี่ยวข้องหรือเกิดขึ้นก่อนการสิ้นสุดการทำงานของแอป (เช่น TRIGGER_TYPE_ANR, TRIGGER_TYPE_OOM หรือ TRIGGER_TYPE_KILL_RECENTS) หรือหากแอปหยุดทำงานก่อนที่การบันทึกจะเสร็จสิ้น ระบบจะบันทึกโปรไฟล์ในเบื้องหลังและจัดคิวผลลัพธ์ไว้สำหรับการรีสตาร์ทแอปและการรีบูตระบบ ระบบจะเก็บผลลัพธ์ที่อยู่ในคิวซึ่งยังไม่ได้ส่งไว้เป็นเวลาสูงสุด 7 วัน และจะส่งผลลัพธ์ดังกล่าวในครั้งถัดไปที่แอปทำงานและเรียกใช้ registerForAllProfilingResults

วิธีการทำงานของการติดตามในเบื้องหลัง

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

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

แผนภาพแสดงวิธีการทำงานของสแนปชอตการติดตามในเบื้องหลัง โดยมีบัฟเฟอร์แบบวงแหวนที่บันทึกข้อมูลก่อนเหตุการณ์ทริกเกอร์
รูปที่ 1 วิธีการทำงานของสแนปชอตการติดตามในเบื้องหลัง

ตั้งค่าระยะเวลาพักเฉพาะทริกเกอร์

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

แก้ไขข้อบกพร่องของทริกเกอร์ในเครื่อง

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

ดูคำสั่ง adb เพื่อบังคับให้มีการติดตามในเบื้องหลัง จำลองทริกเกอร์จากบรรทัดคำสั่ง และตรวจสอบสถานะบริการได้ที่บังคับให้เปิดใช้การติดตามในเบื้องหลังสำหรับการทดสอบทริกเกอร์ในคำสั่งแก้ไขข้อบกพร่องสำหรับการสร้างโปรไฟล์ในเครื่อง