অ্যাপ মেমরি বাজেট অ্যাপকে নিজের জন্য মেমরি বাজেট ঘোষণা করতে দেয়, যা সিস্টেমকে বলে দেয় যে অ্যাপটি তার সেট করা বাজেটের চেয়ে বেশি ব্যবহার করলে তার মেমরি ব্যবহার কমাতে হবে। এটি বিশেষ করে সিস্টেম ও বান্ডেল করা অ্যাপ অথবা মেমরি-সীমাবদ্ধ ডিভাইসকে টার্গেট করা অ্যাপের জন্য উপযোগী, যেখানে ডেভেলপার তার প্রত্যাশিত মেমরি ওয়ার্কিং সেট (ব্যবহারকারী বর্তমানে যা করছেন তার জন্য অ্যাপের সক্রিয়ভাবে যে মেমরির প্রয়োজন হয়) জানেন এবং তার অ্যাপ যাতে সিস্টেমের শেয়ার করা RAM রিসোর্স খুব বেশি ব্যবহার না করে তা নিশ্চিত করতে চান।
সম্প্রতি ব্যবহার করা হয়নি এমন মেমরি পেজ সরিয়ে দেওয়ার জন্য মেমরি এভিকশন ও সোয়াপ ব্যবহার করে বাজেট ব্যালেন্স করা হয়, যাতে অ্যাপের মেমরি ফুটপ্রিন্ট বর্তমান ওয়ার্কিং সেটের উপর ফোকাস করতে পারে। কোনও অ্যাপ তার ঘোষিত বাজেট অতিক্রম করলে, অপারেটিং সিস্টেম বিশেষভাবে সেই অ্যাপে রিক্লেম করার লক্ষ্য রাখে:
- ক্লিন ফাইল-ব্যাকড পেজ (যেমন, ইনঅ্যাক্টিভ কোড ও ম্যাপ করা অ্যাসেট) প্রথমে সরিয়ে দেওয়া হয় কারণ প্রয়োজন হলে স্টোরেজ থেকে সেগুলি আবার পড়া যায়।
- ডার্টি ফাইল-ব্যাকড পৃষ্ঠা স্টোরেজে আবার লেখা হয় এবং সরিয়ে দেওয়া হয়।
- অজ্ঞাত মেমরি পৃষ্ঠা (যেমন হিপ অ্যালোকেশন) কম্প্রেস করা হয় এবং zRAM-এ সোয়্যাপ করা হয়।
যতক্ষণ পর্যন্ত ওয়ার্কিং সেট বাজেটের সীমা অতিক্রম না করে, ততক্ষণ অ্যাপটি ভালভাবে কাজ করবে এবং সেট করা বাজেটের থেকে বেশি মেমরি ব্যবহার করবে না। অপারেটিং সিস্টেম অব্যবহৃত মেমরি খালি করে এবং ইনঅ্যাক্টিভ হিপ পেজ সোয়্যাপে কম্প্রেস করে যাতে প্রসেস বন্ধ না করেই মেমরি অ্যালোকেশন সীমাবদ্ধ থাকে।
কোনও অ্যাপ তার বাজেট অতিক্রম করলে কী হয়
মেমরি বাজেট অতিক্রম করলে সিস্টেম আপনার অ্যাপ বন্ধ বা ক্র্যাশ করে না। পরিবর্তে, যখন কোনও অ্যাপ তার বাজেটের চেয়ে বেশি মেমরি ব্যবহার করে, তখন OS এমন মেমরি খুঁজে বের করে যা অ্যাপটি কিছু সময়ের জন্য ব্যবহার করেনি এবং আবার প্রয়োজন না হওয়া পর্যন্ত নিরাপদে সরিয়ে রাখে। এটি নতুন অ্যাপ মেমরি অ্যালোকেশনের জন্য জায়গা করে দেয় এবং সামগ্রিকভাবে অ্যাপের মেমরি ব্যবহার বাজেটের মধ্যে রাখে।
কোনও নির্দিষ্ট সময়ে অ্যাপের যা প্রয়োজন (এর ওয়ার্কিং সেট) এবং সেট করা বাজেটের মধ্যে যতক্ষণ পর্যন্ত হেডরুম থাকে, ততক্ষণ অ্যাপটি স্বাভাবিকভাবে চলতে থাকে। কোনও অ্যাপ যদি তার ওয়ার্কিং সেটের চেয়ে ছোট বাজেট সেট করে, তাহলে রানটাইমে অ্যাপের পারফর্ম্যান্স ধীর হয়ে যেতে পারে।
OS কীভাবে মেমরি ট্র্যাক করে এবং ফিরিয়ে আনে তা জানতে, মেমরি আর্কিটেকচার গাইড দেখুন:
- মেমরি রিক্লেম ও সোয়াপ: OS কীভাবে আপনার অ্যাপে ব্যক্তিগত বেনামী হিপ ও ফাইল-ব্যাক করা পৃষ্ঠা চার্জ করে এবং বাজেট শেষ হয়ে গেলে কোল্ড পৃষ্ঠা রিক্লেম করে তা ব্যাখ্যা করে।
- মৌলিক ধারণা: শেয়ার করা Zygote মেমরি কেন আপনার অ্যাপের বাজেট থেকে বাদ দেওয়া হয়েছে এবং ফিল্ড টেলিমেট্রিতে রেসিডেন্ট বাজেট কীভাবে বেনামী RSS + সোয়াপ-এর সাথে সম্পর্কিত তা ব্যাখ্যা করে।
- বিটম্যাপ ও মেমরি: GPU-ব্যাকড হার্ডওয়্যার বিটম্যাপ ও DMA-BUF অ্যালোকেশন কীভাবে আপনার অ্যাপের হিপ ও মেমরি বাজেটের বাইরে হিসেব করা হয় তা ব্যাখ্যা করে।
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_FOREGROUNDIMPORTANCE_TOP_SLEEPING |
দৃশ্যমান অ্যাক্টিভিটি আবার শুরু করা হয়েছে, স্ক্রিন লক করা অবস্থায় সবচেয়ে উপরে থাকা অ্যাপ, সবচেয়ে উপরে থাকা অ্যাপের সাথে বাউন্ড করা পরিষেবা বা কন্টেন্ট প্রোভাইডার |
perceptible |
IMPORTANCE_FOREGROUND_SERVICEIMPORTANCE_VISIBLE |
অ্যাক্টিভ মিডিয়া প্লেব্যাক, নেভিগেশন, ডাউনলোড বা সিঙ্ক ফোরগ্রাউন্ড পরিষেবা; ফোরগ্রাউন্ড পরিষেবা বা দৃশ্যমান ফ্ল্যাগ সহ পরিষেবা বাউন্ড |
background |
IMPORTANCE_PERCEPTIBLEIMPORTANCE_CANT_SAVE_STATEIMPORTANCE_SERVICE |
ব্যাকগ্রাউন্ড জব, রিসিভার, ব্যাকগ্রাউন্ড সিঙ্ক, ব্যাকগ্রাউন্ডে আগের অ্যাক্টিভিটি বা Home অ্যাপ |
ক্লায়েন্ট বাইন্ডিং কীভাবে কোনও পরিষেবা বা প্রোভাইডার হোস্ট প্রসেসকে উন্নত করে
এবং ক্লায়েন্ট আনবাইন্ড বা অফ-স্ক্রিনে চলে যাওয়ার পরে কীভাবে প্রসেসটি আবার 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>
Android 17 QPR2 থেকে শুরু করে, এই আউটপুটে একটি মেমরি বাজেট বিভাগ অন্তর্ভুক্ত রয়েছে
যা অ্যাক্টিভ লিমিট সিলিং, লিমিট সোর্স (যেমন
AndroidManifest বা MemoryBudgetManager) এবং বর্তমান রেসিডেন্ট মেমরি দেখায়।
মাল্টি-প্রসেস অ্যাপ
আপনার অ্যাপ্লিকেশন যদি একাধিক প্রসেস জুড়ে তার কাজ ভাগ করে, তাহলে <processes>-এর মধ্যে <process> ট্যাগ ব্যবহার করে ডেডিকেটেড প্রসেস বাজেট কনফিগার করুন।
যেমন, একটি স্ট্রিমিং মিউজিক অ্যাপ (com.example.radio) বিবেচনা করুন:
- প্রধান প্রসেস: দৃশ্যমান UI এবং অডিও প্লেব্যাক ইঞ্জিন হোস্ট করে
(
MediaSessionServicemediaPlaybackফোরগ্রাউন্ড পরিষেবার সাথে)। দেখা গেলে, প্রসেসটি ১৮০ এমবি ফোরগ্রাউন্ড বাজেটের অধীনে কাজ করে। মিউজিক চলাকালীন ব্যবহারকারী অ্যাপ থেকে বেরিয়ে গেলে, প্রসেসটিperceptibleঅবস্থায় প্রবেশ করে, যেখানে প্লেব্যাক ইঞ্জিন এবং অডিও বাফারের জন্য ৬৪ এমবি বাজেট যথেষ্ট। - সিঙ্ক প্রসেস (
: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ডিসপ্লেতে (1.0x), বাজেট হল ১৮০ + ১৬ × ১২ = ১৯৬ এমবি।xxhdpiডিসপ্লেতে (3.0x), বাজেট স্কেল হল ১৮০ + ১৬ × ৩২ = ৩২৪ এমবি।ফিজিক্যাল ডিসপ্লে রেজোলিউশন অনুযায়ী স্কেল করা (
android:additionalBytesPerDisplayPixel): ফিজিক্যাল ডিসপ্লে পিক্সেল (প্রস্থ × উচ্চতা) অনুযায়ী সরাসরি বাইট যোগ করে। এটি সেইসব অ্যাপ্লিকেশনের জন্য আদর্শ যেগুলি ফুল-স্ক্রিন গ্রাফিক্স সারফেস, রেন্ডার বাফার বা ফুল-রেজোলিউশন ফটো ক্যাশে বরাদ্দ করে, যেখানে মেমরি খরচ সরাসরি র Display পিক্সেল কাউন্টের সাথে স্কেল করে, 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 ডিসপ্লেতে (১০৮০ × ২৪০০ ≈ ২.৫৯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 অ্যাট্রিবিউট রেফারেন্স
সব মেমরি সাইজ অ্যাট্রিবিউট মেগাবাইটে (এমবি) প্রকাশ করা হয় এবং Linux
cgroup memory.current চার্জে ম্যাপ করা হয় (যা Zygote-এর মতো শেয়ার করা মেমরি বাদ দেয়)।
| অ্যাট্রিবিউট | ফর্ম্যাট | ডিফল্ট | বর্ণনা |
|---|---|---|---|
android:maxMb |
পূর্ণসংখ্যা (> ০) | প্রয়োজনীয় | এমবি-তে বেসলাইন রেসিডেন্ট মেমরি বাজেট সীমা। |
android:state |
Enum | যেকোনও | এই বাজেট যে প্রসেস স্টেটের ক্ষেত্রে প্রযোজ্য: foreground, perceptible অথবা background। বাদ দেওয়া হলে, ক্লজটি যেকোনও অনির্দিষ্ট স্ট্যাটাসের জন্য ফলব্যাক হিসেবে কাজ করে। |
android:additionalMbPerDensity |
পূর্ণসংখ্যা (≥ ০) | 0 |
mdpi (1.0x)-এর সাপেক্ষে ডিসপ্লে ডেনসিটি অনুপাত-এর বর্গ দিয়ে অতিরিক্ত মেগাবাইট গুণ করা হয়। |
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 বা তার পরবর্তী কোনও ভার্সন চলছে কিনা। আপনার
অ্যাপের 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: যখনই সম্ভব হবে,HandlerThreadLooper-কে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 মেমরি ইভেন্ট মনিটর করার দুটি উপায় প্রদান করে:
- হাই-লেভেল ওয়াচার (
AMemoryBudgetManager_Watcher_create): অটোমেটিক ডিবাউন্সিং সহALooper-এ ইভেন্ট মনিটর করে। - লো-লেভেল ফাইল ডেসক্রিপটর:
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-এর উদাহরণ
নিম্নলিখিত উদাহরণগুলি দেখায় যে কীভাবে রানটাইম API প্রয়োগ করতে হয় Android SDK API (Kotlin-এ লেখা) এবং নেটিভ NDK API (C++-এ লেখা)।
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;
};