একটি লিন প্রসেসের মাধ্যমে ব্যাকগ্রাউন্ড কাজ চালু রাখুন

অনেক অ্যাপের UI আর দেখা না গেলেও সেগুলি বৈধ কাজ করে, যেমন বড় অ্যাসেট প্যাক ডাউনলোড করা, লোকাল ডেটাবেস সিঙ্ক করা, অডিও স্ট্রিম করা, ব্যায়াম ওয়ার্কআউট রেকর্ড করা বা কানেক্ট করা ডিভাইসের সেশন বজায় রাখা। যখন এই ব্যাকগ্রাউন্ড কাজ আপনার ফোরগ্রাউন্ড UI এবং গ্রাফিক্স ইঞ্জিনের মতো একই OS প্রসেসের মধ্যে চলে, তখন পুরো প্রসেসটি UI-এর বরাদ্দ করা সমস্ত মেমরির সাথে একটি উন্নত গুরুত্ব স্তরে (0 এবং 250-এর মধ্যে oom_score_adj) থাকে।

এই পৃষ্ঠা থেকে জানা যায় যে কেন এটি আপনার নিজের অ্যাপের পাশাপাশি ডিভাইসের বাকি অংশের ক্ষতি করে, কীভাবে ব্যাকগ্রাউন্ডের কাজকে android:process ব্যবহার করে একটি ডেডিকেটেড লাইটওয়েট প্রসেসে সরানো যায় এবং কীভাবে adb shell এবং Perfetto ব্যবহার করে মেমরি সেভিংস যাচাই করা যায়।

সিঙ্গেল-প্রসেস কাজ কেন UI মেমরি ধরে রাখে

Android, OS প্রসেসের (pid) গ্র্যানুলারিটিতে মেমরি ও প্রসেস সমাপ্তি ম্যানেজ করে, আলাদা কম্পোনেন্ট (Activity, Service বা BroadcastReceiver) নয়। অ্যাক্টিভিটি ম্যানেজার (OomAdjuster) প্রতিটি প্রসেসকে একটি oom_score_adj ভ্যালু অ্যাসাইন করে যা সেই প্রসেসে সবচেয়ে গুরুত্বপূর্ণ অ্যাক্টিভ কম্পোনেন্ট বা ইনকামিং বাইন্ডিং দেখায়:

  • ব্যবহারকারী আপনার Activity-এর সাথে ইন্টার‍্যাক্ট করলে, প্রসেসটি TOP স্টেটে (oom_score_adj = 0) রান করে।
  • ব্যবহারকারী অন্য কোথাও নেভিগেট করলে এবং আপনার অ্যাপের কোনও অ্যাক্টিভ ব্যাকগ্রাউন্ড কম্পোনেন্ট না থাকলে, প্রসেসটি PREV (oom_score_adj = 700) বা CACHED (oom_score_adj >= 900) বাকেটে চলে যায়। ক্যাশে করা বাকেটে, প্ল্যাটফর্ম cgroup v2 ফ্রিজারের মাধ্যমে প্রসেস ফ্রিজ করে, CachedAppOptimizer-এর মাধ্যমে এর মেমরি কম্প্যাক্ট করে এবং মেমরির উপর চাপ থাকলে lmkd-কে প্রথমে প্রসেস বন্ধ করতে দেয়।
  • তবে, একই প্রসেস যদি কোনও ফোরগ্রাউন্ড সার্ভিস (যেমন dataSync, mediaPlayback, connectedDevice বা location ধরনের, oom_score_adj = 200-এ) রান করে অথবা এমন কোনও সার্ভিস হোস্ট করে যার সাথে সিস্টেম বা অন্য কোনও গুরুত্বপূর্ণ ক্লায়েন্ট যুক্ত থাকে, তাহলে Activity বন্ধ হয়ে যাওয়ার পরেও সম্পূর্ণ প্রসেসটি সেই উন্নত প্রায়োরিটিতেই থাকে।
সিঙ্গেল-প্রসেস কো-লোকেশন, যেখানে UI মেমরি oom_score_adj 200-এ পিন করা থাকে,
       তার সাথে স্প্লিট-প্রসেস আইসোলেশনের তুলনা, যেখানে শুধু ওয়ার্কারকে oom_score_adj 200-এ রাখা হয়
       এবং UI প্রসেস ক্যাশেতে ড্রপ করা হয়।
ছবি ১. ব্যাকগ্রাউন্ডে কাজ করার সময় স্প্লিট-প্রসেস আইসোলেশনের সাথে সিঙ্গেল-প্রসেস কো-লোকেশন তুলনা করা হয়েছে।

যখন একটি ভারী UI প্রসেস oom_score_adj = 200-এ থাকে, তখন মেমরির বেশ কয়েকটি বড় বিভাগ রেসিডেন্ট থাকে:

  • Activity এবং ভিউ হায়ারার্কি বন্ধ করা হয়েছে: Activity স্পষ্টভাবে শেষ না করা পর্যন্ত, ব্যবহারকারী অন্য অ্যাপে থাকাকালীন Java হিপে এর Window, ভিউ ট্রি, লেআউট স্ট্রাকচার এবং ভিউ স্টেট অ্যাক্সেসযোগ্য থাকে।
  • নেটিভ গ্রাফিক্স কনটেক্সট ও GPU অ্যালোকেশন: স্ট্যান্ডার্ড HardwareRenderer তার GPU কনটেক্সট ও ক্যাশে রেখে দেয়, যেখানে বন্ধ হয়ে যাওয়া Activity এমন একটি প্রসেসে থাকে যা ক্যাশে করা হয় না। যেসব অ্যাপ ও গেম ইঞ্জিন নিজেদের Vulkan (VkDevice, VkSwapchainKHR, ডেসক্রিপ্টর পুল, পাইপলাইন ক্যাশে) বা OpenGL ES (EGLContext, টেক্সচার, ভার্টেক্স বাফার) কন্টেক্সট তৈরি করে, সেগুলিও নিজেদের ড্রাইভার স্ট্রাকচার, শেডার প্রোগ্রাম ও গ্রাফিক্স বাফার ধরে রাখে, যদি না ইঞ্জিন বন্ধ হওয়ার সময় গ্রাফিক্স ডিভাইসকে স্পষ্টভাবে বন্ধ করে দেয়।
  • হিপ ফ্র্যাগমেন্টেশন ও রানটাইম ওভারহেড: কোনও অ্যাপ onTrimMemory-এ ক্যাশে রিলিজ করলেও, আংশিকভাবে পপুলেট করা নেটিভ অ্যালোকেটর পৃষ্ঠা, ডার্টি Zygote কপি-অন-রাইট পৃষ্ঠা, লোড করা শেয়ার করা লাইব্রেরি (.so) এবং রানটাইম মেটাডেটা প্রসেসের ব্যক্তিগত মেমরিতে থেকে যায়। প্রসেসটি ক্যাশে করা থ্রেশহোল্ডের (oom_score_adj < 900) উপরে থাকার কারণে, প্ল্যাটফর্ম এটিকে ফ্রিজ করে না বা এতে ক্যাশে করা-অ্যাপ কম্প্যাকশন রান করে না। বাউন্ড পরিষেবার ক্ষেত্রে এই প্রভাব আরও ভালভাবে বুঝতে, মেমরি-ইনটেনসিভ UI-এর সাথে বাউন্ড পরিষেবার কো-লোকেশন দেখুন।

ব্যাকগ্রাউন্ডে চলা হালকা প্রসেস কীভাবে আপনার অ্যাপকে সুরক্ষিত রাখে

ব্যাকগ্রাউন্ড স্টেটে ফোরগ্রাউন্ড-সাইজ মেমরি হোল্ড করলে আপনার ব্যাকগ্রাউন্ডের কাজ ঝুঁকিতে পড়ে:

  • ব্যাকগ্রাউন্ড মেমরির সীমা ফোরগ্রাউন্ড মেমরির সীমার চেয়ে কম। Android 17 ও তার পরবর্তী ভার্সনে, মেমরি লিমিটার আপনার অ্যাপে আরও কম দেখা যায় না এমন মেমরি সীমা প্রয়োগ করে, যখন এর কোনও প্রসেস দৃশ্যমান UI দেখায় না, যার মধ্যে ফোরগ্রাউন্ড সার্ভিস চলাকালীনও অন্তর্ভুক্ত। এই সীমা আপনার অ্যাপের সব প্রসেস একসাথে কভার করে। যে প্রসেস আপনার ফোরগ্রাউন্ড সার্ভিস চালায়, সেটি যদি এখনও UI ও গ্রাফিক মেমরি ধরে রাখে, তাহলে সেই মেমরি ছোট সীমার মধ্যে গণনা করা হয়। আপনার অ্যাপ সীমা অতিক্রম করলে, কার্নেল এর পৃষ্ঠাগুলি রিক্লেম করার সময় এটিকে থ্রটল করে, যা ডাউনলোড, সিঙ্ক বা প্লেব্যাককে ধীর করে দেয়। সোয়াপ শেষ হয়ে যাওয়ার পরেও আপনার অ্যাপ যদি অ্যালোকেট করতে থাকে, তাহলে সিস্টেম এটি বন্ধ করে দেয়। আপনার অ্যাপ প্রভাবিত হয়েছে কিনা তা চেক করতে, অ্যাপ মেমরি সীমা দেখুন।
  • মেমরি কম থাকলে বড় প্রসেসগুলি প্রথমে বন্ধ করা হয়। lmkd-কে মেমরি খালি করতে হবে এবং গুরুত্বপূর্ণ বাকেটে (ro.lmk.kill_heaviest_task) সবচেয়ে ভারী প্রসেস বেছে নেওয়ার জন্য কনফিগার করা হয়েছে, তাহলে একই গুরুত্বের ছোট প্রসেসের আগে ফোরগ্রাউন্ড সার্ভিস প্রসেস বন্ধ করা হয়, যা শত শত এমবি UI মেমরিও ধরে রাখে এবং এর সাথে আপনার ব্যাকগ্রাউন্ডের কাজ বন্ধ হয়ে যায়। kswapd ও lmkd ইন্টার‍্যাকশন দেখুন।
  • ক্যাশে করা UI স্টেট ফ্রিজ, কম্প্যাক্ট এবং ফ্ল্যাশে আবার লেখা যেতে পারে। আপনার ফোরগ্রাউন্ড UI স্টেটকে ক্যাশে করা প্রসেসে সরানোর অনুমতি দিলে, OS প্রসেসটি ফ্রিজ করতে, zRAM-এ এর মেমরি কম্প্যাক্ট করতে এবং এর কোল্ড পেজগুলি ফ্ল্যাশ স্টোরেজে ফিরিয়ে আনতে পারে। আপনার UI স্টেট অক্ষত থাকার সময় এর রেসিডেন্ট মেমরি ফুটপ্রিন্ট সঙ্কুচিত হয়, যা আপনার ব্যাকগ্রাউন্ডের কাজ এবং আপনার ক্যাশে করা UI উভয়কেই ব্যবহারকারী ফিরে না আসা পর্যন্ত টিকে থাকার সেরা সুযোগ দেয়।
  • প্রসেস-প্রতি মেমরি বাজেট ব্যবহারিক হয়ে ওঠে। যে প্রসেস শুধুমাত্র ব্যাকগ্রাউন্ডে কাজ করে, তার ওয়ার্কিং সেট ছোট ও অনুমানযোগ্য হয়, তাই আপনি UI-কে সীমাবদ্ধ না করেই এটিকে একটি কঠোর মেমরি বাজেট দিতে পারেন।

দ্বিতীয় প্রসেস রান করলে মনে হতে পারে যে এটি আপনার অ্যাপের মোট মেমরি ফুটপ্রিন্ট বাড়িয়ে দিচ্ছে, কারণ দ্বিতীয় রানটাইমের জন্য সামান্য বেসলাইন খরচ হয়। এই তুলনা তখনই প্রাসঙ্গিক হয় যখন কোনও একটি প্রসেস ব্যাকগ্রাউন্ডে কাজ করার সময় তার সমস্ত UI এবং গ্রাফিক্স স্টেট সম্পূর্ণভাবে মুক্ত করতে পারে, যা অ্যাপ এবং UI টুলকিটগুলি বাস্তবে নির্ভরযোগ্যভাবে করতে পারে না। OS-কে ফ্রিজ, কম্প্যাক্ট এবং আলাদা ক্যাশে করা UI প্রসেস রাইট ব্যাক করতে দিলে, সাধারণত অনেক কম ফিজিক্যাল মেমরি ব্যবহার হয়, আপনার অ্যাপকে 'দেখা যায় না' এমন সীমার মধ্যে রাখে এবং আপনার অ্যাপ ও ডিভাইস জুড়ে কম-মেমরি সংক্রান্ত সমস্যা কমায়।

নির্দিষ্ট প্রসেসে ব্যাকগ্রাউন্ডের কাজ আলাদা করুন

ব্যাকগ্রাউন্ডে কাজ করার সময় UI ও গ্রাফিক্স রেসিডেন্সি বাদ দিতে, আপনার UI ও গ্রাফিক্স ইঞ্জিন মূল প্রসেসে রেখে, দীর্ঘ সময় ধরে চলা ব্যাকগ্রাউন্ড কম্পোনেন্ট একটি নির্দিষ্ট লাইটওয়েট প্রসেসে চালান:

প্রক্রিয়া করুন ম্যানিফেস্ট ঘোষণা হোস্ট করা কম্পোনেন্ট ব্যাকগ্রাউন্ডে কাজ চলাকালীন ব্যবহারকারী UI থেকে বেরিয়ে গেলে যে অবস্থা হয়
প্রধান UI প্রসেস ডিফল্ট (com.example.app) Activity, UI ভিউ, ইমেজ ক্যাশে, Vulkan বা OpenGL ES রেন্ডারার, অডিও ও সিন ইঞ্জিন PREV এবং তারপর CACHED-এ ড্রপ করে (oom_score_adj >= 900)। ফ্রোজেন, কম্প্যাক্ট করা এবং ফ্ল্যাশে আবার লেখা।
ব্যাকগ্রাউন্ড ওয়ার্কার প্রসেস android:process=":bg" ফোরগ্রাউন্ড Service, ব্যাকগ্রাউন্ড অ্যাসেট ডাউনলোডার, সিঙ্ক ওয়ার্কার বা কানেক্ট করা ডিভাইস ম্যানেজার FGS (oom_score_adj = 200)-এ রান করে, শুধুমাত্র ওয়ার্কারের নিজস্ব হিপ ও কোড সহ, কোনও UI ভিউ বা GPU কনটেক্সট থাকে না।

প্রসেস ও মেমরি বাজেট ঘোষণা করুন

AndroidManifest.xml-এ android:process=":bg" সেট করে আপনার ব্যাকগ্রাউন্ড Service-কে একটি ব্যক্তিগত সাবপ্রসেসে অ্যাসাইন করুন। প্রথমে কোলন (:) ব্যবহার করলে আপনার অ্যাপের UID-তে একটি ব্যক্তিগত প্রসেস তৈরি হয়।

Android 17 QPR2 (API লেভেল ৩৭.২) এবং এর পরবর্তী ভার্সনে, <processes> ব্লকের মধ্যে প্রতি-প্রসেস অ্যাপ মেমরি বাজেট-এর সাথে প্রসেস স্প্লিটিং একত্রিত করুন, যাতে অপারেটিং সিস্টেম আপনার ফোরগ্রাউন্ড UI প্রসেসকে সীমাবদ্ধ না করেই ব্যাকগ্রাউন্ড ওয়ার্কারের উপর একটি কঠোর ওয়ার্কিং-সেট সিলিং প্রয়োগ করে:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:name=".HeavyApplication"
        android:label="@string/app_name">

        <!-- App-wide budgets that cover every process, including :bg -->
        <memory-budget android:maxMb="512" />
        <memory-budget
            android:maxMb="128"
            android:state="background" />

        <processes>
            <!-- Tight budget for the headless background worker process -->
            <process android:process=":bg">
                <memory-budget android:maxMb="32" />
            </process>
        </processes>

        <activity
            android:name=".MainActivity"
            android:exported="true" />

        <service
            android:name=".AssetDownloadService"
            android:process=":bg"
            android:foregroundServiceType="dataSync"
            android:exported="false" />

    </application>
</manifest>

:bg-এ মেমরির ব্যবহার তার নিজস্ব বাজেট এবং অ্যাপ-ব্যাপী বাজেট উভয়ের বিরুদ্ধেই গণনা করা হয়। Android 17 QPR2-এর আগের ভার্সন চলছে এমন ডিভাইসে, সিস্টেম <memory-budget> উপেক্ষা করে, যেখানে android:process=":bg" সব API লেভেলে কাজ করে।

Application.onCreate-এ গেট ইনিশিয়ালাইজেশন

Android আপনার Application ক্লাস ইনস্ট্যান্টিয়েট করে এবং আপনার প্যাকেজের জন্য তৈরি করা প্রতিটি প্রসেসে Application.onCreate কল করে। android:process=":bg" গ্রহণ করার সময় একটি সাধারণ সমস্যা হল ভারী UI টুলকিট, ইমেজ-লোডিং পুল, Analytics SDK বা নেটিভ ইঞ্জিন ইনিশিয়ালাইজেশন (System.loadLibrary) একটি অসুরক্ষিত Application.onCreate মেথডের মধ্যে রেখে দেওয়া। এটি করলে :bg প্রসেসটি একই বেসলাইন ফুটপ্রিন্ট সহ স্ফীত হয়ে যায় যা আপনি এড়াতে চাইছেন।

onCreate-এর মধ্যে Application.getProcessName চেক করুন, যা Android 9 (API লেভেল ২৮) এবং তার পরবর্তী ভার্সনে উপলভ্য এবং শুধুমাত্র বর্তমান প্রসেসের জন্য প্রয়োজনীয় ডিপেন্ডেন্সি ইনিশিয়ালাইজ করুন:

Kotlin

class HeavyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val processName = Application.getProcessName()
        if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
            initBackgroundWorkerProcess()
            return
        }
        initMainUiProcess()
    }

    private fun initBackgroundWorkerProcess() {
        // Initialize only lightweight HTTP, storage, and sync primitives.
        // Avoid loading native rendering libraries or allocating UI caches.
    }

    private fun initMainUiProcess() {
        // Initialize UI frameworks, image caches, and native engines.
        System.loadLibrary("heavy_engine_jni")
    }

    companion object {
        private const val BG_PROCESS_SUFFIX = ":bg"
    }
}

জাভা

public final class HeavyApplication extends Application {
    private static final String BG_PROCESS_SUFFIX = ":bg";

    @Override
    public void onCreate() {
        super.onCreate();
        String processName = Application.getProcessName();
        if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
            initBackgroundWorkerProcess();
            return;
        }
        initMainUiProcess();
    }

    private void initBackgroundWorkerProcess() {
        // Initialize only lightweight HTTP, storage, and sync primitives.
        // Avoid loading native rendering libraries or allocating UI caches.
    }

    private void initMainUiProcess() {
        // Initialize UI frameworks, image caches, and native engines.
        System.loadLibrary("heavy_engine_jni");
    }
}

Jetpack App Startup-এর মতো কন্টেন্ট প্রোভাইডারের মাধ্যমে শুরু হওয়া লাইব্রেরি শুধুমাত্র মূল প্রসেসে শুরু হয়, কারণ android:process ছাড়া ঘোষিত কন্টেন্ট প্রোভাইডার মূল প্রসেসে চলে। :bg-এর কোড যদি এই ধরনের লাইব্রেরির উপর নির্ভর করে, তাহলে initBackgroundWorkerProcess-এ এটি স্পষ্টভাবে শুরু করুন।

আপনি যদি Unity-র মতো গেম ইঞ্জিন ব্যবহার করে তৈরি করেন, তাহলে জাভা বা কোটলিনে লেখা একটি সাধারণ Android লাইব্রেরি বা প্লাগ-ইনে ব্যাকগ্রাউন্ড পরিষেবা প্রয়োগ করুন যা UnityPlayer-এর মতো ইঞ্জিন ক্লাসকে রেফারেন্স করে না। :bg প্রসেসটি তারপরে কখনও ইঞ্জিন নেটিভ লাইব্রেরি লোড করে না। কনফার্ম করতে, চেক করে দেখুন যে :bg প্রসেসের জন্য adb shell cat /proc/<pid>/maps-এ libunity.so বা libil2cpp.so তালিকাভুক্ত নেই।

UI প্রসেস পিন না করেই যোগাযোগ করুন

:bg-এ AssetDownloadService চলাকালীন ব্যবহারকারী MainActivity খুলে রাখলে, UI-তে প্রায়ই লাইভ ডাউনলোড প্রগ্রেস দেখাতে হয়। প্রসেসের গুরুত্ব ক্লায়েন্ট থেকে সেই প্রসেসে ফ্লো করে যা পরিষেবা বা কন্টেন্ট প্রোভাইডারকে হোস্ট করে এটি ব্যবহার করে, তাই প্রতিটি ডিপেন্ডেন্সির দিক গুরুত্বপূর্ণ:

  • UI-এর সাথে ওয়ার্কারের বাইন্ডিং নিরাপদ। MainActivity, AssetDownloadService-এর সাথে বাইন্ড করলে, সিস্টেম, :bg-কে দৃশ্যমান UI-এর গুরুত্বের লেভেলে নিয়ে যায়। ব্যবহারকারী চলে গেলে, UI প্রসেস ক্যাশে হয়ে যায় এবং বাইন্ডিং আর কিছু এলিভেট করে না।
  • UI প্রসেসের উপর নির্ভর করে কর্মী নিরাপদ নয়। :bg-এ কোড যদি কোনও পরিষেবার সাথে বাইন্ড করে বা মূল প্রসেসে চলা কোনও কন্টেন্ট প্রোভাইডারকে কোয়েরি করে, তাহলে সিস্টেম মূল প্রসেসকে ওয়ার্কারের উন্নত গুরুত্বে উন্নীত করে এবং UI মেমরি আবার পিন করা থাকে। পরিবর্তে, UI-কে ওয়ার্কারের সাথে একটি কলব্যাক রেজিস্টার করতে দিন, যেমন Messenger বা একটি AIDL কলব্যাক ইন্টারফেস। কলব্যাক হিসেবে বাইন্ডার অবজেক্ট পাস করলে কোনও প্রসেসের গুরুত্ব পরিবর্তন হয় না।

আনবাইন্ড করার আগে কলব্যাক আনরেজিস্টার করুন, যাতে সিস্টেম ফ্রিজ করার পরে ওয়ার্কার কখনও UI প্রসেসে কল না করে। নিম্নলিখিত উদাহরণটি UI দৃশ্যমান থাকাকালীন শুধুমাত্র ওয়ার্কারের সাথে বাইন্ড করে:

Kotlin

class MainActivity : Activity() {
    private var downloadService: IBinder? = null

    private val downloadConnection = object : ServiceConnection {
        override fun onServiceConnected(
                name: ComponentName,
                service: IBinder
        ) {
            downloadService = service
            // Register a Messenger or AIDL callback for progress updates.
        }

        override fun onServiceDisconnected(name: ComponentName) {
            // The binding stays active and reconnects if the worker restarts.
            downloadService = null
        }
    }

    override fun onStart() {
        super.onStart()
        val intent = Intent(this, AssetDownloadService::class.java)
        bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE)
    }

    override fun onStop() {
        super.onStop()
        if (downloadService != null) {
            // Unregister the progress callback here, before unbinding.
            downloadService = null
        }
        // Unbind the ServiceConnection even if onServiceConnected hasn't run
        // yet.
        unbindService(downloadConnection)
    }
}

জাভা

public final class MainActivity extends Activity {
    private IBinder downloadService;

    private final ServiceConnection downloadConnection =
            new ServiceConnection() {
                @Override
                public void onServiceConnected(
                        ComponentName name, IBinder service) {
                    downloadService = service;
                    // Register a Messenger or AIDL callback for progress
                    // updates.
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                    // The binding stays active and reconnects if the worker
                    // restarts.
                    downloadService = null;
                }
            };

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, AssetDownloadService.class);
        bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (downloadService != null) {
            // Unregister the progress callback here, before unbinding.
            downloadService = null;
        }
        // Unbind the ServiceConnection even if onServiceConnected hasn't run
        // yet.
        unbindService(downloadConnection);
    }
}

বাইন্ডিং ফ্ল্যাগ কীভাবে প্রসেস জুড়ে oom_score_adj-কে প্রভাবিত করে সেই সম্পর্কে আরও জানতে, পরিষেবা বাইন্ডিং এবং প্রসেস স্টেট দেখুন।

নিরাপদে শেয়ার করা অন-ডিস্ক স্টেট কোঅর্ডিনেট করুন

কারণ মূল প্রসেস এবং :bg আলাদা রানটাইম এবং ফাইল ডেসক্রিপটর সহ আলাদা অ্যাড্রেস স্পেসে রান করে:

  • SQLite এবং Room: রাইট-অ্যাহেড লগিং (বেশিরভাগ ডিভাইসে Room-এ ডিফল্ট জার্নাল মোড) ব্যবহার করুন যাতে দুটি প্রসেসই একই সাথে ডেটাবেস পড়তে এবং লিখতে পারে। :bg থেকে enableMultiInstanceInvalidation নম্বরে কল করবেন না। Room মূল প্রসেসে তার ইনভ্যালিডেশন পরিষেবা হোস্ট করে, তাই কর্মী মূল প্রসেসে আবদ্ধ হবে এবং UI মেমরিকে কর্মীর উন্নত গুরুত্বে রাখবে। পরিবর্তে, UI রেজিস্টার করে এমন কলব্যাকের মাধ্যমে ওয়ার্কারকে পরিবর্তন রিপোর্ট করতে বলুন এবং UI শুরু হলে ডেটাবেস আবার কোয়েরি করতে বলুন।
  • পছন্দ: বিভিন্ন প্রসেস জুড়ে SharedPreferences শেয়ার করবেন না। মাল্টি-প্রসেস কোডে DataStore ব্যবহার করুন অথবা IPC-এর মাধ্যমে স্পষ্টভাবে স্টেট পাস করুন।
  • অ্যাসেট ফাইল: ডাউনলোড করা অ্যাসেট প্যাক :bg-এ স্টেজিং ফাইলে লিখুন, :bg-এ লেখার পরে fdatasync এবং তারপর posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) কল করুন (OS-এ মেমরি পৃষ্ঠা ফিরিয়ে দিন-এ যেমন দেখানো হয়েছে) এবং অ্যাটমিক্যালি ফাইলের নাম পরিবর্তন করে যথাস্থানে রাখুন। UI-কে পরের বার চালু হওয়ার সময় ফলাফল বেছে নিতে দিন এবং ওয়ার্কারের সাথে বাইন্ড করুন, UI প্রসেস কল করার পরিবর্তে যা ফ্রিজ হয়ে যেতে পারে।

adb শেলে এফেক্টটি লক্ষ্য করুন

আপনি যাচাই করতে পারেন যে আপনার ফোরগ্রাউন্ড এবং ব্যাকগ্রাউন্ড প্রসেস স্প্লিট করলে অ্যাক্টিভিটি ম্যানেজার প্রসেস স্টেট এবং হোম স্ক্রিনে নেভিগেট করার পরে dumpsys meminfo পরিদর্শন করে এলিভেটেড মেমরি ব্যবহার আইসোলেট করে। এই বিভাগে দেখানো স্যাম্পেল আউটপুট MemoryLab স্যাম্পেল অ্যাপ থেকে নেওয়া হয়েছে। এটি দুটি কনফিগারেশনেই এর UI প্রসেসে একই ৬৪ এমবি নেটিভ অ্যালোকেশন রান করে এবং :remote প্রসেসে এর স্প্লিট ফোরগ্রাউন্ড সার্ভিস হোস্ট করে।

১. প্রসেস স্টেট ও OOM স্কোর তুলনা করা

ব্যাকগ্রাউন্ড ওয়ার্কলোড শুরু করুন, হোম বোতাম প্রেস করুন যাতে Activity বন্ধ হয়ে যায়, এবং অ্যাক্টিভিটি ম্যানেজার কোয়েরি করুন:

adb shell dumpsys activity processes com.example.app | grep -E "Proc #|oom:|state:"

সিঙ্গেল-প্রসেস আর্কিটেকচারে, মূল প্রসেসটি উন্নত থাকে, যদিও এর Activity বন্ধ করা হয়েছে। ব্যবহারকারী চলে যাওয়ার পরে প্রায় ১৫ সেকেন্ড পর্যন্ত, অ্যাক্টিভিটি ম্যানেজার এমন একটি প্রসেস বজায় রাখে যা oom_score_adj = 50 (fg-service-act)-এ ফোরগ্রাউন্ড সার্ভিস চালায় এবং তারপর এটিকে কমিয়ে 200 (fg-service) করে দেয়। হোম বোতাম প্রেস করার ২০ সেকেন্ড পরে নিম্নলিখিত আউটপুট ক্যাপচার করা হয়েছে:

    Proc #1: prcp     F/S/FGS  ---NFUATI  t: 0 13794:com.android.memorylab/u0a284 (fg-service)
        oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
        state: cur=FGS  set=FGS   lastRss=0.00 lastCachedRss=0.00

স্প্লিট-প্রসেস আর্কিটেকচারে, শুধুমাত্র ওয়ার্কার প্রসেস oom_score_adj = 200-এ থাকে, যেখানে মূল UI প্রসেস PREV-এ (oom_score_adj = 700) এবং পরে ক্যাশে করা বাকেটে (oom_score_adj >= 900) নেমে যায়:

    Proc #3: prcp     F/S/FGS  ---NFUATI  t: 0 13544:com.android.memorylab:remote/u0a284 (fg-service)
        oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
        state: cur=FGS  set=FGS   lastRss=0.00 lastCachedRss=0.00
    Proc #1: prev     b/ /LAST -------TI  t: 0 13312:com.android.memorylab/u0a284 (previous)
        oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
        state: cur=LAST set=LAST  lastRss=0.00 lastCachedRss=0.00

আগের অ্যাপের টাইমআউট শেষ হয়ে যাওয়ার পরে, মূল প্রসেসটি ক্যাশে করা বাকেটে (cch, oom_score_adj = 900, previous-expired) চলে যায় এবং অন্তর্নিহিত সিপিইউ টাইম (I) সহ প্রসেস সংক্রান্ত সব ক্ষমতা (---------) হারিয়ে ফেলে। সেই মুহূর্তে সিস্টেম এটিকে ফ্রিজ করে দেয় এবং ওয়ার্কার প্রসেসে কোনও বাধা সৃষ্টি না করেই যেকোনও সময় এটিকে কম্প্যাক্ট বা রিক্লেম করতে পারে।

২. প্রসেস জুড়ে dumpsys meminfo তুলনা করুন

UI প্রসেসের সাথে এলিভেটেড ওয়ার্কারের dumpsys meminfo সারসংক্ষেপের তুলনা করুন:

adb shell dumpsys meminfo com.example.app:bg
adb shell dumpsys meminfo com.example.app

oom_score_adj = 200-এ MemoryLab ওয়ার্কার প্রসেসের জন্য স্যাম্পেল App Summary আউটপুট:

 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:      380                          13840
         Native Heap:     1548                           2832
                Code:      132                          55816
               Stack:      200                            208
            Graphics:        0                              0
       Private Other:      496
              System:     1926
             Unknown:                                    4168

           TOTAL PSS:     4682            TOTAL RSS:    76864       TOTAL SWAP PSS:      441

oom_score_adj = 700-এ MemoryLab UI প্রসেসের জন্য স্যাম্পেল App Summary আউটপুট, যা Private Other-এ ৬৪ এমবি নেটিভ অ্যালোকেশন ধরে রাখে:

 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:     1316                          19816
         Native Heap:    30408                          31596
                Code:     3148                         115796
               Stack:      656                            664
            Graphics:        0                              0
       Private Other:    67044
              System:    13687
             Unknown:                                   71092

           TOTAL PSS:   116259            TOTAL RSS:   238964       TOTAL SWAP PSS:      259

ওয়ার্কার প্রসেসের জন্য ৫ এমবি-র কম PSS প্রয়োজন, কারণ এটি বেশিরভাগ কোড ও রানটাইম পেজ Zygote-এর সাথে শেয়ার করে। Vulkan বা OpenGL ES-এর মাধ্যমে রেন্ডার করা অ্যাপে, UI প্রসেসের Graphics সারিতে GPU মেমরিও অন্তর্ভুক্ত থাকে। এই স্যাম্পেলটি সফ্টওয়্যার রেন্ডারিং সহ একটি Cuttlefish ভার্চুয়াল ডিভাইসে ক্যাপচার করা হয়েছে, তাই উভয় প্রসেসেই Graphics হল 0।

৩. প্রতি-প্রসেস cgroup অ্যাকাউন্টিং পরীক্ষা করুন

আপনার UID-এর অধীনে প্রতিটি প্রসেসের নিজস্ব cgroup v2 ডিরেক্টরি আছে যা আপনার অ্যাপের জন্য একটি পেরেন্ট cgroup-এর মধ্যে থাকে। নিম্নলিখিত কমান্ডটি আপনার অ্যাপের প্রতিটি প্রসেসের জন্য oom_score_adj, memory.current এবং অ্যাপ-ব্যাপী memory.high তালিকাভুক্ত করে:

adb shell '
  for PID in $(pidof com.example.app com.example.app:bg); do
    CG=/sys/fs/cgroup$(sed -n "s/^0:://p" /proc/$PID/cgroup)
    echo "pid=$PID ($(tr -d "\0" < /proc/$PID/cmdline))" \
      "adj=$(cat /proc/$PID/oom_score_adj)" \
      "memory.current=$(cat $CG/memory.current)" \
      "app.memory.high=$(cat ${CG%/*}/memory.high)"
  done
'

হোম প্রেস করার পরে MemoryLab-এর জন্য স্যাম্পেল আউটপুট:

pid=15301 (com.android.memorylab) adj=700 memory.current=103260160 app.memory.high=966787072
pid=16518 (com.android.memorylab:remote) adj=200 memory.current=3608576 app.memory.high=966787072

UI প্রসেসের জন্য প্রায় ৯৮ এমবি চার্জ করা হলেও, ওয়ার্কার প্রসেসের জন্য ৪ এমবির কম চার্জ করা হয়। প্রতিটি প্রসেসের নিজস্ব memory.current আছে, কিন্তু মেমরি লিমিটার আপনার অ্যাপের সব প্রসেস ধারণকারী পেরেন্ট cgroup-এ এর সীমা সেট করে। উভয় লাইনই 966,787,072 বাইটের একই app.memory.high দেখায়, যা নিম্নলিখিত আউটপুটে 922 এমবি-র অদৃশ্য সীমা।

ডিভাইসে মেমরি লিমিটারের প্রয়োগ করা দৃশ্যমান ও অদৃশ্যমান সীমা দেখতে, নিম্নলিখিত কমান্ড রান করুন:

adb shell am memory-limiter status

৪ জিবি RAM সহ ডিভাইস থেকে স্যাম্পেল আউটপুট, যেখানে দৃশ্যমান নয় এমন সীমা দৃশ্যমান সীমার অর্ধেক:

Memory limiter
  enabled                  limits=true              killing=true             ignored=none
  visible:                 memHigh=1843MB           memMax=2048MB            swapMax=1024MB
  not-visible:             memHigh=922MB            memMax=1024MB            swapMax=1024MB

Perfetto-তে এফেক্ট লক্ষ্য করুন

ব্যাকগ্রাউন্ডে কাজ চলাকালীন ব্যবহারকারী ফোরগ্রাউন্ড UI থেকে হোম স্ক্রিনে গেলে, Perfetto আপনাকে দুটি প্রসেস জুড়ে oom_score_adj এবং RSS-এর টাইমলাইন দেখতে দেয়।

১. ট্রেস রেকর্ড করুন

আপনার অ্যাপে ব্যাকগ্রাউন্ড জব শুরু করার সময় এবং হোম স্ক্রিনে নেভিগেট করার সময় প্রসেস মেমরি কাউন্টার, GPU মেমরি এবং oom_score_adj পরিবর্তন ক্যাপচার করে এমন একটি ১৫-সেকেন্ডের ট্রেস রেকর্ড করুন:

adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/process-splitting.perfetto-trace <<'EOF'
buffers: {
  size_kb: 32768
  fill_policy: DISCARD
}
data_sources: {
  config {
    name: "linux.process_stats"
    process_stats_config {
      scan_all_processes_on_start: true
      proc_stats_poll_ms: 250
    }
  }
}
data_sources: {
  config {
    name: "android.gpu.memory"
  }
}
data_sources: {
  config {
    name: "linux.ftrace"
    ftrace_config {
      ftrace_events: "oom/oom_score_adj_update"
      ftrace_events: "gpu_mem/gpu_mem_total"
    }
  }
}
duration_ms: 15000
EOF
adb pull /data/misc/perfetto-traces/process-splitting.perfetto-trace

২. Perfetto UI-তে প্রসেস ট্র্যাক পরীক্ষা করা

Perfetto UI-তে ট্রেস খুলুন এবং com.example.app ও com.example.app:bg-এর জন্য প্রসেস গ্রুপ বড় করুন:

  1. দুটি প্রসেসের জন্য oom_score_adj এবং mem.rss.anon ট্র্যাক পিন করুন যাতে সেগুলি টাইমলাইনের উপরে পাশাপাশি দেখা যায়।
  2. ব্যবহারকারী হোম বোতাম প্রেস করেছেন এমন টাইমস্ট্যাম্প খুঁজে বের করুন:
    • ওয়ার্কার প্রসেসে, oom_score_adj 200-এ থাকে এবং mem.rss.anon ফ্ল্যাট ও ছোট থাকে।
    • UI প্রসেসে, oom_score_adj 0 থেকে 200-এ বৃদ্ধি পায়, Activity বন্ধ হওয়ার সময় প্রায় এক সেকেন্ডের জন্য সেখানে থাকে এবং তারপর 700-এ বৃদ্ধি পায় (PREV)। আগের অ্যাপের টাইমআউটের পরে, এটি ক্যাশে করা রেঞ্জে চলে যায়, যেখানে সিস্টেম ওয়ার্কারকে প্রভাবিত না করেই তার মেমরি কম্প্যাক্ট ও রিক্লেম করতে পারে।
UI প্রসেস ও ওয়ার্কার প্রসেসের জন্য Perfetto ট্র্যাক। UI
       প্রসেস oom_score_adj ০ থেকে ২০০ এবং তারপর হোম-এর পরে ৭০০-তে বৃদ্ধি পায়, যেখানে
       ওয়ার্কার প্রসেস ২০০-তে থাকে এবং প্রায় ১৯ এমবি বেনামী RSS থাকে।
ছবি ২. ব্যবহারকারী হোম বোতাম প্রেস করার পরে প্রসেস স্প্লিট করুন।

২ নম্বর ছবিতে, UI প্রসেস অল্প সময়ের জন্য 200-এ থাকে, যখন এর Activity বন্ধ হয়ে যায় এবং তারপর 700-এ উঠে যায়, যখন ওয়ার্কার 200-এ থাকে, যার সাথে প্রায় ১৯ এমবি বেনামী RSS থাকে। তুলনা করার জন্য, ৩ নম্বর ছবিতে কো-লোকেটেড কনফিগারেশনের ৩৫-সেকেন্ডের ট্রেস দেখানো হয়েছে, যেখানে একটি প্রসেস 50 এবং তারপরে 200-এ তার সমস্ত UI মেমরি সহ থাকে।

Perfetto tracks for a single co-located process. oom_score_adj
       rises from 0 to 50 after Home and to 200 about 15 seconds later, while
       anonymous RSS stays high.
ছবি ৩. ব্যবহারকারী হোম বোতাম প্রেস করার পরে একটি কো-লোকেটেড প্রসেস।

এইসব ভিউ এক্সপ্লোর করতে, Perfetto UI-তে স্প্লিট ট্রেস এবং কো-লোকেটেড ট্রেস খুলুন।

৩. PerfettoSQL-এর মাধ্যমে উন্নত মেমরি ফুটপ্রিন্ট কোয়েরি করুন

প্রতিটি oom_score_adj লেভেলে আপনার অ্যাপের প্রতিটি প্রসেস দ্বারা হোল্ড করা গড় বেনামী এবং ফাইল RSS পরিমাপ করতে কোয়েরি (SQL) ট্যাবে নিম্নলিখিত PerfettoSQL কোয়েরি রান করুন:

INCLUDE PERFETTO MODULE android.memory.process;

SELECT
  process_name,
  score AS oom_score_adj,
  ROUND(SUM(dur) / 1e9, 2) AS duration_sec,
  ROUND(SUM(anon_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_anon_mb,
  ROUND(SUM(file_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_file_mb
FROM memory_oom_score_with_rss_and_swap_per_process
WHERE process_name GLOB 'com.example.app*'
GROUP BY process_name, score
ORDER BY process_name, score;

ছবি ২-এ স্প্লিট ট্রেসের জন্য স্যাম্পেল আউটপুট:

process_name                  oom_score_adj  duration_sec  avg_anon_mb  avg_file_mb
com.android.memorylab                     0          4.11         93.3        110.4
com.android.memorylab                   200          1.06        126.8        110.5
com.android.memorylab                   700          9.72        115.1        110.5
com.android.memorylab:remote              0          0.01         NULL         NULL
com.android.memorylab:remote            200         12.87         18.9         53.7

ছবি ৩-এ দেখানো কো-লোকেটেড ট্রেসের স্যাম্পেল আউটপুট:

process_name           oom_score_adj  duration_sec  avg_anon_mb  avg_file_mb
com.android.memorylab              0          4.10         96.0        110.4
com.android.memorylab             50         16.04        117.6        110.5
com.android.memorylab            200         14.68        116.5        110.6

স্প্লিট কনফিগারেশনে, প্রথম সেকেন্ডের পরে oom_score_adj = 200-এ মাত্র ১৯ এমবি বেনামী মেমরি থাকে। কো-লোকেটেড কনফিগারেশনে, প্রায় ১১৭ এমবি 50-এ থাকে এবং তারপরে ফোরগ্রাউন্ড সার্ভিস যতক্ষণ চলে ততক্ষণ 200-এ থাকে।


← OS-এ মেমরি পেজ ফিরিয়ে দিন | ↑ উপরে