จัดการแอปที่แคชไว้และแอปที่หยุดทำงาน

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

สถานะแอปที่แคชและหยุดชั่วคราว

Android จะดูแลแอปในสถานะต่างๆ เพื่อจัดการทรัพยากรระบบ เช่น หน่วยความจำและ CPU

สถานะแคช

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

เมื่อเชื่อมโยงจากกระบวนการแอปหนึ่งไปยังอีกกระบวนการหนึ่ง เช่น การใช้ bindService สถานะกระบวนการของกระบวนการเซิร์ฟเวอร์จะได้รับการยกระดับให้มีความสำคัญอย่างน้อย เท่ากับกระบวนการไคลเอ็นต์ (ยกเว้นเมื่อระบุ Context#BIND_WAIVE_PRIORITY) เช่น หากไคลเอ็นต์ไม่ได้อยู่ในสถานะแคช เซิร์ฟเวอร์ก็จะไม่ใช่เช่นกัน

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

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

หากต้องการติดตามเมื่อแอปเข้าหรือออกจากสถานะแคช ให้ใช้ ActivityManager.addOnUidImportanceListener

Java

// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
    new ActivityManager.OnUidImportanceListener() { ... },
    IMPORTANCE_CACHED);

Kotlin

// in ActivityManager or Context
activityManager.addOnUidImportanceListener({ uid, importance ->
  // ...
}, IMPORTANCE_CACHED)

สถานะถูกระงับ

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

เมื่อกระบวนการส่งธุรกรรม Binder แบบซิงโครนัส (ไม่ใช่ oneway) ไปยังกระบวนการระยะไกลอื่นที่หยุดทำงาน ระบบจะปิดกระบวนการระยะไกลนั้น ซึ่งจะช่วย ป้องกันไม่ให้เธรดการโทรในกระบวนการโทรหยุดทำงานอย่างไม่มีกำหนด ขณะรอให้กระบวนการระยะไกลเลิกหยุดทำงาน ซึ่งอาจทำให้เกิด การขาดแคลนเธรดหรือการหยุดชะงักในแอปการโทร

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

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

หากต้องการติดตามเวลาที่แอปถูกระงับหรือยกเลิกการระงับ ให้ใช้ IBinder.addFrozenStateChangeCallback

Java

// The binder token of the remote process
IBinder binder = service.getBinder();

// Keep track of frozen state
AtomicBoolean remoteFrozen = new AtomicBoolean(false);

// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(
    myExecutor,
    new IBinder.FrozenStateChangeCallback() {
        @Override
        public void onFrozenStateChanged(boolean isFrozen) {
            remoteFrozen.set(isFrozen);
        }
    });

// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
    // dispatch callback to remote process
}

Kotlin

// The binder token of the remote process
val binder: IBinder = service.getBinder()

// Keep track of frozen state
val remoteFrozen = AtomicBoolean(false)

// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(myExecutor) { isFrozen ->
    remoteFrozen.set(isFrozen)
}

// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
    // dispatch callback to remote process
}

C++

สำหรับโค้ดแพลตฟอร์มที่ใช้ API Binder ของ C++ ให้ทำดังนี้

#include <binder/Binder.h>
#include <binder/IBinder.h>

// The binder token of the remote process
android::sp<android::IBinder> binder = service->getBinder();

// Keep track of frozen state
std::atomic<bool> remoteFrozen = false;

// Define a callback class
class MyFrozenStateCallback : public android::IBinder::FrozenStateChangeCallback {
public:
    explicit MyFrozenStateCallback(std::atomic<bool>* frozenState) : mFrozenState(frozenState) {}
    void onFrozenStateChanged(bool isFrozen) override {
        mFrozenState->store(isFrozen);
    }
private:
    std::atomic<bool>* mFrozenState;
};

// Update remoteFrozen when the remote process freezes or unfreezes
if (binder != nullptr) {
    binder->addFrozenStateChangeCallback(android::sp<android::IBinder::FrozenStateChangeCallback>::make(
        new MyFrozenStateCallback(&remoteFrozen)));
}

// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.load()) {
    // dispatch callback to remote process
}

ใช้ RemoteCallbackList

คลาส RemoteCallbackList เป็นตัวช่วยในการจัดการรายการของ IInterface การเรียกกลับที่กระบวนการระยะไกลลงทะเบียน คลาสนี้จะจัดการการแจ้งเตือนการหยุดทำงานของ Binder โดยอัตโนมัติและมีตัวเลือกสำหรับการจัดการการเรียกกลับ ไปยังแอปที่หยุดทำงาน

เมื่อสร้าง RemoteCallbackList คุณสามารถระบุนโยบายผู้รับสายที่หยุดชั่วคราวได้ดังนี้

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

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

Java

RemoteCallbackList<IMyCallbackInterface> callbacks =
        new RemoteCallbackList.Builder<IMyCallbackInterface>(
                        RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
                .setExecutor(myExecutor)
                .build();

// Registering a callback:
callbacks.register(callback);

// Broadcasting to all registered callbacks:
callbacks.broadcast((callback) -> callback.onSomeEvent(eventData));

Kotlin

val callbacks =
    RemoteCallbackList.Builder<IMyCallbackInterface>(
        RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP
    )
        .setExecutor(myExecutor)
        .build()

// Registering a callback:
callbacks.register(callback)

// Broadcasting to all registered callbacks:
callbacks.broadcast { callback -> callback.onSomeEvent(eventData) }

หากคุณใช้ FROZEN_CALLEE_POLICY_DROP ระบบจะเรียกใช้ callback.onSomeEvent() ก็ต่อเมื่อกระบวนการที่โฮสต์การเรียกกลับไม่ได้ถูกระงับ

บริการของระบบและการโต้ตอบกับแอป

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

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

ติดตามสถานะแอปจากบริการของระบบ

บริการของระบบที่ทำงานใน system_server หรือเป็น daemon ดั้งเดิมยังใช้ API ที่อธิบายไว้ก่อนหน้านี้เพื่อติดตามความสำคัญและสถานะหยุดทำงานของกระบวนการแอปได้ด้วย

  • ActivityManager.addOnUidImportanceListener: บริการของระบบสามารถ ลงทะเบียน Listener เพื่อติดตามการเปลี่ยนแปลงความสำคัญของ UID ได้ เมื่อได้รับ Binder Call หรือ Callback จากแอป บริการจะใช้ Binder.getCallingUid() เพื่อรับ UID และเชื่อมโยงกับ สถานะความสำคัญที่ Listener ติดตามได้ ซึ่งจะช่วยให้บริการของระบบทราบว่าแอปโทรอยู่ในสถานะแคชหรือไม่

  • IBinder.addFrozenStateChangeCallback: เมื่อบริการของระบบ ได้รับออบเจ็กต์ Binder จากแอป (เช่น เป็นส่วนหนึ่งของการลงทะเบียน สำหรับการเรียกกลับ) บริการควรลงทะเบียน FrozenStateChangeCallback ในอินสแตนซ์ IBinder นั้นๆ ซึ่งจะแจ้งให้ระบบ บริการทราบโดยตรงเมื่อกระบวนการของแอปที่โฮสต์ Binder นั้นหยุดทำงานหรือกลับมาทำงานอีกครั้ง

คำแนะนำสำหรับบริการของระบบ

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

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

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