مدیریت برنامه‌های ذخیره شده و فریز شده

هنگام استفاده از binder برای برقراری ارتباط بین فرآیندها، هنگامی که فرآیند راه دور در حالت cache شده یا freeze شده است، بسیار مراقب باشید. فراخوانی برنامه‌های cache شده یا freeze شده می‌تواند باعث خرابی یا مصرف غیرضروری منابع آنها شود.

وضعیت‌های برنامه ذخیره شده و فریز شده

اندروید برنامه‌ها را در حالت‌های مختلف نگه می‌دارد تا منابع سیستم مانند حافظه و پردازنده را مدیریت کند.

حالت ذخیره شده

وقتی یک برنامه هیچ مؤلفه قابل مشاهده توسط کاربر، مانند فعالیت‌ها یا سرویس‌ها، ندارد، می‌توان آن را به حالت ذخیره‌شده منتقل کرد. برای جزئیات بیشتر به بخش فرآیندها و چرخه حیات برنامه مراجعه کنید. برنامه‌های ذخیره‌شده در حافظه موقت نگهداری می‌شوند تا در صورت نیاز کاربر دوباره به آنها مراجعه کند، اما انتظار نمی‌رود که به طور فعال کار کنند.

هنگام اتصال از یک فرآیند برنامه به فرآیند دیگر، مانند استفاده از bindService ، وضعیت فرآیند سرور حداقل به اندازه فرآیند کلاینت اهمیت پیدا می‌کند (به جز زمانی که Context#BIND_WAIVE_PRIORITY مشخص شده باشد). برای مثال، اگر کلاینت در وضعیت کش شده نباشد، سرور نیز در وضعیت کش شده قرار ندارد.

برعکس، وضعیت یک فرآیند سرور، وضعیت کلاینت‌های آن را تعیین نمی‌کند. بنابراین یک سرور ممکن است اتصالات اتصال‌دهنده‌ای به کلاینت‌ها داشته باشد، که معمولاً به شکل فراخوانی‌های برگشتی است، و در حالی که فرآیند از راه دور در حالت ذخیره شده است، سرور ذخیره نشده است.

هنگام طراحی APIهایی که در آن‌ها فراخوانی‌های برگشتی از یک فرآیند سطح بالا سرچشمه می‌گیرند و به برنامه‌ها تحویل داده می‌شوند، توقف ارسال فراخوانی‌های برگشتی هنگام ورود برنامه به حالت ذخیره‌شده و از سرگیری آن هنگام خروج از این حالت را در نظر بگیرید. این کار از کار غیرضروری در فرآیندهای برنامه ذخیره‌شده جلوگیری می‌کند.

برای ردیابی زمان ورود یا خروج برنامه‌ها به حالت کش‌شده، از ActivityManager.addOnUidImportanceListener استفاده کنید:

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

حالت یخ زده

سیستم می‌تواند یک برنامه ذخیره شده در حافظه پنهان را برای صرفه‌جویی در منابع، مسدود کند . وقتی یک برنامه مسدود می‌شود، زمان CPU آن صفر می‌شود و نمی‌تواند هیچ کاری انجام دهد. برای جزئیات بیشتر، به بخش «فریزر برنامه‌های ذخیره شده» مراجعه کنید.

وقتی یک فرآیند، تراکنش binder همزمان (نه oneway ) را به فرآیند راه دور دیگری که مسدود شده است ارسال می‌کند، سیستم فرآیند راه دور را از بین می‌برد. این کار مانع از آن می‌شود که نخ فراخوانی‌کننده در فرآیند فراخوانی‌کننده در حین انتظار برای رفع انسداد فرآیند راه دور، به طور نامحدود هنگ کند، که می‌تواند باعث قحطی نخ یا بن‌بست در برنامه فراخوانی‌کننده شود.

وقتی یک فرآیند، یک تراکنش اتصال ناهمزمان ( oneway ) را به یک برنامه‌ی قفل‌شده ارسال می‌کند (معمولاً با اعلان یک فراخوانی برگشتی، که معمولاً یک متد oneway است)، تراکنش تا زمانی که فرآیند راه دور قفل‌شده نشود، بافر می‌شود. اگر بافر سرریز شود، فرآیند برنامه‌ی گیرنده می‌تواند از کار بیفتد. علاوه بر این، تراکنش‌های بافر شده ممکن است تا زمانی که فرآیند برنامه قفل‌شده و آنها را پردازش می‌کند، بی‌استفاده شوند.

برای جلوگیری از شلوغ شدن برنامه‌ها با رویدادهای قدیمی یا سرریز شدن بافرهای آنها، باید ارسال فراخوانی‌های برگشتی را تا زمانی که فرآیند برنامه گیرنده متوقف شده است، متوقف کنید.

برای ردیابی زمان فریز شدن یا آزاد شدن برنامه‌ها، از IBinder.addFrozenStateChangeCallback استفاده کنید:

// 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
}

استفاده از RemoteCallbackList

کلاس RemoteCallbackList یک کمک‌کننده برای مدیریت لیست‌های فراخوانی‌های IInterface است که فرآیندهای راه دور ثبت می‌کنند. این کلاس به طور خودکار اعلان‌های مرگ binder را مدیریت می‌کند و گزینه‌هایی برای مدیریت فراخوانی‌های برنامه‌های قفل‌شده ارائه می‌دهد.

هنگام ساخت RemoteCallbackList ، می‌توانید یک سیاست فراخوانی‌شده‌ی ثابت‌شده (freeze callee policy) مشخص کنید:

  • FROZEN_CALLEE_POLICY_DROP : فراخوانی‌های مجدد به برنامه‌های فریز شده به طور بی‌صدا حذف می‌شوند. از این سیاست زمانی استفاده کنید که رویدادهایی که هنگام ذخیره برنامه رخ داده‌اند برای برنامه مهم نیستند، به عنوان مثال، رویدادهای حسگر در زمان واقعی.
  • FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT : اگر چندین فراخوانی برگشتی در حین فریز شدن یک برنامه پخش شوند، تنها جدیدترین فراخوانی در صف قرار می‌گیرد و پس از رفع فریز شدن برنامه، تحویل داده می‌شود. این برای فراخوانی‌های برگشتی مبتنی بر وضعیت مفید است که در آن‌ها فقط جدیدترین به‌روزرسانی وضعیت اهمیت دارد، به عنوان مثال، فراخوانی که برنامه را از حجم رسانه فعلی مطلع می‌کند.
  • FROZEN_CALLEE_POLICY_ENQUEUE_ALL : تمام فراخوانی‌های برگشتی که در حین فریز شدن برنامه پخش می‌شوند، در صف قرار می‌گیرند و پس از رفع فریز شدن برنامه، تحویل داده می‌شوند. در مورد این سیاست محتاط باشید، زیرا اگر تعداد زیادی فراخوانی برگشتی در صف قرار گیرند، می‌تواند منجر به سرریز بافر یا تجمع رویدادهای قدیمی شود.

مثال زیر نحوه ایجاد و استفاده از یک نمونه RemoteCallbackList را نشان می‌دهد که فراخوانی‌های برگشتی را به برنامه‌های قفل‌شده منتقل می‌کند:

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));

اگر از FROZEN_CALLEE_POLICY_DROP استفاده کنید، سیستم فقط در صورتی callback.onSomeEvent() را فراخوانی می‌کند که فرآیند میزبان callback، فریز نشده باشد.

سرویس‌های سیستم و تعاملات برنامه

سرویس‌های سیستم اغلب با استفاده از binder با برنامه‌های مختلف زیادی تعامل دارند. از آنجا که برنامه‌ها می‌توانند وارد حالت‌های cache شده و freeze شده شوند، سرویس‌های سیستم باید توجه ویژه‌ای به مدیریت صحیح این تعاملات داشته باشند تا به حفظ پایداری و عملکرد سیستم کمک کنند.

سرویس‌های سیستمی در حال حاضر نیاز دارند موقعیت‌هایی را که در آن‌ها فرآیندهای برنامه به دلایل مختلف از کار می‌افتند، مدیریت کنند. این شامل متوقف کردن کار از طرف آن‌ها و عدم تلاش برای ادامه ارسال فراخوانی‌ها به فرآیندهای از کار افتاده است. در نظر گرفتن فریز شدن برنامه‌ها، امتدادی از این مسئولیت نظارتی موجود است.

پیگیری وضعیت برنامه از طریق سرویس‌های سیستمی

سرویس‌های سیستمی، که در system_server یا به عنوان سرویس‌های بومی اجرا می‌شوند، می‌توانند از APIهایی که قبلاً توضیح داده شد نیز برای ردیابی اهمیت و وضعیت ثابت فرآیندهای برنامه استفاده کنند:

  • ActivityManager.addOnUidImportanceListener : سرویس‌های سیستم می‌توانند یک شنونده (listener) برای ردیابی تغییرات اهمیت UID ثبت کنند. هنگام دریافت فراخوانی binder یا فراخوانی برگشتی از یک برنامه، سرویس می‌تواند از Binder.getCallingUid() برای دریافت UID و مرتبط کردن آن با وضعیت اهمیت ردیابی شده توسط شنونده استفاده کند. این به سرویس‌های سیستم اجازه می‌دهد تا بدانند که آیا برنامه فراخوانی شده در حالت ذخیره شده (cache) است یا خیر.

  • IBinder.addFrozenStateChangeCallback : وقتی یک سرویس سیستمی یک شیء binder را از یک برنامه دریافت می‌کند (برای مثال، به عنوان بخشی از ثبت برای callbackها)، باید یک FrozenStateChangeCallback روی آن نمونه خاص IBinder ثبت کند. این مستقیماً سرویس سیستم را مطلع می‌کند که چه زمانی فرآیند برنامه‌ای که میزبان آن binder است، مسدود یا آزاد می‌شود.

توصیه‌هایی برای سرویس‌های سیستم

ما توصیه می‌کنیم که تمام سرویس‌های سیستمی که ممکن است با برنامه‌ها تعامل داشته باشند، وضعیت حافظه پنهان و وضعیت قفل‌شده‌ی فرآیندهای برنامه‌ای که با آنها ارتباط برقرار می‌کنند را ردیابی کنند . عدم انجام این کار می‌تواند منجر به موارد زیر شود:

  • مصرف منابع: انجام کار برای برنامه‌هایی که در حافظه پنهان (cache) ذخیره شده‌اند و برای کاربر قابل مشاهده نیستند، می‌تواند منابع سیستم را هدر دهد.
  • خرابی برنامه: فراخوانی‌های همزمان binder به برنامه‌های قفل‌شده باعث خرابی آنها می‌شود. فراخوانی‌های غیرهمزمان binder به برنامه‌های قفل‌شده در صورت سرریز شدن بافر تراکنش غیرهمزمان آنها منجر به خرابی می‌شود.
  • Unexpected app behavior: Unfrozen apps wi immediately receive any buffered asynchronous binder transactions that were sent to them while they were frozen. Apps can spend an indefinite period in the freezer, so buffered transactions can be very stale.

سرویس‌های سیستم اغلب RemoteCallbackList برای مدیریت فراخوانی‌های از راه دور و مدیریت خودکار فرآیندهای مرده استفاده می‌کنند. برای مدیریت برنامه‌های هنگ کرده، با اعمال یک سیاست فراخوانی هنگ کرده، همانطور که در بخش «استفاده از RemoteCallbackList» توضیح داده شده است، میزان استفاده موجود از RemoteCallbackList را گسترش دهید.