অ্যাপ মেমরি বাজেট সেট করা

অ্যাপ মেমরি বাজেট অ্যাপকে নিজের জন্য মেমরি বাজেট ঘোষণা করতে দেয়, যা সিস্টেমকে বলে যে অ্যাপটি তার সেট করা বাজেট এর চেয়ে বেশি ব্যবহার করলে তার মেমরি ব্যবহার কম করতে হবে। এটি বিশেষত সিস্টেম ও বান্ডেল করা অ্যাপের জন্য বা মেমরি-সীমাবদ্ধ ডিভাইসে টার্গেট করা অ্যাপের জন্য উপযোগী, যেখানে ডেভেলপার তার প্রত্যাশিত মেমরি ওয়ার্কিং সেট (ব্যবহারকারী বর্তমানে যা করছেন তার জন্য অ্যাপের অ্যাক্টিভভাবে প্রয়োজনীয় মেমরি) সম্পর্কে জানেন এবং নিশ্চিত করতে চান যে তার অ্যাপ যেন সিস্টেমের শেয়ার করা RAM রিসোর্স অত্যধিক ব্যবহার না করে।

মেমরি ইভিকশন ও সোয়াপ ব্যবহার করে বাজেট ব্যালেন্স রাখা হয়। এর মাধ্যমে যেসব মেমরি পেজ সম্প্রতি ব্যবহার করা হয়নি সেগুলি সরিয়ে দেওয়া হয় এবং অ্যাপের মেমরি ফুডপ্রিন্টকে সেটির বর্তমান ওয়ার্কিং সেটে ফোকাস করা হয়। কোনও অ্যাপ তার ঘোষিত বাজেট অতিক্রম করলে, অপারেটিং সিস্টেম সেই অ্যাপকে বিশেষভাবে টার্গেট করে:

  1. ফাইল-ব্যাকড পেজ পরিষ্কার করা (যেমন, ইনঅ্যাক্টিভ কোড ও ম্যাপ করা অ্যাসেট) প্রথমে সরিয়ে দেওয়া হয় কারণ প্রয়োজন হলে সেগুলি স্টোরেজ থেকে আবার পড়া যায়।
  2. ফাইল-ব্যাকড ডার্টি পৃষ্ঠা স্টোরেজে লেখা হয় এবং সরিয়ে দেওয়া হয়।
  3. পরিচয় গোপন রাখা মেমরি পৃষ্ঠা (যেমন হিপ অ্যালোকেশন) কম্প্রেস করা হয় এবং zRAM-এ অদল-বদল করা হয়।

যতক্ষণ পর্যন্ত ওয়ার্কিং সেট বাজেটের বেশি না হচ্ছে, ততক্ষণ পর্যন্ত অ্যাপ ভালভাবে কাজ করবে এবং সেট করা বাজেটের বেশি মেমরি ব্যবহার করবে না। অপারেটিং সিস্টেম অব্যবহৃত মেমরি সরিয়ে দেয় এবং অ্য়াপের ইনঅ্যাক্টিভ হিপ পেজ কম্প্রেস করে যাতে মেমরি অ্যালোকেশন প্রসেস বন্ধ না করেই সীমাবদ্ধ থাকে।

কোনও অ্যাপ তার বাজেট অতিক্রম করলে কী হয়

মেমরি বাজেট অতিক্রম করলে, সিস্টেম আপনার অ্যাপ বন্ধ বা ক্র্যাশ করে না। পরিবর্তে, কোনও অ্যাপ যখন তার বাজেট অনুমোদিত মেমরির চেয়ে বেশি মেমরি ব্যবহার করে, তখন OS এমন মেমরি খুঁজে পায় যা অ্যাপটি কিছু সময় ধরে ব্যবহার করেনি এবং নিরাপদে এটি সরিয়ে দেয় যতক্ষণ না এটি আবার প্রয়োজন হয়। এর ফলে নতুন অ্যাপ মেমরি অ্যালোকেশন করা যায় এবং সামগ্রিকভাবে অ্যাপের মেমরি ব্যবহার বাজেট অনুযায়ী থাকে।

অ্যাপের যেকোনও সময় যা প্রয়োজন (এর ওয়ার্কিং সেট) এবং সেট করা বাজেটের মধ্যে যতক্ষণ পর্যন্ত স্পেস থাকে, ততক্ষণ পর্যন্ত অ্যাপটি স্বাভাবিকভাবে চলতে থাকে। কোনও অ্যাপ যদি তার ওয়ার্কিং সেটের চেয়ে ছোট বাজেট সেট করে, তাহলে রানটাইমে অ্যাপের পারফর্ম্যান্স কমে যেতে পারে।

কীভাবে OS মেমরি ম্যানেজ করে তা জানতে, মেমরি আর্কিটেকচার গাইড দেখুন, বিশেষ করে মেমরি রিক্লেইম ও সোয়াপ সংক্রান্ত বিভাগ দেখুন।

Android ম্যানিফেস্টে বাজেট ঘোষণা করা

আপনার AndroidManifest.xml-এ মেমরি বাজেট ঘোষণা করা হল বাজেট নির্ধারণের প্রাথমিক ও সুপারিশ করা পদ্ধতি। এর জন্য কোনও রানটাইম কোডের প্রয়োজন হয় না, প্রসেস স্টার্ট-আপের সাথে সাথেই এটি কার্যকর হয় এবং এটি অপারেটিং সিস্টেমের জন্য একটি স্পষ্ট চুক্তি প্রদান করে।

Android 17 QPR2 (API লেভেল 37.2) ও এর পরবর্তী যেকোনও ভার্সনে চলা ডিভাইসে <memory-budget> ঘোষণা কার্যকর হয়। Android-এর আগের ভার্সনে, প্ল্যাটফর্ম ম্যানিফেস্ট পার্সার নিরাপদে অচেনা XML এলিমেন্ট উপেক্ষা করে, তাই আপনি <memory-budget> গ্রহণ করতে পারেন। এর ফলে ব্যাকওয়ার্ড কম্প্যাটিবিলিটিতে কোনও প্রভাব পড়ে না।

বেসলাইন বাজেট ঘোষণা করা

বেশিরভাগ অ্যাপের ক্ষেত্রে, অ্যাপ্লিকেশনের জন্য একটি বাজেট নির্ধারণ করাই যথেষ্ট। <application> ট্যাগের মধ্যে সরাসরি <memory-budget> এলিমেন্ট ঘোষণা করুন:

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

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

        <!-- Baseline budget for the application -->
        <memory-budget android:maxMb="256" />

    </application>
</manifest>

এটি প্যাকেজের জন্য সমস্ত প্রসেস ও স্টেট জুড়ে ২৫৬ এমবি রেসিডেন্ট মেমরি বাজেট সেট করে। অ্যাপের মেমরি ফুটপ্রিন্ট ২৫৬ এমবি ছাড়িয়ে গেলে, অপারেটিং সিস্টেম ইভিকশন ও সোয়াপ ব্যবহার করে ইনঅ্যাক্টিভ মেমরি পৃষ্ঠা ট্রিম করে।

প্রসেস স্টেট অনুযায়ী বাজেট আলাদা আলাদা হয়

ব্যবহারকারীর দৃশ্যমানতার উপর নির্ভর করে কোনও অ্যাপের জন্য আলাদা আলাদা মেমরি প্রয়োজন হয়:

  • ফোরগ্রাউন্ড: প্রসেসটি ব্যবহারকারীর সাথে ইন্টার‍্যাক্ট করা একটি দৃশ্যমান অ্যাক্টিভিটি হোস্ট করছে। এই স্টেটে সাধারণত অ্যাক্টিভ UI ও গ্রাফিক্সের কারণে সবচেয়ে বেশি জায়গা লাগে।
  • বোঝার মতো: প্রসেসটি ব্যবহারকারীর কাছে বোঝার মতো হলেও কোনও দৃশ্যমান উইন্ডো হোস্ট করে না (যেমন, মিডিয়া প্লেব্যাক ফোরগ্রাউন্ড পরিষেবা হোস্ট করা, অ্যাক্টিভ ব্যাকগ্রাউন্ড ডাউনলোড, টার্ন-বাই-টার্ন নেভিগেশন বা অ্যাক্টিভ ইনপুট মেথড)।
  • ব্যাকগ্রাউন্ড: প্রসেসটি ব্যাকগ্রাউন্ড জব, রিসিভার বা ডেটা সিঙ্ক চালাচ্ছে। এটি ন্যূনতম ফুটপ্রিন্ট বজায় রাখবে বলে আশা করা যায়।

এইসব স্টেট ম্যাচ করাতে আপনি একাধিক <memory-budget> ক্লজ ঘোষণা করতে পারবেন:

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

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

        <!-- Default budget for visible foreground UI -->
        <memory-budget android:maxMb="200" />

        <!-- Tighter budget when playing audio in background -->
        <memory-budget
            android:maxMb="120"
            android:state="perceptible" />

        <!-- Minimal budget when fully in background -->
        <memory-budget
            android:maxMb="48"
            android:state="background" />

    </application>
</manifest>

android:state ছাড়া বেস ক্লজের প্রয়োজন নেই; আপনি যদি শুধুমাত্র রাজ্য-নির্দিষ্ট ক্লজ (যেমন, android:state="background") উল্লেখ করেন, তাহলে অন্যান্য রাজ্য অ্যাপ বাজেটের দ্বারা সীমাবদ্ধ থাকে না। অন্তর্ভুক্ত করা হলে, android:state ছাড়া ক্লজটি নির্দিষ্ট না করা স্টেট (যেমন ফোরগ্রাউন্ড)-এর জন্য ডিফল্ট ফলব্যাক হিসেবে কাজ করে, যা অ্যাপ perceptible বা background স্টেটে ট্রানজিট করলে পরবর্তী আরও বেশি বিধিনিষেধমূলক ক্লজ ওভাররাইড করে।

প্রসেস স্টেটের সাথে বাজেট স্টেট কীভাবে ম্যাপ করা হয়

প্ল্যাটফর্ম, RunningAppProcessInfo.importance-এর ভিত্তিতে ম্যানিফেস্ট বাজেট স্টেটকে রানটাইম প্রসেস স্টেটের সাথে ম্যাপ করে:

  • foreground: অ্যাক্টিভ, দৃশ্যমান ব্যবহারকারীর ইন্টার‍্যাকশন, যেমন আবার চালু করা অ্যাক্টিভিটি হোস্ট করা বা স্ক্রিন বন্ধ হয়ে গেলে টপ অ্যাপের স্টেট বজায় রাখা।
  • perceptible: ব্যবহারকারী কোনও দৃশ্যমান উইন্ডো ছাড়াই বুঝতে পারেন এমন ওয়ার্কলোড, যেমন, অ্যাক্টিভ মিডিয়া প্লেব্যাক, টার্ন-বাই-টার্ন নেভিগেশন, ক্যামেরা বা মাইক্রোফোন ক্যাপচার, অ্যাক্টিভ ব্যাকগ্রাউন্ড ডাউনলোড বা ডেটা সিঙ্ক ফোরগ্রাউন্ড পরিষেবা। ইন্টার্নাল প্ল্যাটফর্ম মেট্রিক্স এইসব ওয়ার্কলোডের মধ্যে কিছু ওয়ার্কলোডকে PROCESS_STATE_IMPORTANT_FOREGROUND হিসেবে লেবেল করতে পারে, তবে সেই ইন্টার্নাল কনস্ট্যান্ট কোনও দৃশ্যমান উইন্ডোকে বোঝায় না এবং এটি perceptible বাজেট দ্বারা পরিচালিত হয়।
  • background: ব্যবহারকারীকে সঙ্গে সঙ্গে দেখানো হয় না এমন কাজ, যেমন ব্যাকগ্রাউন্ড জব, অ্যালার্ম, ব্রডকাস্ট রিসিভার অথবা ব্যবহারকারী নেভিগেট করে বেরিয়ে যাওয়ার পরে আগের অ্যাক্টিভিটি।

নিম্নলিখিত সারণী থেকে বোঝা যায় যে রানটাইম গুরুত্বের লেভেল কীভাবে ম্যানিফেস্ট স্টেটের সাথে ম্যাপ করে:

ম্যানিফেস্ট android:state রানটাইমের গুরুত্ব (RunningAppProcessInfo) সাধারণ কম্পোনেন্ট
foreground IMPORTANCE_FOREGROUND
IMPORTANCE_TOP_SLEEPING
স্ক্রিন লক করা অবস্থায় আবার চালু করা দেখা যায় এমন অ্যাক্টিভিটি, সবচেয়ে উপরের অ্যাপ
perceptible IMPORTANCE_FOREGROUND_SERVICE
IMPORTANCE_VISIBLE
অ্যাক্টিভ মিডিয়া প্লেব্যাক, নেভিগেশন, ডাউনলোড বা সিঙ্ক ফোরগ্রাউন্ড পরিষেবা
background IMPORTANCE_PERCEPTIBLE
IMPORTANCE_CANT_SAVE_STATE
IMPORTANCE_SERVICE
ব্যাকগ্রাউন্ড জব, রিসিভার, ব্যাকগ্রাউন্ড সিঙ্ক, আগের অ্যাক্টিভিটি বা ব্যাকগ্রাউন্ডে হোম অ্যাপ

কোনও প্রসেস যখন কোনও অ্যাক্টিভ কম্পোনেন্ট না নিয়ে ক্যাশেড স্টেটে (IMPORTANCE_CACHED) প্রবেশ করে, তখন প্ল্যাটফর্মটি তার অ্যাক্টিভ মেমরি বাজেট রিলিজ করে এবং স্ট্যান্ডার্ড ক্যাশেড-প্রসেস রিক্লেমেশনের মাধ্যমে তার মেমরি ম্যানেজ করে।

আপনার অ্যাপের প্রসেস স্টেট ও বাজেট পরিদর্শন করা

ডেভেলপমেন্টের সময় আপনার অ্যাপের অ্যাক্টিভ প্রসেস স্টেট ও গুরুত্ব পরীক্ষা করার একটি উপায় হল ADB ব্যবহার করে অ্যাক্টিভিটি ম্যানেজারকে কোয়েরি করা:

adb shell dumpsys activity processes <package-name>

নিম্নলিখিত সংক্ষিপ্ত উদাহরণে, ফোরগ্রাউন্ড পরিষেবা চালানো অ্যাপের প্রসেস রেকর্ড ও OOM কন্ট্রোল এন্ট্রি দেখানো হয়েছে:

ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
  All known processes:
  *APP* UID 10123 ProcessRecord{edf056c 3919:com.example.app/u0a123}
    pid=3919
    oom adj: max=1001 curRaw=200 setRaw=200 cur=200 set=200
    curProcState=4 mRepProcState=4 setProcState=4 lastStateTime=-42s360ms
    hasStartedServices=true
    mHasForegroundServices=true forcingToImportant=null
...
  Process OOM control (48 total):
    Proc #22: prcp  F/S/FGS  ---NFU-TI  t: 0 3919:com.example.app/u0a123 (fg-service)
        oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
        state: cur=FGS  set=FGS  lastRss=0.00 lastCachedRss=0.00

এই আউটপুটে:

  • curProcState=4 ও state: cur=FGS থেকে বোঝা যায় যে প্রসেসটি ফোরগ্রাউন্ড পরিষেবা স্টেটে আছে। আপনার অ্যাপ কোনও অ্যাক্টিভ দৃশ্যমান অ্যাক্টিভিটি হোস্ট করলে, এটি TOP হিসেবে দেখা যাবে। এটি কোনও গুরুত্বপূর্ণ ফোরগ্রাউন্ড কম্পোনেন্ট (যেমন, ডাউনলোড বা সিঙ্ক) চালালে, এটি IMPF হিসেবে দেখা যায়।
  • প্রসেস OOM কন্ট্রোল টেবিলে, prcp চিহ্নটি ইঙ্গিত করে যে প্রসেসটি বোঝার মতো অগ্রাধিকারের টিয়ারের অধীনে মূল্যায়ন করা হয়েছে, যা perceptible বাজেটের সাথে সম্পর্কিত।

বর্তমানে এনফোর্স করা মেমরি বাজেট ও রেসিডেন্ট মেমরি ব্যবহার পরিদর্শন করতে:

adb shell dumpsys meminfo <package-name>

Android 17 QPR2 থেকে শুরু করে, এই আউটপুটে একটি মেমরি বাজেট বিভাগ থাকে যা অ্যাক্টিভ সীমা, সীমার সোর্স (যেমন AndroidManifest বা MemoryBudgetManager) এবং বর্তমান রেসিডেন্ট মেমরি দেখায়।

মাল্টি-প্রসেস অ্যাপ

আপনার অ্যাপ্লিকেশন যদি একাধিক প্রসেস জুড়ে কাজ ভাগ করে নেয়, তাহলে <processes>-এর মধ্যে <process> ট্যাগ ব্যবহার করে ডেডিকেটেড প্রসেস বাজেট কনফিগার করুন।

যেমন, একটি স্ট্রিমিং মিউজিক অ্যাপের (com.example.radio) কথা বিবেচনা করুন:

  1. মূল প্রসেস: দৃশ্যমান UI ও অডিও প্লেব্যাক ইঞ্জিন হোস্ট করে (MediaSessionService mediaPlayback ফোরগ্রাউন্ড পরিষেবার সাথে)। দেখা গেলে, প্রসেসটি ১৮০MB ফোরগ্রাউন্ড বাজেটের অধীনে কাজ করে। মিউজিক চলতে থাকার সময় ব্যবহারকারী অ্যাপ ছেড়ে বেরিয়ে গেলে, প্রসেস perceptible স্টেটে প্রবেশ করে, যেখানে প্লেব্যাক ইঞ্জিন ও অডিও বাফারের জন্য ৬৪ এমবি বাজেট যথেষ্ট।
  2. সিঙ্ক করার প্রসেস (:sync): ব্যাকগ্রাউন্ড মেটাডেটা সিঙ্ক্রোনাইজেশন ও ডাউনলোড ইন্ডেক্সিং চালানোর জন্য নির্দিষ্ট প্রসেস। এই প্রসেসটি যেহেতু সবসময় ব্যাকগ্রাউন্ডে অ্যাক্টিভ থাকে, তাই আপনাকে আলাদা করে ঘোষণা state="background"করতে হবে না; একটি বাজেটই প্রযোজ্য হবে।
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.radio">

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

        <!-- Package baseline: main process with UI and audio playback -->
        <memory-budget android:maxMb="180" />

        <!-- Tighter budget when audio plays in the background -->
        <memory-budget
            android:maxMb="64"
            android:state="perceptible" />

        <!-- Dedicated background sync process -->
        <processes>
            <process android:process=":sync">
                <memory-budget android:maxMb="32" />
            </process>
        </processes>

        <service
            android:name=".playback.AudioPlayerService"
            android:foregroundServiceType="mediaPlayback"
            android:exported="false" />

        <service
            android:name=".sync.PlaylistSyncService"
            android:process=":sync"
            android:exported="false" />

    </application>
</manifest>

যেকোনও সাবপ্রসেসে মেমরি ব্যবহার করা হলে, সেটি প্রসেস বাজেট ও এনক্লোজিং প্যাকেজ বাজেট, দুটির বিরুদ্ধেই গণনা করা হয়। কোনও প্রসেস রানটাইমে মেমরি প্রেসার অনুভব করে যদি এটি এর প্রসেস বাজেট বা প্যাকেজ বাজেট লঙ্ঘন করে, যে থ্রেশহোল্ডে প্রথমে পৌঁছানো হয়।

হাই-ডেনসিটি ডিসপ্লের জন্য বাজেট স্কেল করা

যেসব অ্যাপ্লিকেশনের মেমরি ফুটপ্রিন্ট, ডিসপ্লেতে একসাথে কতগুলি পিক্সেল ড্র করতে হবে তার সাথে উল্লেখযোগ্যভাবে স্কেল করে—যেমন, ফটো গ্যালারি অ্যাপ স্ক্রিনের সাইজ অনুযায়ী বিটম্যাপ ক্যাশে করে—Android, ডিসপ্লে স্পেসিফিকেশন অনুযায়ী ডায়নামিক বাজেট স্কেল করার জন্য দুটি বিকল্প মেকানিজম প্রদান করে:

  • ডিসপ্লে ডেনসিটি বাকেট (android:additionalMbPerDensity) অনুযায়ী স্কেল করুন: ডিসপ্লের ডেনসিটি রেশিও এর সাথে আনুপাতিকভাবে মেগাবাইট যোগ করে mdpi (১.০x / ১৬০ ডিপিআই)-এর সাথে তুলনা করে। UI ডেনসিটি বাকেটের সাথে মেমরি ব্যবহার স্কেল করলে এটি উপযুক্ত হয়, যেমন, বেশি রেজোলিউশনের র‍্যাস্টার ড্রয়েবল বা UI অ্যাসেট ক্যাশে করা:

    <!-- Baseline 180MB + 16MB per 1.0x density ratio -->
    <memory-budget
        android:maxMb="180"
        android:additionalMbPerDensity="16" />
    

    mdpi ডিসপ্লেতে (১.০x), বাজেট হল ১৮০ + ১৬ × ১ = ১৯৬ এমবি। xxhdpi ডিসপ্লেতে (৩.০x), বাজেট ১৮০ + ১৬ × ৩ = ২২৮ এমবি হিসেবে স্কেল করা হয়।

  • ফিজিক্যাল ডিসপ্লে রেজোলিউশন অনুযায়ী স্কেল করা (android:additionalBytesPerDisplayPixel): ফিজিক্যাল ডিসপ্লে পিক্সেল (প্রস্থ × উচ্চতা) অনুযায়ী সরাসরি বাইট যোগ করে। এটি সেইসব অ্যাপ্লিকেশনের জন্য আদর্শ যেগুলি ফুল-স্ক্রিন গ্রাফিক্স সারফেস, রেন্ডার বাফার বা ফুল-রেজোলিউশন ফটো ক্যাশে বরাদ্দ করে, যেখানে মেমরি খরচ সরাসরি UI ডেনসিটি বাকেটের পরিবর্তে ডিসপ্লে পিক্সেলের কাঁচা সংখ্যার সাথে স্কেল করে:

    <!-- Baseline 128MB + 16 bytes per physical display pixel -->
    <!-- For example, a 4-byte RGBA full-screen buffer with double or quadruple buffering -->
    <memory-budget
        android:maxMb="128"
        android:additionalBytesPerDisplayPixel="16" />
    

    1080p ডিসপ্লেতে (1080 × 2400 ও আনুমানিক ২.৫৯M পিক্সেল), এটি বেসলাইন বাজেটে আনুমানিক ৪১.৪ এমবি যোগ করে। ১৪৪০p ডিসপ্লেতে (১৪৪০ × ৩১২০ ও আনুমানিক ৪.৪৯M পিক্সেল), এটি আনুমানিক ৭১.৮ এমবি যোগ করে।

এই দুটি অ্যাট্রিবিউট বিকল্প। আপনার অ্যাপের প্রাথমিক স্কেলিং ফ্যাক্টরের সাথে মেলে এমন অ্যাট্রিবিউট বেছে নিন এবং একই ক্লজে দুটিকে একত্রিত করা এড়িয়ে চলুন।

ডিভাইস ফর্ম ফ্যাক্টরের জন্য বিশেষীকরণ

ফোন, ট্যাবলেট ও Wear OS জুড়ে APK শিপিং করার সময়, আলাদা আলাদা হার্ডওয়্যার টার্গেটের জন্য বাজেট অ্যাডজাস্ট করতে android:feature অ্যাট্রিবিউট ব্যবহার করুন।

Wear OS ওয়াচে RAM সীমিত থাকে এবং অ্যাপের UI ও ফিচার সেট অনেক বেশি সহজ হয়। আপনি watch ফিচারের জন্য নির্দিষ্ট করা আরও কম বাজেট ঘোষণা করতে পারেন:

<!-- General phone and tablet baseline -->
<memory-budget android:maxMb="180" />

<!-- Wear OS override: simpler UI and constrained hardware -->
<memory-budget
    android:maxMb="48"
    android:feature="watch" />

সমাধান সংক্রান্ত নিয়ম: শেষ প্রযোজ্য ধারা কার্যকর হয়

কোনও অ্যাপ্লিকেশন বা প্রসেসের জন্য একাধিক <memory-budget> এলিমেন্ট সংজ্ঞায়িত করার সময়, সিস্টেম সেগুলিকে ম্যানিফেস্টে ঘোষণা করা ক্রম অনুসারে মূল্যায়ন করে। শেষ প্রযোজ্য বাজেট ক্লজটি প্রয়োগ করা হয়।

যেহেতু শেষ প্রযোজ্য বাজেটটিই কার্যকর হয়, তাই ক্রম গুরুত্বপূর্ণ। সবসময় সবচেয়ে সাধারণ বেসলাইন বাজেট প্রথমে রাখুন, তারপরে আরও নির্দিষ্ট ওভাররাইড (যেমন রাজ্য-নির্দিষ্ট বা হার্ডওয়্যার-নির্দিষ্ট ক্লজ) রাখুন।

XML অ্যাট্রিবিউট রেফারেন্স

মেমরি সাইজের সমস্ত অ্যাট্রিবিউট মেগাবাইট (MB)-এ প্রকাশ করা হয় এবং Linux cgroup memory.current চার্জের সাথে ম্যাপ করা হয় (যেখানে Zygote-এর মতো শেয়ার করা মেমরি অন্তর্ভুক্ত থাকে না)।

অ্যাট্রিবিউট ফর্ম্যাট ডিফল্ট বর্ণনা
android:maxMb পূর্ণসংখ্যা (> ০) প্রয়োজনীয় এমবিতে বেসলাইন রেসিডেন্ট মেমরি বাজেট সীমা।
android:state Enum যেকোনও এই বাজেট যে প্রসেস স্টেটে প্রযোজ্য: foreground, perceptible, বা background। এটি বাদ দেওয়া হলে, কোনও অনির্দিষ্ট স্টেটের জন্য ক্লজটি ফলব্যাক হিসেবে কাজ করে।
android:additionalMbPerDensity পূর্ণসংখ্যা (≥ ০) 0 mdpi (১.০x)-এর তুলনায় ডিসপ্লে ডেনসিটি রেশিওর প্রতি ইউনিটের জন্য যোগ করতে হবে এমন অতিরিক্ত মেগাবাইট।
android:additionalBytesPerDisplayPixel পূর্ণসংখ্যা (≥ ০) 0 প্রতিটি ফিজিক্যাল ডিসপ্লে পিক্সেলের জন্য বরাদ্দ করা অতিরিক্ত বাইট (প্রস্থ × দৈর্ঘ্য), যা সারফেস বাফার ও বিটম্যাপের জন্য উপযোগী।
android:feature স্ট্রিং যেকোনও নির্দিষ্ট হার্ডওয়্যার ফিচার ঘোষণা করা ডিভাইসের ক্ষেত্রে ক্লজ সীমাবদ্ধ করে: watch, automotive বা leanback.

রানটাইম API (সেকেন্ডারি ডায়নামিক বিকল্প)

প্রায় সব অ্যাপের জন্য AndroidManifest.xml-এ স্ট্যাটিক বাজেট ঘোষণা করা হল পছন্দসই সমাধান। তবে, ডায়নামিক ওয়ার্কলোড সহ অ্যাপ্লিকেশন বা রানটাইম এক্সপেরিমেন্টের জন্য, Android রানটাইম SDK এবং NDK API-কে সেকেন্ডারি বিকল্প হিসেবে প্রদান করে।

রানটাইম API আপনাকে এগুলি করতে দেয়:

  • বর্তমান মেমরি ব্যবহার ও কার্যকর বাজেট কোয়েরি করুন।
  • আপনার প্রসেস বাজেট কমিয়ে ডায়নামিক অ্যাডজাস্ট করুন।
  • অপারেটিং সিস্টেম সরাসরি রিক্লেম ট্রিগার করার আগে ক্যাশে প্রোক্টিভভাবে ট্রিম করতে, ওভার-বাজেট ইভেন্ট শুনুন।

Android SDK API (MemoryBudgetManager)

Android 17 QPR2 (মাইনর SDK রিলিজ, API লেভেল 37.2 / Build.VERSION_CODES_FULL.CINNAMON_BUN_2) থেকে শুরু করে Kotlin ও Java-তে লেখা অ্যাপের জন্য MemoryBudgetManager সিস্টেম পরিষেবা উপলভ্য।

পরিষেবা রিট্রিভ করা

MemoryBudgetManager অ্যাক্সেস করার আগে, SDK_INT_FULL ব্যবহার করে চেক করুন যে ডিভাইসটি Android 17 QPR2-এর নিচের কোনও ভার্সনে চলছে না:

if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_2) {
    val budgetManager = context.getSystemService(MemoryBudgetManager::class.java)
}

কোয়েরি ব্যবহার ও বাজেট

// Query current memory charged to this process and the package UID
val processUsageBytes = budgetManager.processCurrentUsageBytes
val packageUsageBytes = budgetManager.packageCurrentUsageBytes

// Query effective budgets (returns LIMIT_IS_DISABLED if unconstrained)
val processBudgetBytes = budgetManager.processBudgetBytes
val packageBudgetBytes = budgetManager.packageBudgetBytes

ডায়নামিক বাজেট সেট করা বা মুছে দেওয়া

লাইটওয়েট টাস্ক চলাকালীন মেমরি সীমিত করতে রানটাইমে আরও কঠোর বাজেট সেট করতে পারেন অথবা টাস্ক শেষ হয়ে গেলে তা মুছে দিতে পারেন:

// Set a tighter dynamic budget on the current process (e.g., 96 MB)
try {
    budgetManager.processBudgetBytes = 96L * 1024L * 1024L
} catch (e: IllegalArgumentException) {
    // Thrown if the budget is <= 0 or exceeds the manifest ceiling or system limit
    Log.e(TAG, "Requested budget exceeds manifest or system ceiling", e)
}

// Clear the dynamic process budget to restore the manifest limit (or unconstrained baseline)
budgetManager.clearProcessBudget()
হয়।

পারফর্ম্যান্স ও আইডেম্পোটেন্সি

রানটাইমে MemoryBudgetManager-এর সাথে কাজ করার সময়, নিম্নলিখিত বৈশিষ্ট্যগুলি মনে রাখবেন:

  • আইডেম্পোটেন্সি: সব বাজেট মিউটেশন API আইডেম্পোটেন্ট। clearProcessBudget() বা clearPackageBudget()-এ বারবার কল করা অথবা কোনও ডায়নামিক বাজেট অ্যাক্টিভ না থাকলে কল করা নিরাপদ নো-অপ। একইভাবে, processBudgetBytes বা setPackageBudgetBytes()-এর মান পরপর একই সেট করলে অপ্রয়োজনীয় সিস্টেম রিকনফিগারেশন ট্রিগার হয় না।
  • কল করার ফ্রিকোয়েন্সি: বাজেট সেট, আপডেট বা ক্লিয়ার করলে, প্রসেসের জন্য অপারেটিং সিস্টেমের মেমরি কন্ট্রোলার আপডেট করতে সিস্টেম সার্ভিসে একটি ক্রস-প্রসেস কল করা হয়। এটি লাইটওয়েট হলেও, সম্পূর্ণ ফ্রি নয়। ডিসক্রিট লাইফসাইকেল ট্রানজিশন ও টাস্ক মাইলস্টোন (যেমন, অ্যাক্টিভিটি শুরু বা শেষ করা, ব্যাকগ্রাউন্ড প্রসেসিং শুরু বা সম্পূর্ণ করা অথবা স্পিচ সংক্রান্ত অনুরোধ ম্যানেজ করা) চলাকালীন এইসব API কল করুন এবং টাইট লুপ, অডিও প্রসেসিং থ্রেড বা পার-ফ্রেম রেন্ডার রুটিনে এগুলি কল করা এড়িয়ে চলুন।
  • কোয়েরি ফ্রিকোয়েন্সি: কোয়েরি প্রপার্টি processCurrentUsageBytes এবং packageCurrentUsageBytes সিস্টেম পরিষেবা থেকে লাইভ মেমরি ব্যবহার সংক্রান্ত তথ্য নেয়। দ্রুত হলেও, কোয়েরিগুলি চাহিদা অনুযায়ী (যেমন, টাস্ক ট্রানজিশন বা OnOverBudgetListener কলব্যাকের মধ্যে) ইনভোক করা উচিত, ক্রমাগত লুপে পোল করা উচিত নয়।

বাজেট বেশি হয়ে যাওয়া সংক্রান্ত কলব্যাক শুনুন

মেমরি ব্যবহার বাজেট থ্রেশহোল্ডের বেশি হলে বিজ্ঞপ্তি পেতে অ্যাপ লিসনার রেজিস্টার করতে পারে। এর ফলে অপারেটিং সিস্টেম সরাসরি রিক্লেম লেটেন্সি ট্রিগার করার আগে অ্যাপটি প্রোঅ্যাক্টিভ অ্যাপ্লিকেশন-লেভেল পরিষ্কার (যেমন, ইন-মেমরি বিটম্যাপ ক্যাশে পরিষ্কার করা) করতে পারে:

val listener = MemoryBudgetManager.OnOverBudgetListener { budgetBytes ->
    Log.w(TAG, "Process exceeded memory budget of $budgetBytes bytes")
    // Proactively evict caches to release memory
    imageTileCache.evictAll()
}

// Register on the main Looper
budgetManager.registerProcessOverBudgetListener(mainLooper, listener)

// When done (e.g., in onStop)
budgetManager.unregisterProcessOverBudgetListener(listener)

বাজেটের বাইরে কলব্যাক করার পেশাদার পদ্ধতি:

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

নেটিভ NDK API (<android/memory_budget_manager.h>)

নেটিভ অ্যাপ libandroid.so-এর মাধ্যমে এক্সপোজ করা C NDK API ব্যবহার করতে পারে যা Android 17 QPR2 (API লেভেল 37.2) থেকে শুরু হয়।

CMake কনফিগারেশন

find_library(android-lib android)
target_link_libraries(my_native_engine PRIVATE ${android-lib})

হেডার ও কোয়েরি ব্যবহার অন্তর্ভুক্ত করুন

#include <android/memory_budget_manager.h>

// Query current memory usage
int64_t process_usage = AMemoryBudgetManager_getProcessCurrentUsageBytes();
int64_t package_usage = AMemoryBudgetManager_getPackageCurrentUsageBytes();

// Query current budget
int64_t process_budget = 0;
AMemoryBudgetResult result = AMemoryBudgetManager_getProcessBudget(&process_budget);
if (result == AMEMORY_BUDGET_RESULT_SUCCESS) {
    // Current budget available in process_budget
} else if (result == AMEMORY_BUDGET_RESULT_LIMIT_IS_DISABLED) {
    // No budget is currently active
}

নেটিভ বাজেট ডায়নামিক কনফিগার করা

// Set a tighter process budget (e.g. 160MB)
AMemoryBudgetResult result = AMemoryBudgetManager_setProcessBudget(160LL * 1024 * 1024);
if (result != AMEMORY_BUDGET_RESULT_SUCCESS) {
    const char* error_msg = AMemoryBudgetManager_resultToString(result);
    // Handle error (e.g. AMEMORY_BUDGET_RESULT_ERROR_EXCEEDS_MANIFEST_LIMIT)
}

// Clear the dynamic budget to resume manifest limits (or unconstrained baseline)
AMemoryBudgetManager_clearProcessBudget();

মেমরি প্রেসার ইভেন্ট মনিটর করা

NDK, মেমরি ইভেন্ট মনিটর করার দুটি উপায় প্রদান করে:

  1. হাই-লেভেল ওয়াচার (AMemoryBudgetManager_Watcher_create): অটোমেটিক ডিবাউন্সিং সহ ALooper-এ ইভেন্ট মনিটর করে।
  2. লো-লেভেল ফাইল ডেসক্রিপ্টর: AMemoryBudgetManager_getProcessMemoryPressureFd একটি নেটিভ ফাইল ডেসক্রিপ্টর রিটার্ন করে যা সরাসরি কাস্টম epoll ইঞ্জিন লুপে ইন্টিগ্রেট করা যেতে পারে।
void onMemoryPressure(int32_t event_mask, const AMemoryBudgetEvents* events, void* userdata) {
    // High-yield eviction of unused native textures or geometry caches
    purgeNativeTextureCaches();
}

// Register watcher on an ALooper with a 1000ms debounce interval
AMemoryBudgetManagerWatcher* watcher = AMemoryBudgetManager_Watcher_create(
    looper,
    AMEMORY_BUDGET_MANAGER_EVENT_PROCESS,
    1000 /* debounce_ms */,
    &onMemoryPressure,
    NULL /* userdata */
);

// When done:
AMemoryBudgetManager_Watcher_destroy(watcher);

রানটাইম API-এর উদাহরণ

নিচের উদাহরণগুলি থেকে বোঝা যায় যে কীভাবে Android SDK API (Kotlin-এ লেখা) এবং Native NDK API (C++-এ লেখা) ব্যবহার করে রানটাইম API প্রয়োগ করতে হয়।

Android SDK উদাহরণ: অ্যাডাপ্টিভ ইমেজ এডিটর

এই উদাহরণে একটি ছবি এডিটিং অ্যাপ (com.example.imageeditor) দেখানো হয়েছে, যার ম্যানিফেস্টে মাল্টি-লেয়ার ক্যানভাসের জন্য ২৫৬ এমবি সর্বোচ্চ সীমা ঘোষণা করা হয়েছে:

<manifest ... >
    <application ... >
        <!-- Manifest ceiling accommodates the heaviest editing workload -->
        <memory-budget android:maxMb="256" />
    </application>
</manifest>

ব্যবহারকারী যখন লাইটওয়েট থাম্বনেল গ্যালারি ব্রাউজ করছেন, তখন অ্যাপটি Kotlin-এ Android SDK API ব্যবহার করে ডায়নামিক পদ্ধতিতে এর প্রসেস বাজেটকে ৯৬ এমবিতে কমিয়ে দেয়। ব্যবহারকারী মাল্টি-লেয়ার এডিটিং ক্যানভাস খুললে, সম্পূর্ণ ২৫৬MB ম্যানিফেস্ট সিলিং রিস্টোর করার জন্য অ্যাপটি ডায়নামিক বাজেট মুছে দেয়। এছাড়াও, এটি OnOverBudgetListener রেজিস্টার করে যাতে বেশি চাপ পড়লে ক্যাশে করা প্রিভিউ বিটম্যাপ সরিয়ে দেওয়া যায়।

package com.example.imageeditor.ui

import android.app.Activity
import android.app.MemoryBudgetManager
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.util.LruCache

class ImageEditorActivity : Activity() {

    private lateinit var budgetManager: MemoryBudgetManager

    // In-memory cache for rendered preview tiles (32MB limit)
    private val previewCache = object : LruCache<String, Bitmap>(32 * 1024 * 1024) {
        override fun sizeOf(key: String, value: Bitmap): Int = value.byteCount
    }

    private val overBudgetListener = MemoryBudgetManager.OnOverBudgetListener { budgetBytes ->
        Log.w(TAG, "Process memory pressure detected (budget: ${budgetBytes / 1048576}MB). Evicting preview cache.")
        previewCache.evictAll()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        budgetManager = getSystemService(MemoryBudgetManager::class.java)
    }

    override fun onStart() {
        super.onStart()
        // Register listener for process-level memory breaches
        budgetManager.registerProcessOverBudgetListener(mainLooper, overBudgetListener)
        // Constrain memory during lightweight gallery browsing
        applyGalleryBudget()
    }

    override fun onStop() {
        super.onStop()
        budgetManager.unregisterProcessOverBudgetListener(overBudgetListener)
    }

    /**
     * Called when the user enters the high-resolution editing canvas.
     * Clears the dynamic budget, restoring the full 256MB manifest ceiling.
     */
    fun enterEditingCanvas() {
        // Clear the tighter dynamic budget to restore the full manifest ceiling (256MB)
        budgetManager.clearProcessBudget()
        Log.i(TAG, "Restored manifest budget ceiling (256MB) for editing canvas")
    }

    /**
     * Called when the user exits the editor back to the thumbnail gallery.
     * Re-applies the tighter dynamic budget.
     */
    fun exitToGallery() {
        previewCache.trimToSize(8 * 1024 * 1024)
        applyGalleryBudget()
    }

    private fun applyGalleryBudget() {
        try {
            // Dynamically tighten budget to 96MB for the lightweight gallery view
            budgetManager.processBudgetBytes = 96L * 1024L * 1024L
            Log.i(TAG, "Tighter dynamic budget applied for gallery: 96MB")
        } catch (e: IllegalArgumentException) {
            Log.e(TAG, "Could not apply dynamic budget", e)
        }
    }

    companion object {
        private const val TAG = "ImageEditor"
    }
}

NDK C++ উদাহরণ: নেটিভ 3D ইঞ্জিন

এই উদাহরণে, অ্যাক্টিভ গ্রাফিক্স কোয়ালিটি লেভেলের উপর ভিত্তি করে মেমরি বাজেট ম্যানেজ করা একটি নেটিভ C++ গেম ইঞ্জিন দেখানো হয়েছে। এখানে ধরে নেওয়া হয়েছে যে অ্যাপ্লিকেশন ম্যানিফেস্ট হাই কোয়ালিটি গ্রাফিক্স (android:maxMb="512") অ্যাডজাস্ট করার জন্য ৫১২ এমবি সিলিং ঘোষণা করে। কম কোয়ালিটি প্রিসেটের জন্য ইঞ্জিন ডাইনামিক বাজেট কম করে এবং বাজেট বেশি হয়ে গেলে টেক্সচার মিপম্যাপ আনলোড করার জন্য ALooper-এ AMemoryBudgetManager_Watcher_create ব্যবহার করে।

#include <android/memory_budget_manager.h>
#include <android/looper.h>
#include <android/log.h>

#define LOG_TAG "Native3DEngineMemory"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)

class MemoryGovernor {
public:
    MemoryGovernor() : mWatcher(nullptr) {}

    ~MemoryGovernor() {
        stopMonitoring();
    }

    // Configures process budget based on user graphics quality settings
    bool setQualityBudget(int qualityLevel) {
        int64_t targetBytes = 0;
        switch (qualityLevel) {
            case 0: // Low (budget: 128MB)
                targetBytes = 128LL * 1024 * 1024;
                break;
            case 1: // Medium (budget: 256MB)
                targetBytes = 256LL * 1024 * 1024;
                break;
            case 2: // High (budget: 512MB)
                targetBytes = 512LL * 1024 * 1024;
                break;
            default:
                // Clear dynamic override and restore manifest limit
                AMemoryBudgetManager_clearProcessBudget();
                return true;
        }

        AMemoryBudgetResult result = AMemoryBudgetManager_setProcessBudget(targetBytes);
        if (result != AMEMORY_BUDGET_RESULT_SUCCESS) {
            LOGW("Could not set quality budget: %s", AMemoryBudgetManager_resultToString(result));
            return false;
        }
        return true;
    }

    bool startMonitoring(ALooper* looper) {
        if (!looper) return false;

        // Monitor process budget events, debounced to at most once every 1000ms
        mWatcher = AMemoryBudgetManager_Watcher_create(
            looper,
            AMEMORY_BUDGET_MANAGER_EVENT_PROCESS,
            1000,
            &MemoryGovernor::onPressureEvent,
            this
        );
        return mWatcher != nullptr;
    }

    void stopMonitoring() {
        if (mWatcher) {
            AMemoryBudgetManager_Watcher_destroy(mWatcher);
            mWatcher = nullptr;
        }
    }

    void unloadUnusedTextures() {
        LOGW("Memory pressure callback triggered. Purging cached texture mipmaps...");
        // Fast, high-yield eviction without allocating memory
    }

private:
    static void onPressureEvent(
        int32_t event_mask,
        const AMemoryBudgetEvents* events,
        void* userdata
    ) {
        auto* governor = static_cast<MemoryGovernor*>(userdata);
        governor->unloadUnusedTextures();
    }

    AMemoryBudgetManagerWatcher* mWatcher;
};