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

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

สถานะแคชและสถานะหยุดทำงานของแอป

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

สถานะแคช

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

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

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

เมื่อออกแบบ 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) ระบบจะบัฟเฟอร์ธุรกรรมไว้จนกว่ากระบวนการระยะไกลจะกลับมาทำงาน หากบัฟเฟอร์ล้น กระบวนการของแอปผู้รับอาจขัดข้อง นอกจากนี้ ธุรกรรมที่บัฟเฟอร์ไว้อาจล้าสมัยเมื่อกระบวนการของแอปกลับมาทำงานและประมวลผลธุรกรรมเหล่านั้น

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

หากต้องการติดตามเวลาที่แอปหยุดทำงานหรือกลับมาทำงาน ให้ใช้ 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++

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

#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 เป็นคลาสตัวช่วยสำหรับการจัดการรายการ Callback IInterface ที่กระบวนการระยะไกลลงทะเบียนไว้ คลาสนี้จะจัดการการแจ้งเตือนการสิ้นสุดของ Binder โดยอัตโนมัติและมีตัวเลือกสำหรับการจัดการการเรียกกลับไปยังแอปที่หยุดทำงาน

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

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

ตัวอย่างต่อไปนี้แสดงวิธีสร้างและใช้อินสแตนซ์ 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 หรือเป็นเดมอนเนทีฟยังสามารถใช้ API ที่อธิบายไว้ก่อนหน้านี้เพื่อติดตามความสำคัญและสถานะหยุดทำงานของกระบวนการของแอปได้ด้วย ดังนี้

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

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

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

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

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

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