اتصال سرویس‌ها و وضعیت فرآیندها

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

وضعیت فرآیند و نمرات OOM

چارچوب اندروید از وضعیت‌های فرآیند (Process States) برای ردیابی اهمیت هر فرآیند در حال اجرا استفاده می‌کند. سپس این وضعیت‌ها توسط OomAdjuster برای اختصاص یک مقدار تنظیم امتیاز OOM ( oom_score_adj ) استفاده می‌شوند که از -1000 تا 1000 متغیر است.

oom_score_adj پایین‌تر به این معنی است که فرآیند از اهمیت بیشتری برخوردار است و احتمال کمتری دارد که توسط Low Memory Killer (LMK) از بین برود.

حالت‌های رایج فرآیند

جدول زیر برخی از رایج‌ترین حالت‌های فرآیند و مقادیر معمول oom_score_adj آنها را نشان می‌دهد. برای مشاهده لیست کامل و به‌روز، به android.app.ActivityManager و com.android.server.am.psc.Constants در کد منبع اندروید مراجعه کنید.

وضعیت فرآیند (مخفف) توضیحات oom_score_adj معمولی
PER (پایدار) فرآیندهای سیستمی که همیشه باید اجرا شوند (مثلاً Telephony). -۸۰۰
بالا فرآیندی که کاربر در حال حاضر با آن در تعامل است. 0
VIS (قابل مشاهده) این فرآیند یک فعالیت قابل مشاهده دارد (مثلاً پشت یک کادر محاوره‌ای شفاف). ۱۰۰
PERC (حساس) فرآیند پس‌زمینه‌ای که کاربر از آن آگاه است (مثلاً پخش موسیقی). ۲۰۰
اف جی اس فرآیندی که میزبان یک سرویس پیش‌زمینه است. ۰ تا ۲۰۰ (متفاوت)
BTOP (بالای مقید) فرآیندی که توسط یک برنامه TOP محدود شده است. ۱۰۰
بی اف جی اس سرویس پیش‌زمینه محدود (معمولاً محدود به سیستم). 0
قبلی (قبلی) آخرین فرآیندی که کاربر قبل از فرآیند فعلی در آن بوده است. ۷۰۰
ذخیره شده برنامه‌های پس‌زمینه که می‌توانند با خیال راحت بسته شوند. ۹۰۰ تا ۹۹۹

تأثیر اتصال سرویس‌ها

وقتی یک فرآیند کلاینت (مثلاً یک برنامه در حالت TOP ) به یک سرویس در یک فرآیند سرور متصل می‌شود، فرآیند سرور اغلب اولویت بالاتری را به ارث می‌برد . این تضمین می‌کند که سرویس تا زمانی که کلاینت به آن نیاز دارد، در دسترس باقی بماند.

نموداری که فرآیند A (TOP) را نشان می‌دهد که bindService() را از طریق system_server فراخوانی می‌کند و فرآیند B را به BTOP ارتقا می‌دهد.

کنترل وراثت با پرچم‌های BIND

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

پرچم‌های کلیدی BIND برای امتیاز OOM

پرچم‌های زیر هنگام مدیریت فشار حافظه در سطح سیستم بیشترین اهمیت را دارند:

  • BIND_AUTO_CREATE : رایج‌ترین پرچم. این پرچم تضمین می‌کند که فرآیند سرویس تا زمانی که اتصال وجود دارد، آغاز شده و فعال می‌ماند. به طور پیش‌فرض، اولویت فرآیند سرور را نیز برای مطابقت با کلاینت افزایش می‌دهد.
  • BIND_NOT_FOREGROUND : از افزایش اولویت زمان‌بندی فرآیند سرویس هدف به اولویت زمان‌بندی پیش‌زمینه (اولویت CPU) جلوگیری می‌کند. با این حال، همچنان اجازه می‌دهد اولویت حافظه ( oom_score_adj ) افزایش یابد. این برای کارهای پس‌زمینه‌ای که نباید با رابط کاربری برای چرخه‌های CPU رقابت کنند، اما همچنان باید از کشته شدن محافظت شوند، مفید است.
  • BIND_WAIVE_PRIORITY : یک پرچم بسیار قوی که به سیستم دستور می‌دهد اولویت زمان‌بندی یا مدیریت حافظه فرآیند هدف را تحت تأثیر قرار ندهد . فرآیند سرویس طوری مدیریت می‌شود که گویی یک فرآیند پس‌زمینه معمولی در لیست LRU است، و آن را حتی در حین اتصال، واجد شرایط برای از بین بردن OOM می‌کند.
  • BIND_ABOVE_CLIENT : نشان می‌دهد که سرویس از خود برنامه‌ی کلاینت مهم‌تر است. وقتی سیستم نیاز به بازیابی حافظه دارد، ترجیح می‌دهد قبل از از بین بردن سرویس متصل، برنامه‌ی کلاینت را از بین ببرد. این دستور از BIND_AUTO_CREATE "قوی‌تر" است زیرا یک لایه‌ی حفاظتی اضافی برای سرویس فراهم می‌کند که هزینه‌ی آن را کلاینت متحمل می‌شود.
  • BIND_NOT_PERCEPTIBLE : اهمیت سرویس هدف را به زیر سطح PERCEPTIBLE کاهش می‌دهد و به سیستم اجازه می‌دهد حافظه خود را بازیابی کند تا جایی برای فرآیندهای حیاتی‌تر و قابل درک توسط کاربر ایجاد کند.

عملی: مشاهده اثرات اتصال

ما از برنامه MemoryLab برای نشان دادن چگونگی تأثیر یک اتصال از یک برنامه TOP بر وضعیت یک فرآیند جداگانه استفاده خواهیم کرد.

۱. MemoryLab را اجرا کنید

دستور زیر برنامه را اجرا می‌کند. پس از باز شدن، مطمئن شوید که برنامه در پیش‌زمینه باقی می‌ماند (هنوز دکمه خانه یا تغییر برنامه را فشار ندهید).

adb shell am start -n com.android.memorylab/.MainActivity

۲. فرآیندها را شناسایی کنید

قبل از اتصال، وضعیت فرآیندها را بررسی کنید. MemoryLab رابط کاربری اصلی خود را در یک فرآیند اجرا می‌کند و یک RemoteService دارد که در یک فرآیند :remote اجرا می‌شود.

adb shell dumpsys activity processes com.android.memorylab

نمونه قطعه خروجی:

  Process OOM control (154 total, non-act at 7, non-svc at 7):
    Proc #0: fg       T/A/TOP  LCMNFUATI  t: 0 13470:com.android.memorylab/u0a417 (top-activity)
        oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
        state: cur=TOP  set=TOP   lastRss=0.00 lastCachedRss=0.00

شما فرآیند اصلی com.android.memorylab در حالت TOP خواهید دید. فرآیند :remote هنوز شروع نشده است.

۳. اتصال تریگر

برای فعال کردن اتصال سرویس، یک broadcast به برنامه ارسال کنید:

adb shell am broadcast -a com.android.memorylab.LEAK_BINDER

۴. حالت بالا را رعایت کنید

دوباره وضعیت فرآیند را بررسی کنید:

adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"

نمونه قطعه خروجی:

  Proc #  1: vis      F/ /BTOP ---NFUATI  t: 0 13560:com.android.memorylab:remote/u0a417 (service)
    com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
    oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
    state: cur=BTOP set=BTOP  lastRss=0.00 lastCachedRss=0.00

فرآیند :remote اکنون در حال اجرا است و در حالت BTOP (Bound TOP) با oom_score_adj برابر با ۱۰۰ قرار دارد. این به طور قابل توجهی محافظت‌شده‌تر از یک سرویس پس‌زمینه معمولی (که در 500 یا بالاتر خواهد بود) است. نماد <=Proc{...} نشان می‌دهد که کدام فرآیند مسئول این افزایش اولویت است.

۵. ارسال به پس‌زمینه

دکمه HOME روی دستگاه را فشار دهید. دوباره وضعیت‌ها را بررسی کنید:

adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"

نمونه قطعه خروجی:

    Proc #  2: prev     b/ /LAST --------I  t: 0 13560:com.android.memorylab:remote/u0a417 (service)
        com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
        oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
        state: cur=LAST set=LAST  lastRss=0.00 lastCachedRss=0.00
    Proc #  1: prev     b/ /LAST --------I  t: 0 13470:com.android.memorylab/u0a417 (previous)
        oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
        state: cur=LAST set=LAST  lastRss=209MB lastCachedRss=0.00

اکنون هر دو فرآیند به حالت اولویت پایین‌تر ( PREV / oom_score_adj 700 ) منتقل شده‌اند، زیرا فرآیند کلاینت دیگر TOP نیست. (نکته: LAST در رونوشت وضعیت به وضعیت داخلی LAST_ACTIVITY اشاره دارد که در خلاصه‌های سطح بالا به PREV نگاشت می‌شود).

تجزیه و تحلیل با procstats

ابزار procstats یک نمای تاریخی از این وضعیت‌ها ارائه می‌دهد.

# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab

نمونه قطعه خروجی:

  *   com.android.memorylab / u0a417 / v37:
      *   Prc com.android.memorylab / u0a417 / v37:
             TOTAL: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
               Top: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
      *   Prc com.android.memorylab:remote / u0a417 / v37:
             TOTAL: 0.19%
           Bnd Top: 0.19%

در اینجا، Bnd Top درصد زمانی را نشان می‌دهد که فرآیند از راه دور صرف اتصال به یک برنامه در حالت TOP می‌کند.

ضبط و تجزیه و تحلیل اتصالات با Perfetto

در حالی که dumpsys یک تصویر لحظه‌ای به شما می‌دهد، Perfetto به شما امکان می‌دهد لحظه دقیق وقوع یک اتصال و نحوه تغییر امتیاز OOM را به صورت بلادرنگ مشاهده کنید.

۱. ثبت ردپا

از پیکربندی‌ای استفاده کنید که شامل linux.process_stats و دسته‌ی am atrace باشد:

adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/service_bindings.perfetto-trace <<EOF
buffers: { size_kb: 65536 }
data_sources: {
    config {
        name: "linux.process_stats"
        process_stats_config { proc_stats_poll_ms: 100 }
    }
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config { ftrace_events: "am/am_proc_bound" }
    }
}
duration_ms: 15000
EOF

۲. پرس‌وجو در مورد انتقال امتیاز OOM

با استفاده از PerfettoSQL، می‌توانید ببینید که چگونه امتیاز OOM فرآیند راه دور نسبت به فرآیند UI تغییر کرده است:

SELECT ts, p.name, value AS oom_score_adj
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.memorylab%'
  AND t.name = 'oom_score_adj'
ORDER BY ts;

۳. رویدادهای الزام‌آور را شناسایی کنید

برای اینکه ببینید دقیقاً چه زمانی یک وابستگی اتصال ایجاد شده و کدام فرآیند آن را آغاز کرده است، از این پرس و جو استفاده کنید:

SELECT
    s.ts,
    p.name AS process_name,
    t.name AS thread_name,
    s.name AS slice_name
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread t USING (utid)
JOIN process p USING (upid)
WHERE s.name LIKE 'bindService:{com.android.memorylab%';

اتصال سیستم به برنامه

خود سیستم اندروید اغلب برای ارائه قابلیت‌های اصلی به سرویس‌های برنامه‌های شخص ثالث متصل می‌شود. اغلب هدف این اتصال‌ها کاهش تأخیر است. با زنده نگه داشتن یک فرآیند و در حافظه، سیستم از سربار پرهزینه "شروع سرد" (بارگذاری APK، مقداردهی اولیه زمان اجرا و ایجاد شیء Application) هنگام وقوع یک تعامل حیاتی با کاربر جلوگیری می‌کند. اتصال‌های دیگری نیز وجود دارند که از شروع سرد مکرر برای برنامه‌هایی که نیاز به مدیریت جریان رویدادهای پس‌زمینه دارند، جلوگیری می‌کنند.

در اینجا چند مثال از دنیای واقعی که می‌توانید در یک دستگاه معمولی مشاهده کنید، آورده شده است:

VoiceInteractor

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

وقتی یک دستیار صوتی فعال می‌شود (مانند عبارت کلیدی «OK Google» در گوشی‌های گوگل پیکسل)، دستیار دیجیتال باید فوراً پاسخ دهد. برای اطمینان از این امر، system_server یک اتصال دائمی به سرویس تعامل صوتی انتخاب شده توسط کاربر برقرار می‌کند.

نموداری که اتصال system_server به فرآیند تعاملی برنامه گوگل را نشان می‌دهد

اگر وضعیت فرآیندها را بررسی کنید (مثلاً با استفاده از dumpsys activity processes )، ممکن است فرآیندی مانند com.google.android.googlequicksearchbox:interactor در وضعیت BFGS (Bound Foreground Service) ببینید که توسط یک اتصال از system_server (UID 1000) فعال نگه داشته شده است.

NotificationListenerService

برای برخی از اتصال‌های سیستم به برنامه، هدف تأخیر نیست، بلکه جلوگیری از شروع‌های سرد مکرر است. NotificationListenerService ، سرویسی که هنگام ارسال یا حذف اعلان‌های جدید، فراخوانی‌هایی را از سیستم دریافت می‌کند، یک مثال بارز است. یک کاربر معمولی تلفن هوشمند ممکن است در طول روز صدها اعلان دریافت کند. اگر سیستم از یک شنونده اعلان جدا شود، احتمالاً فرآیند آن برنامه به حالت ذخیره شده می‌رود و ممکن است توسط LMK از بین برود.

وقتی اعلان بعدی می‌رسد - احتمالاً چند ثانیه بعد - سیستم مجبور می‌شود برای اجرای رویداد، فرآیند برنامه را دوباره از ابتدا شروع کند. این چرخه مداوم توقف و شروع سرد، CPU و باتری بسیار بیشتری نسبت به فعال و غیرفعال نگه داشتن فرآیند در پس‌زمینه مصرف می‌کند.

صفحه «-۱» لانچر (فید خبری)

برنامه‌های مدرن لانچر معمولاً قابلیت‌های اصلی ناوبری (آیکون‌های خانه و ویجت‌ها) را با یک فید خبری که در یکی از صفحات لانچر موجود است و به طور یکپارچه با لانچر UX ادغام شده است، ترکیب می‌کنند. این فید خبری ممکن است توسط برنامه دیگری ارائه شود. به عنوان مثال، در گوگل پیکسل، لانچر با فیدی که توسط برنامه گوگل ارائه می‌شود، ادغام می‌شود.

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

مثال‌های رایج دیگر

  • لانچر (HOME_APP_ADJ) : برنامه لانچر (Home) جایگاه ویژه خود را در لیست اولویت‌ها دارد. اگرچه همیشه به یک سرویس محدود نمی‌شود، اما HOME_APP_ADJ (معمولاً ۶۰۰ ) به آن اختصاص داده می‌شود. سیستم ترجیح می‌دهد لانچر را فعال نگه دارد، زیرا کاربر مرتباً به آن مراجعه می‌کند. در واقع، سیستم ترجیح می‌دهد برنامه‌ای که قبلاً استفاده شده است ( PREV_APP_ADJ = 700 ) را از بین ببرد تا اینکه لانچر را از بین ببرد، زیرا از بین بردن لانچر منجر به تجربه کاربری کند هنگام خروج از هر برنامه‌ای می‌شود زیرا کاربر باید منتظر بماند تا لانچر به طور کامل اجرا شود.
  • ویرایشگر روش ورودی (IME) : وقتی تایپ می‌کنید، سیستم به برنامه صفحه‌کلید انتخابی شما (مثلاً Gboard) متصل می‌شود. این کار باعث می‌شود که حتی اگر صفحه‌کلید موقتاً پنهان شده باشد، پردازش صفحه‌کلید در حالت بالا نگه داشته شود. این تضمین می‌کند که وقتی روی فیلد متنی دیگری ضربه می‌زنید، صفحه‌کلید می‌تواند فوراً دوباره ظاهر شود.
  • پرداخت‌های NFC : وقتی برای پرداخت، گوشی خود را لمس می‌کنید، سیستم به سرویس پرداخت NFC (مثلاً Google Wallet) متصل می‌شود. این تراکنش‌ها اغلب الزامات سختگیرانه‌ای برای دریافت وجه در لحظه از پایانه فروشگاه دارند. اگر برنامه پرداخت مجبور به شروع سرد (Cold Start) شود، تراکنش ممکن است با وقفه مواجه شده و ناموفق باشد.

بده‌بستان‌ها و صخره عملکرد

اگرچه اتصال‌ها برای عملکرد و صحت ضروری هستند، اما به قیمت سلامت حافظه سیستم تمام می‌شوند.

  • کاهش انعطاف‌پذیری : هر فرآیند محدود شده، فرآیندی است که LMK به راحتی نمی‌تواند آن را از بین ببرد. این امر باعث کاهش «ضربه» فرآیندهای ذخیره شده در حافظه پنهان می‌شود که سیستم می‌تواند از آن برای آزاد کردن حافظه تحت فشار استفاده کند.
  • تشدید پرتگاه عملکرد : اگر فرآیندهای زیادی محدود شوند، سیستم ممکن است تقریباً هیچ فرآیند پس‌زمینه قابل حذفی نداشته باشد. وقتی فشار حافظه افزایش می‌یابد، سیستم خیلی سریع‌تر از "پرتگاه عملکرد" ​​سقوط می‌کند، زیرا مجبور می‌شود فرآیندهای مهم‌تر را حذف کند یا حافظه پنهان صفحه را به شدت فشرده کند.

← محلی | ↑ بالا | در کل سیستم →