Uygulama bellek bütçeleri, uygulamaların kendileri için bir bellek bütçesi beyan etmesine olanak tanır. Bu bütçe, uygulama belirlenen bütçesinden daha fazla bellek kullandığında sisteme bellek kullanımını azaltmasını söyler. Bu, özellikle sistem uygulamaları ve paket uygulamalar ya da belleği kısıtlı cihazları hedefleyen uygulamalar için kullanışlıdır. Bu durumlarda geliştirici, beklenen bellek çalışma grubunu (uygulamanın kullanıcının o anda yaptığı işlem için aktif olarak ihtiyaç duyduğu bellek) bilir ve uygulamasının sistemin paylaşılan RAM kaynaklarını çok fazla kullanmamasını ister.
Bütçe, son zamanlarda kullanılmayan bellek sayfalarını kaldırmak için bellek çıkarma ve takas kullanılarak dengede tutulur. Böylece uygulamanın bellekte kapladığı yer, mevcut çalışma grubuna odaklanır. Bir uygulama beyan edilen bütçesini aştığında işletim sistemi, özellikle bu uygulamayı hedefleyerek geri alma işlemi gerçekleştirir:
- Temiz dosya destekli sayfalar (ör. etkin olmayan kod ve eşlenen öğeler), gerektiğinde depolama alanından yeniden okunabildikleri için ilk olarak çıkarılır.
- Kirli dosya destekli sayfalar depolama alanına geri yazılır ve çıkarılır.
- Anonim bellek sayfaları (ör. yığın ayırmalar) sıkıştırılır ve zRAM'e aktarılır.
Çalışma grubu bütçeyi aşmadığı sürece uygulama, bütçesinden daha fazla bellek kullanmadan iyi çalışır. İşletim sistemi, kullanılmayan belleği çıkarır ve etkin olmayan yığın sayfalarını takas alanına sıkıştırır. Böylece bellek ayırmaları, işlem sonlandırılmadan sınırlı kalır.
Bir uygulama bütçesini aştığında ne olur?
Bellek bütçesinin aşılması, sistemin uygulamanızı sonlandırmasına veya kilitlenmesine neden olmaz. Bunun yerine, bir uygulama bütçesinin izin verdiğinden daha fazla bellek kullandığında işletim sistemi, uygulamanın bir süredir kullanmadığı belleği bulur ve tekrar ihtiyaç duyulana kadar güvenli bir şekilde saklar. Bu sayede yeni uygulama bellek ayırmaları için yer açılır ve uygulamanın genel bellek kullanımı bütçe dahilinde tutulur.
Uygulamanın belirli bir zamanda ihtiyaç duyduğu bellek (çalışma kümesi) ile belirlenen bütçe arasında boşluk olduğu sürece uygulama normal şekilde çalışmaya devam eder. Bir uygulama, çalışma grubundan daha küçük bir bütçe belirlerse uygulamanın çalışma zamanındaki performansı yavaşlayabilir.
İşletim sisteminin belleği nasıl izlediğini ve geri aldığını öğrenmek için bellek mimarisi kılavuzuna bakın:
- Bellek geri kazanımı ve takas: İşletim sisteminin, özel anonim yığın ve dosya destekli sayfaları uygulamanıza nasıl yüklediğini ve bütçe aşıldığında soğuk sayfaları nasıl geri kazandığını açıklar.
- Temel kavramlar: Paylaşılan Zygote belleğinin neden uygulamanızın bütçesinden hariç tutulduğunu ve yerleşik bütçelerin saha telemetrisindeki anonim RSS + takas ile nasıl ilişkili olduğunu açıklar.
- Bit eşlemler ve bellek: GPU destekli donanım bit eşlemlerinin ve DMA-BUF ayırmalarının, uygulamanızın yığın ve bellek bütçesi dışında nasıl hesaba katıldığını açıklar.
Android manifest dosyasında bütçeleri bildirme
Bellek bütçelerinizi AndroidManifest.xml içinde tanımlamak, bütçe tanımlamak için birincil ve önerilen yöntemdir. Çalışma zamanı kodu gerektirmez, işlem başlatıldığında hemen geçerli olur ve işletim sistemi için net bir sözleşme sağlar.
<memory-budget> beyanları, Android 17 QPR2 (API düzeyi 37.2) ve sonraki sürümleri çalıştıran cihazlarda geçerli olur. Daha eski Android sürümlerinde platform manifest ayrıştırıcısı, tanınmayan XML öğelerini güvenli bir şekilde yoksayar. Böylece geriye dönük uyumluluğu etkilemeden <memory-budget> öğesini kullanabilirsiniz.
Referans bütçe belirleme
Çoğu uygulamada, uygulama için tek bir bütçe tanımlamak yeterlidir. Doğrudan <application> etiketinin içinde bir <memory-budget> öğesi tanımlayın:
<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>
Bu, paketin tüm işlemleri ve durumları için 256 MB'lık bir yerleşik bellek bütçesi belirler. Uygulamanın bellekte kapladığı yer 256 MB'ı aştığında işletim sistemi, çıkarma ve takas yöntemlerini kullanarak etkin olmayan bellek sayfalarını kırpar.
Bütçeleri işlem durumuna göre değiştirme
Bir uygulamanın ihtiyaç duyduğu bellek miktarı, kullanıcı görünürlüğüne bağlı olarak değişir:
- Ön plan: İşlem, kullanıcıyla etkileşimde bulunan görünür bir etkinliği barındırıyor. Bu durum, etkin kullanıcı arayüzü ve grafikler nedeniyle genellikle en büyük ayak izine sahiptir.
- Algılanabilir: İşlem, kullanıcı tarafından algılanabilir ancak görünür bir pencere barındırmaz (ör. medya oynatma ön plan hizmeti, etkin bir arka plan indirme işlemi, adım adım rota veya etkin bir giriş yöntemi barındırma).
- Arka plan: İşlem, arka plan işlerini, alıcıları veya veri senkronizasyonlarını çalıştırıyor. Minimum düzeyde yer kaplaması beklenir.
Bu durumlarla eşleşecek birden fazla <memory-budget> ifadesi bildirebilirsiniz:
<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>
Ön plan veya atlanan durum yan tümcesi gerekli değildir. Yalnızca perceptible veya background yan tümcelerini bildirirseniz foreground gibi daha yüksek öneme sahip durumlar, uygulama bütçesiyle kısıtlanmaz. android:state ifadesinin kullanılmaması, varsayılan olarak foreground değerinin kullanılmasına neden olur. Bu değer, daha sonraki bir yan tümce bu durumları geçersiz kılmadığı sürece perceptible ve background değerlerine de uygulanır. Yan tümce sıralamasıyla ilgili ayrıntılar için Çözüm kuralı: Geçerli olan son yan tümce yürürlüğe girer başlıklı makaleyi inceleyin.
Her durum için bütçe boyutunu belirlerken, en yüksek yerleşik bellek (RSS üst sınır) yerine o durumun etkin çalışma grubunu hedefleyin:
- Sorunsuz kare oluşturma için
foregroundbütçelerini boyutlandırın: Ön plan kullanıcı arayüzüne, etkin çalışma grubu için yeterli alan verin. Böylece eşzamanlı geri alma, kare oluşturmayı geciktirmez. Uygulamanızda yoğun bellek kullanan bağımsız bir iş akışı (ör. yüksek çözünürlüklü resim veya video düzenleyici) varsa bu etkinliği özel bir işleme (Çok işlemli uygulamalar bölümüne bakın) taşımayı düşünebilirsiniz. Böylece, tahsisatları bağımsız olarak bütçelendirilir ve kullanıcı çıktığında kaldırılır. - Başsız çalışma için bütçeleri sıkı bir şekilde
backgroundboyutlandırın: Saha telemetrisindeki yüksek arka plan RSS'si, genellikle ön plandan ayrıldıktan sonra korunan kullanıcı arayüzü tahsislerini (Bellek yoğun bir kullanıcı arayüzüyle birlikte konumlandırılan bağlama hizmeti ve Etkinliklere yanıt olarak belleği serbest bırakma bölümlerine bakın) veya arka plan dizine ekleme ve senkronizasyon sırasında tek seferlik dosya okumalarını yansıtır. Başsız arka plan çalışmasının kare son tarihleri olmadığından, arka plan senkronizasyonu bütçesini kısa süreliğine aştığında tek seferlik dosya önbelleği sayfalarının çıkarılması ve etkin olmayan yığın sayfalarının zRAM ile değiştirilmesi, kullanıcı tarafından görülebilen bir takılmaya neden olmaz ve arka plandaki ani artışların kullanıcının ön planda çalışan uygulamasını çıkarmasını önler.
Adım adım iş akışı için Çalışma kümenizi ölçme ve bütçeleri kalibre etme başlıklı makaleyi inceleyin.
Bütçe durumlarının işlem durumlarıyla eşleşme şekli
Platform, manifest bütçe durumlarını çalışma zamanı işlem durumlarıyla eşler. Bu eşleme, Activity Manager'ın hem işlemin kendi etkin bileşenlerinden hem de diğer uygulamalardan gelen hizmet bağlamaları veya içerik sağlayıcı sorgularından hesapladığı RunningAppProcessInfo.importance değerine göre yapılır:
foreground: Devam ettirilen bir etkinliği barındırma, ekran kapandığında en üstteki uygulama durumunu koruma veya en üstteki ön planda çalışan uygulama tarafından etkin bir şekilde bağlı ya da sorgulanan bir hizmeti veya içerik sağlayıcıyı barındırma gibi etkin, görünür kullanıcı etkileşimleri.perceptible: Görünür bir pencere olmadan kullanıcı tarafından algılanabilen iş yükleri. Örneğin, etkin medya oynatma, adım adım rota, kamera veya mikrofonla kayıt, etkin arka plan indirmeleri, veri senkronizasyonu ön plan hizmetleri ya daBIND_NOT_FOREGROUNDgibi işaretler kullanılarak ön plan istemcisine bağlı hizmetler (BIND işaretleriyle devralmayı kontrol etme bölümüne bakın). Dahili platform metrikleri bu iş yüklerinden bazılarınıPROCESS_STATE_IMPORTANT_FOREGROUNDolarak etiketleyebilir ancak bu dahili sabit, görünür bir pencereyi ifade etmez veperceptiblebütçesi tarafından yönetilir.background: Arka plan işleri, alarmlar ve yayın alıcıları gibi kullanıcı tarafından hemen fark edilemeyen işlemler.
Aşağıdaki tabloda, çalışma zamanı önem düzeylerinin manifest durumlarıyla nasıl eşleştiği gösterilmektedir:
Manifest android:state |
Çalışma zamanı önemi (RunningAppProcessInfo) |
Tipik bileşenler |
|---|---|---|
foreground |
IMPORTANCE_FOREGROUNDIMPORTANCE_TOP_SLEEPING |
Devam ettirilen görünür etkinlik, ekranı kilitli olan en üstteki uygulama, en üstteki uygulamaya bağlı hizmet veya içerik sağlayıcı |
perceptible |
IMPORTANCE_FOREGROUND_SERVICEIMPORTANCE_VISIBLE |
Etkin medya oynatma, navigasyon, indirme veya senkronizasyon ön plan hizmetleri; ön plan hizmeti veya görünür işaretlerle bağlı hizmet |
background |
IMPORTANCE_PERCEPTIBLEIMPORTANCE_CANT_SAVE_STATEIMPORTANCE_SERVICEIMPORTANCE_CACHED |
Arka plan işleri, alıcılar, arka plan senkronizasyonu ve önbelleğe alınmış işlemler |
İstemci bağlamalarının bir hizmet veya sağlayıcı ana makine işlemini nasıl yükselttiği ve istemci bağlamayı kaldırdıktan veya ekrandan çıktıktan sonra işlemin nasıl tekrar background ya da cached durumuna geçtiği hakkında ayrıntılı bilgi için Hizmet bağlamaları ve işlem durumları başlıklı makaleyi inceleyin.
Bir işlem, etkin bileşenleri olmadan önbelleğe alınmış duruma (IMPORTANCE_CACHED) girdiğinde platform, önbelleğe alınmış işlem dondurma ve geri alma işlemlerinin yanı sıra background bellek bütçesini uygulamaya devam eder. Böylece, dondurma veya sıkıştırma gerçekleşmese bile işlem sınırlı kalır.
Uygulamanızın işlem durumunu ve bütçesini inceleme
Geliştirme sırasında uygulamanızın etkin işlem durumunu ve önemini incelemenin bir yolu, ADB'yi kullanarak Etkinlik Yöneticisi'ni sorgulamaktır:
adb shell dumpsys activity processes <package-name>
Aşağıdaki kısaltılmış örnekte, ön plan hizmeti çalıştıran bir uygulamanın işlem kaydı ve OOM kontrol girişleri gösterilmektedir:
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
Bu çıkışta:
curProcState=4vestate: cur=FGS, işlemin ön plan hizmeti durumunda olduğunu gösterir. Uygulamanız etkin bir görünür etkinliğe ev sahipliği yapıyorsa bu,TOPolarak görünür. Önemli bir ön plan bileşeni (ör. indirme veya senkronizasyon) çalıştırıyorsaIMPFolarak görünür.- İşlem OOM kontrol tablosunda
prcp, işleminperceptiblebütçesine karşılık gelen algılanabilir öncelik katmanı altında değerlendirildiğini gösterir.
Uygulamanızın mevcut yerleşik bellek kullanımını incelemek için:
adb shell dumpsys meminfo <package-name>
Çok işlemli uygulamalar
Uygulamanız işini birden fazla işleme bölüyorsa <processes> içindeki <process> etiketini kullanarak özel işlem bütçeleri yapılandırın.
Örneğin, bir müzik yayını uygulaması (com.example.radio) düşünün:
- Ana işlem: Görünür kullanıcı arayüzünü ve ses çalma motorunu barındırır (
mediaPlaybackön plan hizmetiyleMediaSessionService). Görünür olduğunda işlem, 180 MB ön plan bütçesiyle çalışır. Kullanıcı, müzik çalmaya devam ederken uygulamadan ayrıldığında işlemperceptibledurumuna girer. Bu durumda, oynatma motoru ve ses arabelleği için 64 MB bütçe yeterlidir. - Senkronizasyon işlemi (
:sync): Arka planda meta veri senkronizasyonu ve indirme dizine ekleme işlemlerini çalıştıran özel işlem. Bu işlem yalnızca arka planda etkin olduğundanstate="background"değerini açıkça belirtmeniz gerekmez. Tek bir bütçe geçerlidir.
<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>
Manifest dosyanızda bir <processes> bloğu beyan ettiğinizde, yalnızca <memory-budget> beyan eden işlemler için <process> girişlerini listeleyin. Uygulamanın geri kalanını cihazın sistem sınırında bırakırken yalnızca belirli alt süreçleri bütçelemek istiyorsanız doğrudan <application> altındaki <memory-budget> öğelerini atlayabilirsiniz.
Herhangi bir alt işlemdeki bellek kullanımı, hem kendi işlem bütçesine hem de kapsayıcı paket bütçesine dahil edilir. Bir işlem, çalışma zamanında işlem bütçesini veya paket bütçesini (hangisine önce ulaşılırsa) aşarsa bellek baskısı yaşar.
Yüksek yoğunluklu ekranlar için bütçeleri ölçeklendirme
Bellekte kapladığı yer, tek seferde çizilecek piksel sayısına göre önemli ölçüde değişen uygulamalar (ör. pencere boyutunda bit eşlemleri önbelleğe alan bir fotoğraf galerisi uygulaması) için Android, bütçeleri ekran özelliklerine göre dinamik olarak ölçeklendiren iki alternatif mekanizma sunar:
Ekran yoğunluğu grubuna göre ölçeklendirme (
android:additionalMbPerDensity): Ekranınmdpi(1,0x / 160 dpi) değerine göre yoğunluk oranının karesiyle orantılı olarak megabayt ekler.Bunun nedeni, 2D raster çizilebilir öğelerin ve kullanıcı arayüzü öğelerinin hem genişlik hem de yükseklik açısından ölçeklendirilmesidir:<!-- Baseline 180MB + 16MB multiplied by (density ratio)^2 --> <memory-budget android:maxMb="180" android:additionalMbPerDensity="16" />mdpiekranda (1,0x) bütçe 180 + 16 × 12 = 196 MB'tır.xxhdpiekranda (3,0x), bütçe 180 + 16 × 32 = 324 MB'a ölçeklenir.Pencere piksel sayısına göre ölçeklendir (
android:additionalBytesPerDisplayPixel): Etkin pencere sınırlarında (WindowMetrics.getBoundsgenişlik × yükseklik) piksel başına bayt ekler. Bu, tam pencere grafik yüzeyleri, oluşturma arabellekleri veya yüksek çözünürlüklü fotoğraf önbellekleri ayıran uygulamalar için uygundur. Bu uygulamalarda bellek tüketimi, kullanıcı arayüzü yoğunluk grupları yerine doğrudan ham piksel sayısıyla ölçeklenir:<!-- Baseline 128MB + 16 bytes per window pixel --> <!-- For example, a 4-byte RGBA full-window buffer with double or quadruple buffering --> <memory-budget android:maxMb="128" android:additionalBytesPerDisplayPixel="16" />Tam ekran 1080p pencerede (1080 × 2400 ≈ 2,59 milyon piksel), bu, temel bütçeye 41.472.000 bayt (≈ 39,6 MB, burada 1 MB = 1.048.576 bayt) ekler. 1440p pencerede (1440 × 3120 ≈ 4,49 milyon piksel) 71.884.800 bayt (≈ 68,6 MB) ekler.
Bu iki özellik birbirinin alternatifidir. Uygulamanızın birincil ölçeklendirme faktörüyle eşleşen özelliği seçin ve her ikisini aynı maddede birleştirmekten kaçının.
Cihaz form faktörleri için özelleştirme
Telefonlar, tabletler ve Wear OS için APK gönderirken farklı donanım hedeflerine yönelik bütçeleri ayarlamak için android:feature özelliğini kullanın.
Wear OS kol saatlerinde RAM sınırlıdır ve uygulamanın kullanıcı arayüzü ile özellik seti çok daha basittir. watch özelliği için özel olarak daha sıkı bir bütçe belirleyebilirsiniz:
<!-- 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" />
Çözüm kuralı: Geçerli olan son madde uygulanır.
Sistem, bir uygulama veya işlem için birden fazla <memory-budget> öğesi tanımlarken bunları manifest dosyasında göründükleri sırayla değerlendirir ve geçerli olan son bütçe maddesini uygular.
Atlanan bir android:state, varsayılan olarak foreground değerini alır. Bu değer, daha sonraki bir ifadeyle geçersiz kılınmadığı sürece perceptible ve background için de geçerli olur. Sıra yan tümceleri foreground (veya atlanan android:state) ile perceptible ile background arasında olmalıdır ve android:feature geçersiz kılmaları eşleşen eyalet grubuna yerleştirilmelidir. Böylece, sondaki atlanan eyalet geçersiz kılma işlemi önceki perceptible veya background yan tümcelerini gölgelemez.
XML özelliği referansı
Tüm megabayt (MB) özellikleri ikili birimler kullanır (1 MB = 1.048.576 bayt) ve Linux cgroup memory.current ücretiyle eşlenir (Zygote'tan paylaşılan bellek hariç tutulur).
| Özellik | Biçim | Varsayılan | Açıklama |
|---|---|---|---|
android:maxMb |
Tam sayı (> 0) | Zorunlu | MB cinsinden temel yerleşik bellek bütçesi sınırı. |
android:state |
Enum | foreground |
Bu bütçenin geçerli olduğu işlem durumu: foreground, perceptible veya background. Atlanırsa varsayılan olarak foreground olur ve daha sonraki bir yan tümce tarafından geçersiz kılınmadığı sürece perceptible ve background değerlerine basamaklanır. |
android:additionalMbPerDensity |
Tam sayı (≥ 0) | 0 |
Ek megabaytlar, mdpi (1,0x) ile karşılaştırıldığında ekran yoğunluğu oranının karesiyle çarpılır. |
android:additionalBytesPerDisplayPixel |
Tam sayı (≥ 0) | 0 |
Pencere pikseli başına ayrılan ek bayt sayısı (WindowMetrics.getBounds genişlik × yükseklik). Yüzey arabellekleri ve bit eşlemler için kullanışlıdır. |
android:feature |
Dize | Tümü | Yan tümceyi belirli donanım özelliklerini bildiren cihazlarla kısıtlar: watch, automotive veya leanback. |
Çalışma setinizi ölçme ve bütçeleri kalibre etme
İyi bir bütçe, uygulamanızın belirli bir durumdaki çalışma grubunun hemen üzerinde yer alır, kısıtlanmamış zirvesinde değil. Bu noktayı bulmak için gerçekçi bir kullanıcı işlemleri akışı çalıştırın, bütçeyi adım adım azaltın ve uygulamanızın bütçeyi karşılamaya başladığı anı izleyin.
Çalışma kümesi ile yerleşik bellek arasındaki farkı anlama
Bütçe olmadan uygulamanızın yerleşik ayak izi (memory.current) iki tür bellek barındırır:
- Çalışma grubu: Java, Kotlin ve yerel yığın ile mevcut kullanıcı işlemleri akışının tekrar tekrar dokunduğu, dosya destekli etkin sayfalar (DEX kodu, yerel kitaplıklar ve öğeler)
- Soğuk boşluk: Önceki ekranlardan bitmap önbellekleri, tek seferlik başlangıç arabellekleri, bir ayırma patlamasından sonra kalan yığın boşluğu ve bir kez okunan ve bir daha dokunulmayan dosya sayfaları
Kısıtlanmamış zirve noktanızdaki bir bütçe, soğuk boşluğu asla geri almaz. Çalışma kümenizin altındaki bir bütçe, çekirdeğin yığın sayfalarını değiştirmesine ve uygulamanızın hemen geri döndürdüğü kod sayfalarını çıkarmasına neden olur. Bu çalışma kümesi yeniden hataları, CPU süresi ve pil tüketimine neden olur. Kullanıcı arayüzü iş parçacığına ulaştıklarında kare kaybına da yol açarlar.
Aradığınız şey büküm noktası: Geri alma işleminin yalnızca soğuk boşluğu kaldırdığı en düşük bütçe. Böylece yeniden hata, CPU süresi ve kare süreleri bütçe olmadan olduğu yerde kalır.
Test ortamınızı hazırlama
Üretim gibi davranan tekrarlanabilir bir test oluşturun:
- Gönderdiğiniz R8 ile optimize edilmiş, hata ayıklanamayan yayınlanan derlemeyi kalibre edin. Hata ayıklama derlemeleri daha fazla bellek kullandığından, hata ayıklama derlemesine göre ayarlanmış bir bütçe, yayın için çok yüksektir.
- Android 17 QPR2 (API düzeyi 37.2) veya sonraki sürümleri çalıştıran bir cihazda ya da emülatörde test edin. Google Play içermeyen bir
userdebugderlemesi veya emülatör görüntüsü kullanmak en iyisidir. Bazı cihazlar, aşağıdaki bölümlerde yer alan cgroup dosyalarını yalnızcaadb rootkomutunu çalıştırdıktan sonra okumanıza izin verir. - Bütçe ayırmayı planladığınız her durum için bir kez Jetpack Macrobenchmark, UI Automator veya
adb shell inputile 30 ila 60 saniyelik bir kullanıcı işlemleri akışı için komut dosyası oluşturun. Boşta olan bir uygulama, çalışma kümesi hakkında bilgi vermez.foreground: Yoğun listeleri ve medya ızgaralarını kaydırın, ana hedefleriniz arasında geçiş yapın, menüleri ve iletişim kutularını açın ve animasyonları çalıştırın.perceptible: Algılanabilir çalışma (ör. ses çalma veya indirme) devam ederken görünür pencereden çıkın, 10-15 saniye çalışmasına izin verin ve ardından uygulamaya dönün.background: Uygulamanın görünür bir kullanıcı arayüzü yokken senkronizasyon,WorkManagerişi (ör.adb shell cmd jobscheduler run -f <package-name> <job-id>ile), başlatılmış bir hizmet veya yayın alıcısı tetikleme
background bütçesini, işlem önbelleğe alınmış durumda (IMPORTANCE_CACHED) boştayken değil, arka plan çalışması etkin bir şekilde yürütülürken ölçün.
background bütçesi önbelleğe alınmış durumdayken geçerli olsa da boşta olan önbelleğe alınmış bir işlem, arka plan çalışması yürütmez ve arka plan işlerinizin veya alıcılarınızın çalışma grubunu yansıtmaz.
Referans değerinizi ölçme
Normalin nasıl göründüğünü anlamak için bütçesi olmayan çalıştırmalarla başlayın.
<memory-budget> beyanları içermeyen bir derleme kullanın ve her çalıştırmayı yeni bir işlemle (adb shell am force-stop <package-name>) başlatın.
Her uygulama işleminin kendi cgroup v2 bellek denetleyicisi vardır. Cgroup düzeni cihazlar arasında farklılık gösterebileceğinden, yolu kendiniz oluşturmak yerine /proc/<PID>/cgroup dosyasının 0:: satırından yolu bulun. Cihaz kabuğunun tırnak işaretlerini görebilmesi için her cihaz komutunu adb shell aracına tek tırnak içine alınmış bir dize olarak iletin:
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"
Bu dosyaları okumak kolaydır ve uygulamanızı rahatsız etmez. Bu nedenle, çalışma sırasında her saniye örnekleme yapabilirsiniz:
memory.current: İşleme yüklenen yerleşik bellek (bayt cinsinden). Bu,<memory-budget>sınırının sayısıdır.memory.swap.current: zRAM takasına sıkıştırılmış bellek (bayt cinsinden)anonandfileinmemory.stat: uygulamasında yerleşik anonim ve dosya destekli bellek (bayt cinsinden)workingset_refault_anonveworkingset_refault_file,memory.stat: Uygulamanızın tekrar ihtiyaç duyduğu, takas edilen veya çıkarılan sayfaların çalışan sayıları. Bu sayaçlar, bütçenin çok kısıtlı olduğunun en önemli göstergesidir.
İzin hatası alırsanız adb root komutunu çalıştırıp tekrar deneyin. Kök erişimi elde edemezseniz adb shell dumpsys meminfo <package-name> size daha kaba bir görünüm sunar. App Summary, Java Heap, Native Heap ve Stack çoğunlukla anonim bellektir, Code ise dosya desteklidir. Yazılım bit eşlemleri Native Heap ve bütçeniz için hesaba katılırken donanım bit eşlemleri ve Graphics hesaba katılmaz. Kare süresini ölçerken dumpsys meminfo komutunu döngü içinde çalıştırmayın. Uygulamanızda çöp toplamayı zorunlu kılar ve tüm bellek eşlemelerini inceler. Bu da ölçmeye çalıştığınız şeyi değiştirir.
Aday bütçeleri tarama
Uygulamanızı yeniden derlemeden aday bütçeleri deneyebilirsiniz. Çalışan bir işlem için bütçe belirlemek üzere am memory-limiter manual, bütçeyi kaldırmak için ise none komutunu kullanın:
adb shell am memory-limiter manual <PID> 120MBadb shell am memory-limiter manual <PID> none
Manuel geçersiz kılma, işlem durumu değişiklikleri boyunca o işlem örneğinin kullanım ömrü boyunca devam eder. Bu nedenle, devam etmeden önce bu ayarı temizleyin veya işlemi yeniden başlatın.
Ayrıca işlemin katı sınırını (memory.max) aynı değere ayarlar. Böylece, uygulamanızın çalışma grubunun altındaki bir aday bütçe, işlemi yavaşlatmak yerine sonlandırabilir. Bu bütçeyi başarısız olarak değerlendirin.
Ayrıca, bir araç bağımsız değişkeninden aday değer okuyup MemoryBudgetManager.processBudgetBytes değerini ayarlayarak karşılaştırma derlemesinden tarama yapabilirsiniz. Çalışma zamanı bütçesi yalnızca manifest bütçesini daraltabilir. Bu nedenle, <memory-budget> beyanları olmadan derleme üzerinde tarama yapın. Ayrıca, clearProcessBudget yöntemini çağırana kadar işlem durumu değişiklikleri boyunca geçerli kalır.
Referans değerinizden en yüksek memory.current ile başlayın ve her seferinde 10 ila 15 MB azaltın. Her bir aday bütçe için:
- Yeni bir süreç başlatın, süreci hedef duruma getirin ve bütçeyi uygulayın.
- Kullanıcı işlemleri akışını bir kez çalıştırarak ısınma sağlayın. Bütçe uygulandığında sıfırdan başlatma sayfaları bir kez çıkarılır. Bu geçiş yerine sabit durumu ölçmek istersiniz.
Kare istatistiklerini sıfırlayın ve başlangıçtaki yeniden hata sayaçlarını ve işlem CPU süresini (saat tikleri cinsinden
utimevestime) kaydedin:adb shell dumpsys gfxinfo <package-name> resetadb shell "grep -E '^workingset_refault_(anon|file) ' $CG/memory.stat"adb shell "cut -d ' ' -f 14,15 /proc/$PID/stat"Kullanıcı işlemleri akışınızın ölçülen geçişini çalıştırın.
memory.current,anon,file, yeniden hata sayaçları ve CPU süresini tekrar kaydedin ve kare istatistiklerini toplayın.dumpsys gfxinfoaynı istatistikleri birden fazla kez yazdırabilir. Bu nedenle, uygulamanın tamamını kapsayan ilk eşleşmeyi alın:adb shell "dumpsys gfxinfo <package-name>" | grep -m 1 'Janky frames:'adb shell "dumpsys gfxinfo <package-name>" | grep -m 1 '95th percentile'
Her adayı en az üç kez tekrarlayın ve bir adayın tüm tekrarlarını arka arkaya çalıştırmak yerine adayları aralıklı olarak çalıştırın. Her sonucu tek bir çalıştırmayla değil, temel çalıştırmalarınızın dağılımıyla karşılaştırın.
Sonuçları okuma
Bütçe azaldıkça uygulamanız üç aşamadan geçer:
- Diz üstü:
memory.current, soğukfilesayfaları ve boş yığınları atarak bütçeyi azaltır.anondüz kalır, yeniden varsayılan değerler referans değerine yakın kalır ve CPU süresi ile kare süreleri değişmez. Bu, olmak istediğiniz yerdir. - Diz altı: Soğuk sayfalar kaybolur. Bu nedenle çekirdek, uygulamanızın hâlâ kullandığı yığın sayfalarını değiştirmeye başlar. Yeniden hata sayısı her çalıştırmada yüzlerce veya binlerce sayfa artar ve CPU süresi uzar. Kare süreleri, boşta olan bir test cihazında neredeyse hiç değişmeyebilir ancak uygulamanız artık kendi çalışma grubunu sıkıştırıp açmak için CPU süresi ve pil harcar.
- Diz çizgisinin çok altında: Geri alma ve takas hataları, kullanıcı arayüzü iş parçacığına
ve
RenderThread'ye düşer. Takılma, kare süreleri ve sıcak devam süreleri artar.
Kısıtlanmamış referansınızla karşılaştırıldığında, aşağıdaki koşulların tümünü karşılayan bir aday bütçe kabul edilir. Bu koşullar makul bir başlangıç noktasıdır:
- İşlem, çalıştırma sırasında devam eder.
- Yeniden hata farkları, referans değere yakın kalır
- İşlem CPU süresi %5'ten fazla artmaz.
- Takılan kareler en fazla 1 yüzde puanı artar
- P95 kare süresi en fazla 3 ms artar
- Sıcak devam ettirme süresi (
adb shell am start -WüzerindenTotalTime),perceptiblevebackgroundbütçeleri için en fazla %15 artar.
Kırılma noktası, kabul edilen en düşük aday bütçedir. Örneğin, 138 MB'lık kısıtlanmamış zirveye sahip varsayımsal bir uygulama için ön plan taraması aşağıda verilmiştir:
| Aday bütçe | memory.current |
anon |
file |
Yeniden hata (sayfa) | P95 kare süresi | Rejim |
|---|---|---|---|---|---|---|
| Yok | 138 MB | 75 MB | 63 MB | 0 | 10,5 ms | Temel |
| 125 MB | 121 MB | 74 MB | 47 MB | 10 | 10,5 ms | Diz üstü |
| 110 MB | 110 MB | 75 MB | 35 MB | 15 | 10,8 ms | Diz |
| 95 MB | 95 MB | 74 MB | 21 MB | 176 | 11,2 ms | Diz altı |
| 85 MB | 85 MB | 68 MB | 17 MB | 2.050 | 12,8 ms | Diz altı |
| 70 MB | 70 MB | 54 MB | 16 MB | 8.771 | 16,5 ms | Dizin çok altında |
Bütçenizi seçin
android:maxMb değerini, dizin %10 ila %20 üzerinde olacak şekilde ayarlayın. Böylece kullanıcı verileri, yerel ayarlar ve tahsisat artışlarındaki farklılıklar uygulamanızı bu değerin altına düşürmez. Önceki örnekte, diz 110 MB'ta olduğundan 125 MB iyi bir seçimdir.
Ardından, yeni <memory-budget> beyanlarını içeren bir yayınlanan derleme yükleyin, kullanıcı işlemleri akışınızı tekrar çalıştırın ve geçiş ölçütlerinin hâlâ geçerli olup olmadığını kontrol edin.
Ön planda olmayan bütçeleri sıcak dönüşle doğrulama
Uygulamanız bu durumdayken sıkı bir perceptible veya background bütçesi iyi görünebilir ancak kullanıcı geri döndüğünde size maliyet çıkarabilir. Dönüş yolculuğunu da test edin:
- Ön plan akışını çalıştırın, uygulamayı hedef duruma taşıyın ve
am memory-limiter manualile aday bütçeyi uygulayın. - Algılanabilir veya arka plan çalışmasının 10 ila 15 saniye boyunca çalışmasına izin verin.
- Geçersiz kılmayı temizleyin (
adb shell am memory-limiter manual <PID> none), ardından uygulamayı hemenadb shell am start -Wile geri getirin veTotalTimedeğerini kaydedin. AdayıMemoryBudgetManagerüzerinden ayarlarsanız uygulama döndürülmeden önceclearProcessBudgetyöntemini çağırın. - Kısa bir ön plan etkileşimi çalıştırın ve yeniden hata sayısını ve kare istatistiklerini geçiş ölçütleriyle karşılaştırın.
Uygulama bu durumdayken ve sıcak dönüşte her ikisini de geçen en düşük bütçeyi seçin ve aynı %10 ila %20'lik ek payı ekleyin.
Ekran ölçeklendirmesini kalibre etme
android:additionalBytesPerDisplayPixel kullanıyorsanız ön plan dizini, 1080 × 2400 (≈ 2,59 milyon piksel) ve 1440 × 3120 (≈ 4,49 milyon piksel) gibi farklı çözünürlüklere sahip iki ekranda bulun.
android:additionalBytesPerDisplayPixel değerini elde etmek için iki kırılma noktası arasındaki farkı (bayt cinsinden) piksel sayısındaki farka bölün. Çözünürlükten bağımsız tabanı elde etmek için daha küçük ekranın dizinden piksel başına miktarını çıkarın ve android:maxMb elde etmek için tavan boşluğu ekleyin.
Çalışma zamanı API'leri (ikincil dinamik seçenek)
Bütçeleri AndroidManifest.xml içinde statik olarak bildirmek, neredeyse tüm uygulamalar için tercih edilen çözümdür. Ancak Android, dinamik iş yükleri olan uygulamalar veya çalışma zamanı denemeleri için ikincil bir seçenek olarak çalışma zamanı SDK API'si sunar.
Çalışma zamanı API'si şunları yapmanıza olanak tanır:
- Mevcut bellek kullanımını ve geçerli bütçeleri sorgulama
- İşlem bütçenizi dinamik olarak azaltın.
- İşletim sistemi doğrudan geri alma işlemini tetiklemeden önce önbellekleri proaktif olarak kırpmak için bütçe aşımı etkinliklerini dinleyin.
Android SDK API (MemoryBudgetManager)
MemoryBudgetManager sistem hizmeti, Android 17 QPR2'den (küçük SDK sürümü, API düzeyi 37.2 / Build.VERSION_CODES_FULL.CINNAMON_BUN_2) itibaren Kotlin ve Java dillerinde yazılmış uygulamalarda kullanılabilir.
Hizmeti alma
MemoryBudgetManager özelliğine erişmeden önce SDK_INT_FULL değerini kontrol ederek cihazın Android 17 QPR2 veya sonraki bir sürümü çalıştırdığından emin olun. Uygulamanızın minSdk değeri 36'dan düşükse öncelikle SDK_INT değerini kontrol edin. Böylece, Android 15 (API düzeyi 35) veya daha eski sürümleri çalıştıran cihazlar SDK_INT_FULL değerini okurken NoSuchFieldError hatası vermez:
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)
}
Sorgu kullanımı ve bütçeler
// 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
Bütçeleri dinamik olarak ayarlama veya temizleme
Hafif görevler sırasında belleği kısıtlamak için çalışma zamanında daha sıkı bir bütçe belirleyebilir veya görev tamamlandığında bütçeyi temizleyebilirsiniz:
// 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()
İşlem foreground, perceptible ve background durumları arasında geçiş yaptığında bütçeleri otomatik olarak değiştiren <memory-budget> manifest yan tümcelerinin aksine, MemoryBudgetManager aracılığıyla ayarlanan dinamik bütçe, kodunuz güncellenene veya temizlenene kadar tek bir tavan uygular. Saha denemeleri yapmak veya duruma özel sınırları yönetmek için çalışma zamanı API'sini kullanıyorsanız yaşam döngüsü geçişleri genelinde dinamik bütçeyi güncelleyin veya temizleyin (ör. ProcessLifecycleOwner veya etkinlik yaşam döngüsü geri çağırma yöntemlerini kullanarak, Uyarlanabilir resim düzenleyici örneğinde gösterildiği gibi).
Performans ve eş kuvvetlilik
Çalışma zamanında MemoryBudgetManager ile çalışırken aşağıdaki özellikleri göz önünde bulundurun:
- Idempotency: Tüm bütçe mutasyon API'leri idempotenttir.
clearProcessBudget()veyaclearPackageBudget()işlevini tekrar tekrar ya da etkin bir dinamik bütçe yokken çağırmak güvenli bir işlem yapmama durumudur. Benzer şekilde,setProcessBudgetBytes()veyasetPackageBudgetBytes()işlevini art arda aynı değere ayarlamak, gereksiz sistem yeniden yapılandırmalarını tetiklemez. - Çağrı sıklığı: Bütçe ayarlama, güncelleme veya temizleme işlemleri, işlemin işletim sistemi bellek denetleyicisini güncellemek için sistem hizmetine çapraz işlem çağrısı yapar. Hafif olsa da tamamen ücretsiz değildir. Bu API'leri ayrı yaşam döngüsü geçişleri ve görev aşamaları sırasında (ör. bir etkinliğe girerken veya etkinlikten çıkarken, arka plan işlemeyi başlatırken veya tamamlarken ya da konuşma isteklerini işlerken) çağırın ve bunları dar döngülerde, ses işleme iş parçacıklarında veya kare başına oluşturma rutinlerinde çağırmaktan kaçının.
- Sorgu sıklığı:
processCurrentUsageBytesvepackageCurrentUsageBytessorgu özellikleri, sistem hizmetinden canlı bellek kullanımını getirir. Sorgular hızlı olsa da sürekli döngüler halinde sorgulanmak yerine isteğe bağlı olarak (ör. görev geçişleri sırasında veyaOnOverBudgetListenergeri çağırmalarında) çağrılmalıdır.
Bütçe aşımı baskısı geri çağırmalarını dinleme
Uygulamalar, bellek kullanımı bütçe eşiğini aştığında bildirim almak için bir dinleyici kaydedebilir. Bu sayede uygulama, işletim sistemi işlemi veya paketi bütçesi dahilinde tutmak için belleği geri alırken uygulama düzeyinde proaktif temizleme (bellek içi bitmap önbelleklerini temizleme gibi) gerçekleştirebilir:
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)
Bütçe aşımı geri aramalarıyla ilgili en iyi uygulamalar:
- Arka plan kullanın
Looper: Mümkün olduğundaregisterProcessOverBudgetListenerveyaregisterPackageOverBudgetListeneriçinHandlerThreadLooperarka planı iletin. Önbellek çıkarma veya dosya temizleme işlemlerini arka plan iş parçacığında çalıştırmak, işlem bellek baskısı altındayken kullanıcı arayüzü iş parçacığında kare oluşturma ile çakışmayı önler.Looper.getMainLooper, yalnızca ana iş parçacığına bağlı kullanıcı arayüzü durumuna dokunan dinleyiciler için ayrılmıştır. - Tekrarlanan geri çağırmalar bekleyin: Bütçe aşımı geri çağırmaları tek seferlik bildirimler değildir. Kullanım bütçeyi aştığında işletim sistemi önce soğuk sayfaları geri alır. Baskı devam ederse sistem, belleği geri almaya ve dinleyicinizi saniyede en fazla bir kez çağırmaya devam eder. Geri çağırma işlevinizi eş güçlü yapın. Uygulamanızın yalnızca tek bir bildirime ihtiyacı varsa veya temizleme işlemleri arasında daha uzun bir bekleme süresi istiyorsa geri çağırma içinde
unregisterProcessOverBudgetListener(veyaunregisterPackageOverBudgetListener) yöntemini çağırın ya da temizleme işleminizi bir bekleme süresi zaman damgası veyaAtomicBooleanile koruyun. - Bütçeyi daraltmadan önce kaydolun:
setProcessBudgetBytesveyasetPackageBudgetBytesyöntemini çağırdığınızda mevcut bellek kullanımı yeni bütçeyi zaten aşıyorsa yeni bütçe uygulanır uygulanmaz bütçe aşımı etkinliği tetiklenir. Bütçeyi ayarlamadan önce dinleyicinizi kaydedin. Böylece uygulamanız, bütçe aşımıyla ilgili ilk bildirimi kaçırmaz. - Hızlı olun: Geri kazanım işlemleri anında rahatlama sağlamalıdır. Yük altındayken yapılan karmaşık hesaplamalar performansı düşürür.
- Tahsislerden kaçının: Geri çağırma içinde yeni nesneler tahsis etmeyin veya yeni iş parçacıkları başlatmayın. Aksi takdirde, işletim sistemi doğrudan geri alma işlemini hemen tetikleyebilir.
- Yüksek verimli hedeflere odaklanın: Büyük bit eşlemleri, oluşturma arabelleklerini çıkarmak veya bellekle eşlenen dosyaları kapatmak, çok sayıda küçük nesneyi serbest bırakmaktan çok daha etkilidir.
Çalışma zamanı API örneği
Aşağıdaki örnekte, Kotlin'de Android SDK API'yi kullanarak çalışma zamanı API'sinin nasıl uygulanacağı gösterilmektedir:
Android SDK örneği: Uyarlanabilir resim düzenleyici
Bu örnekte, manifest dosyasında çok katmanlı tuvalini barındırmak için 256 MB'lık bir üst sınır beyan eden bir resim düzenleme uygulaması (com.example.imageeditor) gösterilmektedir:
<manifest ... >
<application ... >
<!-- Manifest ceiling accommodates the heaviest editing workload -->
<memory-budget android:maxMb="256" />
</application>
</manifest>
Kullanıcı, basit küçük resim galerisine göz atarken uygulama, Kotlin'deki Android SDK API'sini kullanarak işlem bütçesini dinamik olarak 96 MB'a düşürür.
Kullanıcı çok katmanlı düzenleme tuvalini açtığında uygulama, dinamik bütçeyi temizleyerek 256 MB'lık manifest tavanını tamamen geri yükler. Ayrıca, kullanıcı arayüzü iş parçacığını engellemeden baskı altında önbelleğe alınmış önizleme bit eşlemlerini çıkarmak için galeri bütçesini uygulamadan önce arka plan HandlerThread Looper üzerinde bir OnOverBudgetListener kaydeder.
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"
}
}