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

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

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

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

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

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

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

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

OS কীভাবে মেমরি ট্র্যাক করে এবং ফিরিয়ে আনে তা জানতে, মেমরি আর্কিটেকচার গাইড দেখুন:

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

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

Android 17 QPR2 (API লেভেল ৩৭.২) এবং তার পরবর্তী যেকোনও ভার্সনে চলা ডিভাইসে <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 অবস্থায় ট্রানজিশন করলে পরবর্তী আরও বেশি সীমাবদ্ধ ক্লজ ওভাররাইড করে।

প্রতিটি স্টেটের জন্য বাজেটের সাইজ নির্ধারণ করার সময়, পিক রেসিডেন্ট মেমরির (RSS হাই-ওয়াটার মার্ক) পরিবর্তে সেই স্টেটের অ্যাক্টিভ ওয়ার্কিং সেটকে টার্গেট করুন:

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

ধাপে ধাপে ওয়ার্কফ্লোর জন্য, আপনার ওয়ার্কিং সেট পরিমাপ করুন এবং বাজেট ক্যালিব্রেট করুন দেখুন।

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

প্ল্যাটফর্মটি RunningAppProcessInfo.importance-এর উপর ভিত্তি করে রানটাইম প্রসেস স্টেটে ম্যানিফেস্ট বাজেট স্টেট ম্যাপ করে, যা অ্যাক্টিভিটি ম্যানেজার প্রসেসের নিজস্ব অ্যাক্টিভ কম্পোনেন্ট এবং অন্যান্য অ্যাপ থেকে আসা যেকোনও সার্ভিস বাইন্ডিং বা কন্টেন্ট প্রোভাইডার কোয়েরি থেকে গণনা করে:

  • foreground: অ্যাক্টিভ, দৃশ্যমান ব্যবহারকারীর ইন্টার‍্যাকশন, যেমন আবার শুরু করা অ্যাক্টিভিটি হোস্ট করা, স্ক্রিন বন্ধ হয়ে গেলে টপ অ্যাপের স্ট্যাটাস বজায় রাখা, অথবা টপ ফোরগ্রাউন্ড অ্যাপের মাধ্যমে অ্যাক্টিভভাবে বাউন্ড বা কোয়েরি করা কোনও পরিষেবা বা কন্টেন্ট প্রোভাইডার হোস্ট করা।
  • perceptible: দৃশ্যমান উইন্ডো ছাড়াই ব্যবহারকারীর কাছে বোধগম্য ওয়ার্কলোড, যেমন অ্যাক্টিভ মিডিয়া প্লেব্যাক, ধাপে ধাপে নেভিগেশন, ক্যামেরা বা মাইক্রোফোন ক্যাপচার, অ্যাক্টিভ ব্যাকগ্রাউন্ড ডাউনলোড, ডেটা সিঙ্ক ফোরগ্রাউন্ড সার্ভিস বা ফোরগ্রাউন্ড ক্লায়েন্ট দ্বারা আবদ্ধ পরিষেবা, যেমন BIND_NOT_FOREGROUND (BIND ফ্ল্যাগ সহ ইনহেরিটেন্স কন্ট্রোল করা দেখুন)। ইন্টার্নাল প্ল্যাটফর্ম মেট্রিক এইসব ওয়ার্কলোডের কিছু অংশকে 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
ব্যাকগ্রাউন্ড জব, রিসিভার, ব্যাকগ্রাউন্ড সিঙ্ক

ক্লায়েন্ট বাইন্ডিং কীভাবে কোনও পরিষেবা বা প্রোভাইডার হোস্ট প্রসেসকে উন্নত করে এবং ক্লায়েন্ট আনবাইন্ড বা অফ-স্ক্রিনে চলে যাওয়ার পরে কীভাবে প্রসেসটি আবার background বা cached-এ ফিরে আসে সেই সম্পর্কে বিস্তারিত জানতে, পরিষেবা বাইন্ডিং ও প্রসেস স্টেট দেখুন।

কোনও প্রসেস কোনও অ্যাক্টিভ কম্পোনেন্ট ছাড়া ক্যাশে করা অবস্থায় (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>

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

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

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

  1. প্রধান প্রসেস: দৃশ্যমান UI এবং অডিও প্লেব্যাক ইঞ্জিন হোস্ট করে (MediaSessionService mediaPlayback ফোরগ্রাউন্ড পরিষেবার সাথে)। দেখা গেলে, প্রসেসটি ১৮০ এমবি ফোরগ্রাউন্ড বাজেটের অধীনে কাজ করে। মিউজিক চলাকালীন ব্যবহারকারী অ্যাপ থেকে বেরিয়ে গেলে, প্রসেসটি 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" />

        <processes>
            <!-- Dedicated background sync process -->
            <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>

আপনার ম্যানিফেস্টে <processes> ব্লক ঘোষণা করার সময়, শুধুমাত্র <memory-budget> ঘোষণা করা প্রসেসের জন্য <process> এন্ট্রি তালিকাভুক্ত করুন। আপনি যদি শুধুমাত্র নির্দিষ্ট সাবপ্রসেসের জন্য বাজেট সেট করতে চান এবং অ্যাপের বাকি অংশ ডিভাইসের সিস্টেম সীমাতে রাখতে চান, তাহলে আপনি সরাসরি <application>-এর অধীনে <memory-budget> এলিমেন্ট বাদ দিতে পারেন।

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

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

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

  • ডিসপ্লে ডেনসিটি বাকেট (android:additionalMbPerDensity) অনুযায়ী স্কেল করা: ডিসপ্লের ডেনসিটি অনুপাতের বর্গক্ষেত্রের সাথে আনুপাতিকভাবে মেগাবাইট যোগ করে, যা mdpi (1.0x / 160 dpi)-এর সাথে সম্পর্কিত, কারণ 2D রাস্টার ড্রয়েবল এবং UI অ্যাসেট প্রস্থ ও উচ্চতা জুড়ে স্কেল করে:

    <!-- Baseline 180MB + 16MB multiplied by (density ratio)^2 -->
    <memory-budget
        android:maxMb="180"
        android:additionalMbPerDensity="16" />
    

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

  • ফিজিক্যাল ডিসপ্লে রেজোলিউশন অনুযায়ী স্কেল করা (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 ডিসপ্লেতে (১০৮০ × ২৪০০ &approx; ২.৫৯M পিক্সেল), এটি বেসলাইন বাজেটে &approx; ৪১.৪ এমবি যোগ করে। ১৪৪০p ডিসপ্লেতে (১৪৪০ × ৩১২০ &approx; ৪.৪৯M পিক্সেল), এটি &approx; ৭১.৮ এমবি যোগ করে।

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

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

ফোন, ট্যাবলেট ও 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 অ্যাট্রিবিউট রেফারেন্স

সব মেমরি সাইজ অ্যাট্রিবিউট মেগাবাইটে (এমবি) প্রকাশ করা হয় এবং 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।

আপনার ওয়ার্কিং সেট পরিমাপ করুন এবং বাজেট ক্যালিব্রেট করুন

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

ওয়ার্কিং সেট বনাম রেসিডেন্ট মেমরি বোঝা

বাজেট ছাড়া, আপনার অ্যাপের রেসিডেন্ট ফুটপ্রিন্ট (memory.current) দুই ধরনের মেমরি ধরে রাখে:

  • ওয়ার্কিং সেট: Java, Kotlin ও নেটিভ হিপ এবং হট ফাইল-ব্যাকড পৃষ্ঠা (DEX কোড, নেটিভ লাইব্রেরি ও অ্যাসেট) যা বর্তমান ব্যবহারকারীর ফ্লো বারবার টাচ করে
  • কোল্ড স্ল্যাক: আগের স্ক্রিন থেকে বিটম্যাপ ক্যাশে, এক-বার স্টার্টআপ বাফার, অ্যালোকেশন বার্স্টের পরে হিপ স্ল্যাক বাকি থাকা এবং ফাইল পেজ যা একবার পড়া হয়েছে এবং আর কখনও স্পর্শ করা হয়নি

আপনার আনকনস্ট্রেইন্ড পিক বাজেটের মাধ্যমে কখনও কোল্ড স্ল্যাক রিক্লেম করা যায় না। আপনার ওয়ার্কিং সেটের থেকে কম বাজেট থাকলে, কার্নেল হিপ পৃষ্ঠা সোয়াপ আউট করে এবং কোড পৃষ্ঠা বের করে দেয়, যার ফলে আপনার অ্যাপে অবিলম্বে আবার সমস্যা দেখা দেয়। এই ওয়ার্কিং-সেট রিফল্ট সিপিইউ টাইম ও ব্যাটারি খরচ করে এবং UI থ্রেডে চলে এলে, ফ্রেমও খরচ করে।

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

আপনার টেস্ট এনভায়রনমেন্ট প্রস্তুত করা

প্রোডাকশনের মতো কাজ করে এমন একটি রিপিটেবল টেস্ট সেট-আপ করুন:

  • আপনি যে R8-অপ্টিমাইজ করা, নন-ডিবাগেবল রিলিজ বিল্ড শিপ করেন, সেটি ক্যালিব্রেট করুন। ডিবাগ বিল্ট বেশি মেমরি ব্যবহার করে, তাই ডিবাগ বিল্টে টিউন করা বাজেট রিলিজের জন্য খুব বেশি।
  • Android 17 QPR2 (API লেভেল ৩৭.২) বা তার পরবর্তী যেকোনও ভার্সন চলছে এমন ডিভাইস বা এমুলেটরে পরীক্ষা করুন। Google Play ছাড়া userdebug বিল্ড বা এমুলেটর ইমেজ সবচেয়ে ভালো, কারণ কিছু ডিভাইস আপনাকে adb root চালানোর পরে নিম্নলিখিত বিভাগে শুধুমাত্র cgroup ফাইল পড়তে দেয়।
  • Jetpack Macrobenchmark, UI Automator বা adb shell input-এর মাধ্যমে ৩০ থেকে ৬০ সেকেন্ডের ইউজার ফ্লো স্ক্রিপ্ট করুন, আপনি বাজেট করার পরিকল্পনা করছেন এমন প্রতিটি অবস্থার জন্য একবার। কোনও অ্যাপ কাজ না করলে, তার ওয়ার্কিং সেট সম্পর্কে কিছুই জানা যায় না।
    • foreground: ঘন তালিকা ও মিডিয়া গ্রিড স্ক্রল করুন, আপনার প্রধান গন্তব্যের মধ্যে ঘুরে বেড়ান, মেনু ও ডায়ালগ খুলুন এবং অ্যানিমেশন চালান
    • perceptible: কোনও কাজ (যেমন, অডিও প্লেব্যাক বা ডাউনলোড) চলাকালীন দৃশ্যমান উইন্ডো থেকে বেরিয়ে আসুন, ১০ থেকে ১৫ সেকেন্ডের জন্য এটি চলতে দিন এবং তারপরে অ্যাপে ফিরে আসুন
    • background: অ্যাপের কোনও দৃশ্যমান UI না থাকলে, সিঙ্ক, WorkManager জব (যেমন, adb shell cmd jobscheduler run -f <package-name> <job-id>-এর সাথে), শুরু করা পরিষেবা বা ব্রডকাস্ট রিসিভার ট্রিগার করা

ব্যাকগ্রাউন্ডে কাজ চলাকালীন background বাজেট পরিমাপ করুন। প্রসেসটি ক্যাশ করা হয়ে গেলে, প্ল্যাটফর্মটি তার বাজেট রিলিজ করে (কীভাবে বাজেট স্টেট প্রসেস স্টেটে ম্যাপ করে দেখুন), তাই একটি নিষ্ক্রিয় ক্যাশ করা প্রসেস আপনাকে আপনার background বাজেট সম্পর্কে কিছু বলে না।

আপনার বেসলাইন পরিমাপ করুন

বাজেট নেই এমন রান দিয়ে শুরু করুন, যাতে আপনি বুঝতে পারেন যে স্বাভাবিক রান কেমন হয়। <memory-budget> ঘোষণা ছাড়া একটি বিল্ড ব্যবহার করুন এবং প্রতিটি রান একটি নতুন প্রসেস (adb shell am force-stop <package-name>) থেকে শুরু করুন।

প্রতিটি অ্যাপ প্রসেসের নিজস্ব cgroup v2 মেমরি কন্ট্রোলার আছে। /proc/<PID>/cgroup-এর 0:: লাইন থেকে এর পাথ দেখুন, নিজে পাথ তৈরি করবেন না, কারণ cgroup লেআউট ডিভাইস অনুযায়ী আলাদা হতে পারে। প্রতিটি ডিভাইস কমান্ডকে adb shell-এ সিঙ্গেল কোট স্ট্রিং হিসেবে পাস করুন যাতে ডিভাইস শেল কোট দেখতে পায়:

PID=$(adb shell pidof -s <package-name>)
CG=/sys/fs/cgroup$(adb shell "sed -n 's/^0:://p' /proc/$PID/cgroup")
adb shell "cat $CG/memory.current $CG/memory.swap.current"
adb shell "grep -E '^(anon|file|workingset_refault_.*) ' $CG/memory.stat"

এইসব ফাইল রিড করা সস্তা এবং আপনার অ্যাপে কোনও সমস্যা তৈরি করে না, তাই রান করার সময় আপনি প্রতি সেকেন্ডে এগুলি স্যাম্পেল করতে পারবেন:

  • memory.current: প্রসেসে চার্জ করা রেসিডেন্ট মেমরি, বাইটে। এটি হল সেই সংখ্যা যা <memory-budget> সীমিত করে।
  • memory.swap.current: zRAM সোয়াপে কম্প্রেস করা মেমরি, বাইটে
  • anon এবং file memory.stat-এ: রেসিডেন্ট অ্যানোনিমাস এবং ফাইল-ব্যাকড মেমরি, বাইটে
  • memory.stat-এ workingset_refault_anon এবং workingset_refault_file: আপনার অ্যাপের আবার প্রয়োজন হয়েছে এমন সোয়্যাপ-আউট বা উচ্ছেদ করা পৃষ্ঠার রানিং কাউন্ট। এইসব কাউন্টার হল আপনার প্রধান চিহ্ন যে বাজেট খুব কঠোর।

অনুমতি সংক্রান্ত কোনও সমস্যা হলে, adb root রান করে আবার চেষ্টা করুন। আপনি রুট অ্যাক্সেস করতে না পারলে, adb shell dumpsys meminfo <package-name> আপনাকে একটি মোটা ভিউ দেয়। App Summary, Java Heap, Native Heap এবং Stack-এ বেশিরভাগই বেনামী মেমরি এবং Code ফাইল-ব্যাকড। সফ্টওয়্যার বিটম্যাপ Native Heap এবং আপনার বাজেটের দিকে গণনা করে, যখন হার্ডওয়্যার বিটম্যাপ এবং Graphics না। ফ্রেম টাইমিং পরিমাপ করার সময় লুপে dumpsys meminfo রান করবেন না। এটি আপনার অ্যাপে গার্বেজ কালেকশন করতে বাধ্য করে এবং এর সব মেমরি ম্যাপিংয়ের মাধ্যমে চলে, যা আপনি যা পরিমাপ করার চেষ্টা করছেন তা পরিবর্তন করে।

প্রার্থীর বাজেট সুইপ করুন

আপনি অ্যাপটি আবার তৈরি না করেই ক্যান্ডিডেট বাজেট ব্যবহার করে দেখতে পারেন। চলমান প্রসেসের জন্য বাজেট সেট করতে am memory-limiter manual এবং এটি সরাতে none ব্যবহার করুন:

adb shell am memory-limiter manual <PID> 120MB
adb shell am memory-limiter manual <PID> none

প্রসেস স্টেট পরিবর্তন জুড়ে, সেই প্রসেস ইনস্ট্যান্সের লাইফটাইমের জন্য ম্যানুয়াল ওভাররাইড থাকে, তাই আপনি এগিয়ে যাওয়ার আগে এটি মুছে দিন বা প্রসেসটি রিস্টার্ট করুন। এটি প্রসেসের হার্ড লিমিট (memory.max) একই ভ্যালুতে সেট করে, যাতে আপনার অ্যাপের ওয়ার্কিং সেটের নিচে থাকা ক্যান্ডিডেট বাজেট প্রসেসকে শুধু স্লো ডাউন করার পরিবর্তে বন্ধ করে দিতে পারে। সেটিকে ব্যর্থ বাজেট হিসেবে বিবেচনা করুন।

এছাড়াও, আপনি ইনস্ট্রুমেন্টেশন আর্গুমেন্ট থেকে ক্যান্ডিডেট ভ্যালু পড়ে এবং MemoryBudgetManager.processBudgetBytes সেট করে বেঞ্চমার্ক বিল্ড থেকে সুইপ করতে পারেন। রানটাইম বাজেট শুধুমাত্র ম্যানিফেস্ট বাজেটকে আরও কঠোর করতে পারে, তাই <memory-budget> ঘোষণা ছাড়াই বিল্ডে সুইপ করুন। আপনি clearProcessBudget কল না করা পর্যন্ত এটি প্রসেস স্টেট পরিবর্তন জুড়েও কার্যকর থাকে।

আপনার বেসলাইন থেকে সর্বোচ্চ memory.current-এ শুরু করুন এবং একবারে ১০ থেকে ১৫ এমবি কমান। প্রতিটি সম্ভাব্য বাজেটের জন্য:

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

    adb shell dumpsys gfxinfo <package-name> reset
    adb shell "grep -E '^workingset_refault_(anon|file) ' $CG/memory.stat"
    adb shell "cut -d ' ' -f 14,15 /proc/$PID/stat"
  4. আপনার ইউজার ফ্লোর পরিমাপ করা পাস রান করুন।

  5. memory.current, anon, file, রিফল্ট কাউন্টার ও সিপিইউ টাইম আবার রেকর্ড করুন এবং ফ্রেমের পরিসংখ্যান সংগ্রহ করুন। dumpsys gfxinfo একই পরিসংখ্যান একাধিকবার প্রিন্ট করতে পারে, তাই প্রথম ম্যাচটি নিন, যা পুরো অ্যাপ কভার করে:

    adb shell "dumpsys gfxinfo <package-name>" | grep -m 1 'Janky frames:'
    adb shell "dumpsys gfxinfo <package-name>" | grep -m 1 '95th percentile'

প্রতিটি প্রার্থীকে অন্তত তিনবার রিপিট করুন এবং একজন প্রার্থীর সব রিপিটেশন পরপর চালানোর পরিবর্তে প্রার্থীদের ইন্টারলিভ করুন। প্রতিটি ফলাফলকে আপনার বেসলাইন রানের স্প্রেডের সাথে তুলনা করুন, একটি রানের সাথে নয়।

ফলাফল পড়ুন

বাজেট কমে যাওয়ার সাথে সাথে, আপনার অ্যাপ তিনটি শাসন ব্যবস্থার মধ্য দিয়ে যায়:

  • হাঁটুর উপরে: memory.current কোল্ড file পৃষ্ঠা এবং অলস হিপ শেড করে বাজেট কমিয়ে দেয়। anon ফ্ল্যাট থাকে, রিফল্ট বেসলাইনের কাছাকাছি থাকে এবং সিপিইউ টাইম ও ফ্রেম টাইম পরিবর্তন হয় না। আপনি এখানেই থাকতে চান।
  • নিচের স্তরে: কোল্ড পেজগুলি চলে গেছে, তাই কার্নেল আপনার অ্যাপ এখনও ব্যবহার করে এমন হিপ পেজগুলি সোয়্যাপ আউট করা শুরু করে। প্রতিটি রান এবং সিপিইউ টাইম বেড়ে যাওয়ার ফলে রিফল্ট শত শত বা হাজার হাজার পৃষ্ঠা লাফিয়ে যায়। ফ্রেম টাইম হয়ত অন্যথায় আইডল টেস্ট ডিভাইসে খুব কমই পরিবর্তিত হয়, তবে আপনার অ্যাপ এখন সিপিইউ টাইম এবং ব্যাটারি খরচ করে নিজের ওয়ার্কিং সেট কম্প্রেস এবং ডিকম্প্রেস করে।
  • হাঁটুর অনেক নিচে: রিক্লেম ও সোয়াপ-ইন ফল্ট UI থ্রেড এবং RenderThread-এ ল্যান্ড করে এবং জ্যাঙ্ক, ফ্রেম টাইম ও ওয়ার্ম রিজিউম টাইম সব বেড়ে যায়।

আপনার আনকনস্ট্রেইন্ড বেসলাইনের সাথে তুলনা করে, কোনও ক্যান্ডিডেট বাজেট পাস করে যদি এটি নিম্নলিখিত সবকটি পূরণ করে, যা একটি যুক্তিসঙ্গত স্টার্টিং পয়েন্ট:

  • প্রসেসটি রান করার পরেও থেকে যায়
  • ডিফল্ট ডেল্টা বেসলাইনের কাছাকাছি থাকে
  • প্রসেস সিপিইউ টাইম ৫%-এর বেশি বৃদ্ধি পায় না
  • জ্যাঙ্কি ফ্রেম ১ শতাংশ পয়েন্টের বেশি বৃদ্ধি পায় না
  • P95 ফ্রেম টাইম ৩ মিলিসেকেন্ডের বেশি বাড়ে না
  • perceptible এবং background বাজেটের জন্য, ওয়ার্ম রিজিউম টাইম (adb shell am start -W থেকে TotalTime) ১৫%-এর বেশি বৃদ্ধি পায় না

নী হল সবচেয়ে কম সম্ভাব্য বাজেট যা পাস করে। যেমন, এখানে একটি কাল্পনিক অ্যাপের জন্য ফোরগ্রাউন্ড সুইপ দেখানো হয়েছে যার আনকনস্ট্রেনড পিক হল ১৩৮ এমবি:

প্রার্থীর বাজেট memory.current anon file রিফল্ট (পৃষ্ঠা) P95 ফ্রেম টাইম শাসনকাল
কোনওটিই নয় ১৩৮ এমবি ৭৫ এমবি ৬৩ এমবি 0 ১০.৫ মিলিসেকেন্ড বেসলাইন
১২৫ এমবি ১২১ এমবি ৭৪ এমবি ৪৭ এমবি 10 ১০.৫ মিলিসেকেন্ড হাঁটুর উপরে
১১০ এমবি ১১০ এমবি ৭৫ এমবি ৩৫ এমবি 15 ১০.৮ মিলি.সে. হাঁটু
৯৫ এমবি ৯৫ এমবি ৭৪ এমবি ২১ এমবি 176 ১১.২ মিলিসেকেন্ড হাঁটুর নিচে
৮৫ এমবি ৮৫ এমবি ৬৮ এমবি ১৭ এমবি ২,০৫০ ১২.৮ মিলি.সে. হাঁটুর নিচে
৭০ এমবি ৭০ এমবি ৫৪ এমবি ১৬ এমবি 8,771 ১৬.৫ মিলি.সে. হাঁটুর অনেক নিচে

আপনার বাজেট বেছে নিন

android:maxMb-কে হাঁটুর ১০% থেকে ২০% উপরে সেট করুন, যাতে ব্যবহারকারীর ডেটা, লোকেল এবং বরাদ্দকরণ বার্স্টের পার্থক্য আপনার অ্যাপকে এর নিচে ঠেলে না দেয়। আগের উদাহরণে, নি ১১০ এমবিতে আছে, তাই ১২৫ এমবি বেছে নেওয়া ভাল।

তারপর নতুন <memory-budget> ডিক্লারেশন সহ রিলিজ বিল্ড ইনস্টল করুন, আপনার ইউজার ফ্লো আবার রান করুন এবং পাস করার মাপকাঠি এখনও বজায় আছে কিনা তা চেক করুন। dumpsys meminfo তার মেমরি বাজেট বিভাগে অ্যাক্টিভ বাজেট এবং তার সোর্স দেখায়।

একটি ওয়ার্ম রিটার্ন সহ নন-ফোরগ্রাউন্ড বাজেট যাচাই করুন

আপনার অ্যাপ সেই অবস্থায় থাকাকালীন perceptible বা background-এর জন্য কম বাজেট নির্ধারণ করা ঠিক মনে হলেও, ব্যবহারকারী ফিরে এলে আপনার খরচ বেড়ে যেতে পারে। ফেরার ট্রিপও পরীক্ষা করুন:

  1. ফোরগ্রাউন্ড ফ্লো রান করুন, অ্যাপকে টার্গেট স্টেটে নিয়ে যান এবং am memory-limiter manual-এর সাথে ক্যান্ডিডেট বাজেট প্রয়োগ করুন।
  2. ১০ থেকে ১৫ সেকেন্ডের জন্য ব্যাকগ্রাউন্ডে কাজ চলতে দিন।
  3. ওভাররাইড (adb shell am memory-limiter manual <PID> none) মুছে দিন, তারপর অবিলম্বে adb shell am start -W-এর মাধ্যমে অ্যাপটি ফিরিয়ে আনুন এবং TotalTime রেকর্ড করুন। আপনি যদি MemoryBudgetManager-এর মাধ্যমে প্রার্থী সেট করেন, তাহলে অ্যাপ রিটার্ন করার আগে clearProcessBudget-কে কল করুন।
  4. একটি ছোট ফোরগ্রাউন্ড ইন্টার‍্যাকশন চালান এবং পাস করার মাপকাঠির সাথে রিফল্ট এবং ফ্রেমের পরিসংখ্যান চেক করুন।

অ্যাপটি সেই অবস্থায় থাকাকালীন এবং ওয়ার্ম রিটার্নে উভয় ক্ষেত্রেই পাস করে এমন সর্বনিম্ন বাজেট বেছে নিন এবং একই ১০% থেকে ২০% হেডরুম যোগ করুন।

ডিসপ্লে স্কেলিং ক্যালিব্রেট করুন

আপনি android:additionalBytesPerDisplayPixel ব্যবহার করলে, ১০৮০ × ২৪০০ (&approx; ২.৫৯M পিক্সেল) এবং ১৪৪০ × ৩১২০ (&approx; ৪.৪৯M পিক্সেল)-এর মতো আলাদা রেজোলিউশন সহ দুটি ডিসপ্লেতে ফোরগ্রাউন্ড নি খুঁজুন। android:additionalBytesPerDisplayPixel পেতে, বাইটে, দুটি নী-এর মধ্যে পার্থক্যকে পিক্সেল সংখ্যার পার্থক্য দিয়ে ভাগ করুন। রেজোলিউশন-ইন্ডিপেন্ডেন্ট বেস পেতে, ছোট ডিসপ্লের জন্য প্রতি-পিক্সেল পরিমাণ তার নি থেকে বিয়োগ করুন এবং android:maxMb পেতে হেডরুম যোগ করুন।

রানটাইম 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 বা তার পরবর্তী কোনও ভার্সন চলছে কিনা। আপনার অ্যাপের minSdk ৩৬-এর কম হলে, প্রথমে SDK_INT চেক করুন যাতে Android 15 (API লেভেল ৩৫) বা তার আগের ভার্সনে চলা ডিভাইস SDK_INT_FULL পড়ার সময় NoSuchFieldError থ্রো না করে:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA &&
    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()

<memory-budget> ম্যানিফেস্ট ক্লজের মতো নয়, যা foreground, perceptible এবং background অবস্থার মধ্যে প্রসেস ট্রানজিশন হওয়ার সময় অটোমেটিক বাজেট পরিবর্তন করে, MemoryBudgetManager-এর মাধ্যমে সেট করা ডায়নামিক বাজেট আপনার কোড আপডেট বা মুছে না দেওয়া পর্যন্ত একটিই সর্বোচ্চ সীমা প্রয়োগ করে। আপনি যদি ফিল্ড এক্সপেরিমেন্ট চালাতে বা স্টেট-নির্দিষ্ট সীমা ম্যানেজ করতে রানটাইম API ব্যবহার করেন, তাহলে লাইফসাইকেল ট্রানজিশন জুড়ে ডায়নামিক বাজেট আপডেট বা মুছে দিন (যেমন, ProcessLifecycleOwner বা অ্যাক্টিভিটি লাইফসাইকেল কলব্যাক ব্যবহার করে, যেমন অ্যাডাপ্টিভ ইমেজ এডিটর উদাহরণে দেখানো হয়েছে)।

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

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

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

বাজেট বেশি হয়ে যাওয়ার কারণে চাপের জন্য কলব্যাক শুনুন

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

val cleanupThread = HandlerThread("MemoryBudgetCleanup").apply { start() }

val listener = MemoryBudgetManager.OnOverBudgetListener { budgetBytes ->
    Log.w(TAG, "Process exceeded memory budget of $budgetBytes bytes")
    // Proactively evict thread-safe caches on the background Looper
    imageTileCache.evictAll()
}

// Register on a background Looper before tightening a dynamic budget
budgetManager.registerProcessOverBudgetListener(cleanupThread.looper, listener)

// When done (for example, in onStop)
budgetManager.unregisterProcessOverBudgetListener(listener)

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

  • ব্যাকগ্রাউন্ড ব্যবহার করুন Looper: যখনই সম্ভব হবে, HandlerThread Looper-কে registerProcessOverBudgetListener বা registerPackageOverBudgetListener-এ পাস করুন। ব্যাকগ্রাউন্ড থ্রেডে ক্যাশে এভিকশন বা ফাইল ক্লিনআপ রান করলে, প্রসেসটি মেমরি প্রেসারে থাকাকালীন UI থ্রেডে ফ্রেম রেন্ডারিংয়ের সাথে কনটেন্ডিং এড়ানো যায়। শুধুমাত্র মূল-থ্রেড-বাউন্ড UI স্টেট স্পর্শ করে এমন শ্রোতাদের জন্য Looper.getMainLooper রিজার্ভ করুন।
  • বারবার কলব্যাক করা হবে: বাজেট শেষ হয়ে গেলে কলব্যাক করার বিজ্ঞপ্তি একবারই পাঠানো হয় না। ব্যবহার বাজেটের সীমা ছাড়িয়ে গেলে, অপারেটিং সিস্টেম প্রথমে কোল্ড পেজ পুনরুদ্ধার করে এবং চাপ অব্যাহত থাকলে, সিস্টেম মেমরি পুনরুদ্ধার করা চালিয়ে যায় এবং প্রতি সেকেন্ডে সর্বাধিক একবার আপনার লিসেনারকে কল করে। আপনার কলব্যাক আইডেমপোটেন্ট করুন। আপনার অ্যাপের যদি শুধুমাত্র একটি বিজ্ঞপ্তি প্রয়োজন হয় বা ক্লিনআপ পাসের মধ্যে দীর্ঘ কুলডাউন চায়, তাহলে কলব্যাকের মধ্যে unregisterProcessOverBudgetListener (বা unregisterPackageOverBudgetListener) কল করুন, অথবা কুলডাউন টাইমস্ট্যাম্প বা AtomicBoolean দিয়ে আপনার ক্লিনআপ সুরক্ষিত করুন।
  • বাজেট কমানোর আগে রেজিস্টার করুন: আপনি setProcessBudgetBytes বা setPackageBudgetBytes কল করার সময় বর্তমান মেমরির ব্যবহার যদি নতুন বাজেটের থেকে বেশি হয়, তাহলে নতুন বাজেট প্রয়োগ করার সাথে সাথেই ওভার-বাজেট ইভেন্ট ট্রিগার করা হয়। বাজেট সেট করার আগে আপনার লিসেনার রেজিস্টার করুন যাতে আপনার অ্যাপ সেই প্রাথমিক ওভার-বাজেট বিজ্ঞপ্তি মিস না করে।
  • দ্রুত কাজ করুন: রিক্লেম অপারেশন থেকে অবিলম্বে স্বস্তি পেতে হবে। চাপের সময় জটিল কম্পিউটেশন পারফর্ম্যান্সকে আরও খারাপ করে তোলে।
  • অ্যালোকেশন এড়িয়ে চলুন: কলব্যাকের মধ্যে নতুন অবজেক্ট অ্যালোকেট করবেন না বা নতুন থ্রেড শুরু করবেন না, কারণ এটি করলে অপারেটিং সিস্টেম অবিলম্বে ডাইরেক্ট রিক্লেম ট্রিগার করতে পারে।
  • হাই-ইল্ড টার্গেটে ফোকাস করুন: অনেক ছোট অবজেক্ট রিলিজ করার চেয়ে বড় বিটম্যাপ, রেন্ডার বাফার এভিক্ট করা অথবা মেমরি-ম্যাপ করা ফাইল বন্ধ করা অনেক বেশি কার্যকর।

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

নেটিভ অ্যাপ, Android 17 QPR2 (API লেভেল ৩৭.২) থেকে শুরু করে libandroid.so-এর মাধ্যমে প্রকাশ করা C NDK API ব্যবহার করতে পারে।

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 a background worker ALooper with a 1000ms debounce interval
AMemoryBudgetManagerWatcher* watcher = AMemoryBudgetManager_Watcher_create(
    background_looper,
    AMEMORY_BUDGET_MANAGER_EVENT_PROCESS,
    1000 /* debounce_ms */,
    &onMemoryPressure,
    NULL /* userdata */
);

// When done:
AMemoryBudgetManager_Watcher_destroy(watcher);

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

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

নিম্নলিখিত উদাহরণগুলি দেখায় যে কীভাবে Android SDK API (Kotlin-এ লেখা) এবং নেটিভ 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 ব্যবহার করে ডায়নামিকভাবে এর প্রসেস বাজেট কমিয়ে ৯৬ এমবি করে। ব্যবহারকারী মাল্টি-লেয়ার এডিটিং ক্যানভাস খুললে, অ্যাপটি ডায়নামিক বাজেট মুছে দেয় যাতে সম্পূর্ণ ২৫৬ এমবি ম্যানিফেস্ট সিলিং রিস্টোর করা যায়। এছাড়াও, এটি UI থ্রেড ব্লক না করে চাপের মধ্যে ক্যাশে করা প্রিভিউ বিটম্যাপ বের করে দেওয়ার জন্য গ্যালারি বাজেট প্রয়োগ করার আগে ব্যাকগ্রাউন্ড HandlerThread Looper-এ OnOverBudgetListener রেজিস্টার করে।

package com.example.imageeditor.ui

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

class ImageEditorActivity : Activity() {

    private lateinit var budgetManager: MemoryBudgetManager
    private lateinit var cleanupThread: HandlerThread

    // Thread-safe 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)
        cleanupThread = HandlerThread("MemoryBudgetCleanup").apply { start() }
    }

    override fun onStart() {
        super.onStart()
        // Register listener on a background Looper before tightening the budget
        budgetManager.registerProcessOverBudgetListener(cleanupThread.looper, overBudgetListener)
        // Constrain memory during lightweight gallery browsing
        applyGalleryBudget()
    }

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

    override fun onDestroy() {
        cleanupThread.quitSafely()
        super.onDestroy()
    }

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

    // Call on a background worker ALooper before tightening the budget with setQualityBudget
    bool startMonitoring(ALooper* backgroundLooper) {
        if (!backgroundLooper) return false;

        // Monitor process budget events, debounced to at most once every 1000ms
        mWatcher = AMemoryBudgetManager_Watcher_create(
            backgroundLooper,
            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, idempotent, 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;
};