অ্যাপ মেমরি বাজেট অ্যাপগুলোকে নিজেদের জন্য একটি মেমরি বাজেট ঘোষণা করার সুযোগ দেয়, যা সিস্টেমকে নির্দেশ দেয় যে অ্যাপটি তার নির্ধারিত বাজেটের চেয়ে বেশি মেমরি ব্যবহার করলে যেন তার ব্যবহার কমিয়ে আনা হয়। এটি বিশেষত সিস্টেম ও বান্ডেলড অ্যাপ অথবা সীমিত মেমরিযুক্ত ডিভাইসকে লক্ষ্য করে তৈরি অ্যাপের জন্য উপযোগী, যেখানে ডেভেলপার তার প্রত্যাশিত কার্যকরী মেমরি সেট সম্পর্কে জানেন এবং নিশ্চিত করতে চান যে তার অ্যাপটি যেন সিস্টেমের শেয়ার করা র্যাম রিসোর্সের খুব বেশি অংশ ব্যবহার না করে।
সম্প্রতি ব্যবহৃত হয়নি এমন মেমরি পেজগুলো সরিয়ে ফেলার জন্য মেমরি ইভিকশন এবং সোয়াপ ব্যবহার করে বাজেট ভারসাম্যপূর্ণ রাখা হয়, যা অ্যাপটির মেমরি ফুটপ্রিন্টকে তার বর্তমান ওয়ার্কিং সেটের উপর কেন্দ্রীভূত করে। যখন কোনো অ্যাপ তার ঘোষিত বাজেট অতিক্রম করে, তখন অপারেটিং সিস্টেম বিশেষভাবে সেই অ্যাপটিকে লক্ষ্য করে রিক্লেইম করে:
- ফাইল-সমর্থিত পরিষ্কার পেজগুলো (যেমন নিষ্ক্রিয় কোড এবং ম্যাপ করা অ্যাসেট) প্রথমে সরিয়ে ফেলা হয়, কারণ প্রয়োজনে সেগুলো স্টোরেজ থেকে পুনরায় পড়া যেতে পারে।
- ত্রুটিপূর্ণ ফাইল-সমর্থিত পৃষ্ঠাগুলি স্টোরেজে পুনরায় লেখা হয় এবং বিতাড়িত করা হয়।
- অজ্ঞাত মেমরি পেজগুলো (যেমন হিপ অ্যালোকেশন) সংকুচিত করে zRAM-এ সোয়াপ করা হয়।
যতক্ষণ পর্যন্ত বাজেট ওয়ার্কিং সেট অতিক্রম না করে, অ্যাপটি তার নির্ধারিত বাজেটের চেয়ে বেশি মেমরি ব্যবহার না করেই ভালোভাবে কাজ করবে। অপারেটিং সিস্টেম অব্যবহৃত মেমরি খালি করে দেয় এবং নিষ্ক্রিয় হিপ পেজগুলোকে সোয়াপে সংকুচিত করে, যাতে প্রসেসটি বন্ধ না করেই মেমরি বরাদ্দ সীমিত থাকে।
অ্যান্ড্রয়েড ম্যানিফেস্টে বাজেট ঘোষণা করুন
আপনার AndroidManifest.xml ফাইলে মেমরি বাজেট ঘোষণা করাই হলো বাজেট নির্ধারণের প্রধান এবং প্রস্তাবিত পদ্ধতি। এর জন্য কোনো রানটাইম কোডের প্রয়োজন হয় না, প্রসেস চালু হওয়ার সাথে সাথেই এটি কার্যকর হয় এবং অপারেটিং সিস্টেমের জন্য একটি সুস্পষ্ট চুক্তি প্রদান করে।
<memory-budget> ডিক্লারেশনগুলো Android 17 QPR2 (API লেভেল 37.2) এবং এর চেয়ে উচ্চতর সংস্করণে চালিত ডিভাইসগুলোতে কার্যকর হয়। 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 স্টেটে রূপান্তরিত হওয়ার সময় পরবর্তী আরও সীমাবদ্ধ ক্লজগুলো ওভাররাইড করে।
মাল্টি-প্রসেস অ্যাপস
যদি আপনার অ্যাপ্লিকেশন তার কাজ একাধিক প্রসেসে ভাগ করে নেয়, তাহলে <processes> ট্যাগের ভিতরে <process> ট্যাগ ব্যবহার করে প্রতিটি প্রসেসের জন্য আলাদা বাজেট কনফিগার করুন।
উদাহরণস্বরূপ, একটি স্ট্রিমিং মিউজিক অ্যাপের ( com.example.radio ) কথা বিবেচনা করুন:
- মূল প্রসেস : এটি দৃশ্যমান ইউজার ইন্টারফেস (UI) এবং অডিও প্লেব্যাক ইঞ্জিন (
mediaPlaybackফোরগ্রাউন্ড সার্ভিস সহMediaSessionService) হোস্ট করে। দৃশ্যমান অবস্থায়, প্রসেসটি ১৮০ মেগাবাইট ফোরগ্রাউন্ড বাজেটের অধীনে কাজ করে। যখন মিউজিক বাজতে থাকা অবস্থায় ব্যবহারকারী অ্যাপটি ছেড়ে যান, তখন প্রসেসটি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" />
<!-- 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:additionalMbPerDensity) :mdpi(1.0x / 160 dpi)-এর সাপেক্ষে ডিসপ্লের ডেনসিটি অনুপাতের সমানুপাতিকভাবে মেগাবাইট যোগ করে। এটি তখন উপযুক্ত যখন UI ডেনসিটি বাকেটের সাথে মেমরি ব্যবহারও বাড়ে, যেমন উচ্চ-রেজোলিউশনের রাস্টার ড্রয়েবল বা UI অ্যাসেট ক্যাশ করার ক্ষেত্রে:<!-- Baseline 180MB + 16MB per 1.0x density ratio --> <memory-budget android:maxMb="180" android:additionalMbPerDensity="16" />একটি
mdpi(1.0x) ডিসপ্লেতে, মেমরি বাজেট হলো 180 + 16 × 1 = 196 MB। একটিxxhdpi(3.0x) ডিসপ্লেতে, এই বাজেট বেড়ে 180 + 16 × 3 = 228 MB হয়।ফিজিক্যাল ডিসপ্লে রেজোলিউশন অনুযায়ী স্কেল করুন (
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 ≈ 2.59M পিক্সেল), এটি বেসলাইন বাজেটের সাথে প্রায় 41.4 MB যোগ করে। একটি 1440p ডিসপ্লেতে (1440 × 3120 ≈ 4.49M পিক্সেল), এটি প্রায় 71.8 MB যোগ করে।
এই দুটি অ্যাট্রিবিউট হলো বিকল্প। আপনার অ্যাপের প্রধান স্কেলিং ফ্যাক্টরের সাথে মেলে এমন অ্যাট্রিবিউটটি বেছে নিন এবং একই ক্লজে উভয়কে একত্রিত করা থেকে বিরত থাকুন।
ডিভাইসের ফর্ম ফ্যাক্টরের জন্য বিশেষায়িত করুন
ফোন, ট্যাবলেট এবং Wear OS-এ APK পাঠানোর সময়, বিভিন্ন হার্ডওয়্যার টার্গেটের জন্য বাজেট সমন্বয় করতে android:feature অ্যাট্রিবিউটটি ব্যবহার করুন।
Wear OS ঘড়িগুলিতে র্যাম সীমিত থাকে এবং অ্যাপটির 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) প্রকাশ করা হয় এবং লিনাক্স cgroup memory.current charge-এর সাথে ম্যাপ করা হয় (যা জাইগোটের মতো শেয়ার্ড মেমরিকে বাদ দেয়)।
| বৈশিষ্ট্য | ফর্ম্যাট | ডিফল্ট | বর্ণনা |
|---|---|---|---|
android:maxMb | পূর্ণসংখ্যা (> 0) | প্রয়োজনীয় | বেসলাইন রেসিডেন্ট মেমরি বাজেট সীমা (এমবি-তে)। |
android:state | এনাম | যেকোনো | এই বাজেটটি যে প্রক্রিয়ার অবস্থার উপর প্রযোজ্য তা হলো: foreground , perceptible , বা background । যদি এটি বাদ দেওয়া হয়, তবে ধারাটি যেকোনো অনির্দিষ্ট অবস্থার জন্য একটি বিকল্প ব্যবস্থা হিসেবে কাজ করে। |
android:additionalMbPerDensity | পূর্ণসংখ্যা (≥ ০) | 0 | mdpi (1.0x)-এর সাপেক্ষে ডিসপ্লে ডেনসিটি রেশিওর প্রতি ইউনিটে যোগ করার জন্য অতিরিক্ত মেগাবাইট। |
android:additionalBytesPerDisplayPixel | পূর্ণসংখ্যা (≥ ০) | 0 | প্রতিটি ফিজিক্যাল ডিসপ্লে পিক্সেলের (প্রস্থ × উচ্চতা) জন্য বরাদ্দকৃত অতিরিক্ত বাইট, যা সারফেস বাফার এবং বিটম্যাপের জন্য উপযোগী। |
android:feature | স্ট্রিং | যেকোনো | এই ধারাটি শুধুমাত্র সেইসব ডিভাইসের ক্ষেত্রে প্রযোজ্য যেগুলো নির্দিষ্ট হার্ডওয়্যার বৈশিষ্ট্য ঘোষণা করে: watch , automotive , বা leanback । |
রানটাইম এপিআই (সেকেন্ডারি ডাইনামিক অপশন)
প্রায় সব অ্যাপের জন্যই AndroidManifest.xml এ স্ট্যাটিক্যালি বাজেট ঘোষণা করাটাই পছন্দনীয় সমাধান। তবে, ডাইনামিক ওয়ার্কলোডযুক্ত অ্যাপ্লিকেশন বা রানটাইম এক্সপেরিমেন্টের জন্য, অ্যান্ড্রয়েড দ্বিতীয় বিকল্প হিসেবে রানটাইম SDK এবং NDK 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()
বাজেট-বহির্ভূত চাপের কারণে পুনরায় ফোন করার বিষয়টি শুনুন।
অ্যাপগুলো একটি লিসেনার রেজিস্টার করতে পারে, যার মাধ্যমে মেমরি ব্যবহার নির্ধারিত সীমা অতিক্রম করলে নোটিফিকেশন পাওয়া যায়। এর ফলে, অপারেটিং সিস্টেম সরাসরি রিক্লেইম ল্যাটেন্সি চালু করার আগেই অ্যাপটি সক্রিয়ভাবে অ্যাপ্লিকেশন-স্তরের পরিষ্করণ (যেমন ইন-মেমরি বিটম্যাপ ক্যাশ পরিষ্কার করা) সম্পাদন করতে পারে।
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)
বাজেট-বহির্ভূত অনুরোধের ক্ষেত্রে সর্বোত্তম পন্থা:
- দ্রুত হোন : পুনরুদ্ধার কার্যক্রম অবশ্যই তাৎক্ষণিক স্বস্তি প্রদান করবে। চাপের সময় জটিল গণনা কর্মক্ষমতা হ্রাস করে।
- অ্যালোকেশন পরিহার করুন : কলব্যাকের ভিতরে নতুন অবজেক্ট অ্যালোকেট করবেন না বা নতুন থ্রেড শুরু করবেন না, কারণ এমনটা করলে অপারেটিং সিস্টেম তাৎক্ষণিক সরাসরি রিক্লেইম করতে পারে।
- অধিক ফলপ্রসূ লক্ষ্যবস্তুর উপর মনোযোগ দিন : অনেকগুলো ছোট অবজেক্ট রিলিজ করার চেয়ে বড় বিটম্যাপ, রেন্ডার বাফার বা মেমোরি-ম্যাপড ফাইল বন্ধ করা অনেক বেশি কার্যকর।
নেটিভ এনডিকে এপিআই ( <android/memory_budget_manager.h> )
অ্যান্ড্রয়েড ১৭ কিউপিআর২ (এপিআই লেভেল ৩৭.২) থেকে নেটিভ অ্যাপগুলো libandroid.so দ্বারা উন্মুক্ত করা সি এনডিকে এপিআই ব্যবহার করতে পারবে।
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();
মেমরি চাপের ঘটনাগুলি নিরীক্ষণ করুন
এনডিকে মেমরি ইভেন্ট নিরীক্ষণের জন্য দুটি উপায় প্রদান করে:
- উচ্চ-স্তরের পর্যবেক্ষক (
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 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);
রানটাইম এপিআই উদাহরণ
নিম্নলিখিত উদাহরণগুলিতে অ্যান্ড্রয়েড এসডিকে এপিআই (কোটলিনে লেখা) এবং নেটিভ এনডিকে এপিআই (সি++ এ লেখা) ব্যবহার করে কীভাবে রানটাইম এপিআইগুলি প্রয়োগ করতে হয় তা দেখানো হয়েছে।
অ্যান্ড্রয়েড এসডিকে উদাহরণ: অভিযোজিত ইমেজ এডিটর
এই উদাহরণটি একটি ইমেজ এডিটিং অ্যাপ ( com.example.imageeditor ) দেখাচ্ছে, যার ম্যানিফেস্টে এর মাল্টি-লেয়ার ক্যানভাসের জন্য ২৫৬ মেগাবাইটের একটি সর্বোচ্চ সীমা ঘোষণা করা হয়েছে:
<manifest ... >
<application ... >
<!-- Manifest ceiling accommodates the heaviest editing workload -->
<memory-budget android:maxMb="256" />
</application>
</manifest>
যখন ব্যবহারকারী লাইটওয়েট থাম্বনেইল গ্যালারি ব্রাউজ করেন, তখন অ্যাপটি কোটলিনে অ্যান্ড্রয়েড এসডিকে এপিআই ব্যবহার করে তার প্রসেস বাজেটকে ডাইনামিকভাবে ৯৬ মেগাবাইটে সংকুচিত করে। যখন ব্যবহারকারী মাল্টি-লেয়ার এডিটিং ক্যানভাস খোলেন, তখন অ্যাপটি সম্পূর্ণ ২৫৬ মেগাবাইটের ম্যানিফেস্ট সিলিং পুনরুদ্ধার করার জন্য ডাইনামিক বাজেটটি ক্লিয়ার করে দেয়। এছাড়াও, চাপের মুখে ক্যাশ করা প্রিভিউ বিটম্যাপগুলো সরিয়ে ফেলার জন্য এটি একটি 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"
}
}
এনডিকে সি++ উদাহরণ: নেটিভ ৩ডি ইঞ্জিন
এই উদাহরণটি একটি নেটিভ C++ গেম ইঞ্জিনকে দেখায় যা সক্রিয় গ্রাফিক্স কোয়ালিটি লেভেলের উপর ভিত্তি করে মেমরি বাজেট পরিচালনা করে, এই ধরে নিয়ে যে অ্যাপ্লিকেশনটির ম্যানিফেস্টে হাই কোয়ালিটি গ্রাফিক্সের জন্য 512MB-এর একটি সর্বোচ্চ সীমা ( 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;
};