Not: Çoğu durumda aşağıdakileri yapmanızı öneririz: Glide aracını uygulamanızda bit eşlemler getirmek, kodunu çözmek ve görüntülemek için kitaplık. Özetlerin çoğunu kaydırarak ve bunları ele almanın karmaşıklığını başka görevler de vardır. Glide'ı kullanma ve indirme hakkında bilgi edinmek için şu sayfayı ziyaret edin: GitHub'da Glide deposu.
Kullanıcı arayüzünüze tek bir bit eşlem yüklemek oldukça basittir ancak işler giderek artmaktadır
tek seferde daha büyük bir resim grubunu yüklemeniz gerekiyorsa karmaşık hale gelir. Birçok durumda (örneğin,
ListView
, GridView
veya ViewPager
gibi bileşenleri), toplam resim sayısı ile
ekranı hızlıca kaydırabilme olanağı da aslında sınırsızdır.
Bunun gibi bileşenlerle, çocukların görünümleri hareket ederken geri dönüştürülerek bellek kullanımı azaltılır. ekranın dışına çıkar. Çöp toplayıcı, uzun süreli referanslar tutmadığınız varsayılarak yüklenen bitmap'lerinizi de boşaltır. Tüm bunlar gayet normal, ancak akıcı ve hızlı yüklenen bir kullanıcı arayüzünü korumak için bu resimlerin ekrana her geri geldiklerinde sürekli olarak işlenmesini önlemek istiyorsunuz. Anılar ve disk önbelleği genellikle bu konuda işe yarayabilir. Bu sayede, bileşenler, işlenen görüntüleri hızlıca yeniden yükleyebilir.
Bu derste, yanıt verme hızını artırmak için bellek ve disk bit eşlem önbelleği kullanma konusunda ve akıcılığınızı konuşturmak için kullanabileceğiniz birkaç teknik var.
Önbellek Kullan
Bellek önbelleği, değerli uygulama belleğini kullanma pahasına bitmap'lere hızlı erişim sağlar. LruCache
sınıfı (geri kullanılmak üzere Destek Kitaplığı'nda da mevcuttur)
özellikle de bit eşlemleri önbelleğe alma görevine uygundur.
Güçlü referanslı LinkedHashMap
içinde referans verilen nesnelerden en az
son kullanılan üyeye ulaşılmasını sağlayın.
Not: Geçmişte popüler bir bellek önbelleği uygulaması
Bununla birlikte, SoftReference
veya WeakReference
bit eşlem önbelleği
bu önerilmez. Android 2.3 (API Düzeyi 9) sürümünden itibaren çöp toplayıcı
yumuşak/zayıf referanslar toplama konusunda agresif ve bu durum onları oldukça etkisiz hale getirir. Ayrıca,
Önceden bir bit eşlemin yedekleme verileri yerel bellekte depolanıyordu.
tahmin edilebilir bir şekilde yayınlanamaması, bu durumun uygulama
ve çökmeye neden olabilir.
LruCache
için uygun bir beden seçerken çeşitli faktörleri dikkate alın.
dikkate alınmalıdır. Örneğin:
- Etkinliğinizin ve/veya uygulamanızın geri kalanı bellek ne kadar yoğun?
- Ekranda aynı anda kaç resim olacak? Kaç kişinin hizmet vermeye hazır olması gerekir? ekranda mı?
- Cihazın ekran boyutu ve yoğunluğu nedir? Ekstra yüksek yoğunluklu ekran (xhdpi) cihaz Galaxy Nexus için bir ekran Nexus S (hdpi) gibi cihazlarla karşılaştırıldığında aynı sayıda resmi tutmak için daha büyük bir önbellek sunar.
- Hangi boyutlar ve yapılandırmalar bit eşlemlerdir ve dolayısıyla her biri için ne kadar bellek yükseldi?
- Resimlere ne sıklıkta erişilecek? Bazılarına diğerlerine göre daha sık erişilecek mi?
Öyleyse bazı öğeleri her zaman bellekte tutmak, hatta farklı bit eşlem grupları için birden fazla
LruCache
nesnesi kullanmak isteyebilirsiniz. - Nitelik ile niceliği dengeleyebilir misiniz? Bazen, daha büyük bir dosyayı saklamak daha düşük kaliteli bit eşlem sayısı ve potansiyel olarak başka bir arka plan görevi var.
Tüm uygulamalara uygun belirli bir boyut veya formül yoktur. Kullanımınızı analiz edip uygun bir çözüm bulmak size bağlıdır. Çok küçük bir önbellek,
işe yaramaz, çok büyük bir önbellek yine java.lang.OutOfMemory
istisnalarına neden olabilir
ve uygulamanızın geri kalanında çalışmak için
çok az bellek olur.
Aşağıda, bit eşlemler için bir LruCache
ayarlama örneği verilmiştir:
Kotlin
private lateinit var memoryCache: LruCache<String, Bitmap> override fun onCreate(savedInstanceState: Bundle?) { ... // Get max available VM memory, exceeding this amount will throw an // OutOfMemory exception. Stored in kilobytes as LruCache takes an // int in its constructor. val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt() // Use 1/8th of the available memory for this memory cache. val cacheSize = maxMemory / 8 memoryCache = object : LruCache<String, Bitmap>(cacheSize) { override fun sizeOf(key: String, bitmap: Bitmap): Int { // The cache size will be measured in kilobytes rather than // number of items. return bitmap.byteCount / 1024 } } ... }
Java
private LruCache<String, Bitmap> memoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... // Get max available VM memory, exceeding this amount will throw an // OutOfMemory exception. Stored in kilobytes as LruCache takes an // int in its constructor. final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // Use 1/8th of the available memory for this memory cache. final int cacheSize = maxMemory / 8; memoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in kilobytes rather than // number of items. return bitmap.getByteCount() / 1024; } }; ... } public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { memoryCache.put(key, bitmap); } } public Bitmap getBitmapFromMemCache(String key) { return memoryCache.get(key); }
Not: Bu örnekte uygulama belleğinin sekizde biri
ayırdığımızı göreceksiniz. Normal/hdpi cihazlarda bu boyut yaklaşık 4 MB'tır (32/8). 800x480 çözünürlüğe sahip bir cihazda resimlerle dolu tam ekran GridView
yaklaşık 1,5 MB (800*480*4 bayt) kullanır. Bu nedenle, en az 2,5 sayfalık resim bellekte önbelleğe alınır.
ImageView
öğesine bit eşlem yüklerken LruCache
olup olmadığına bakın. Bir giriş bulunursa ImageView
öğesini güncellemek için hemen kullanılır. Aksi takdirde, resmin işlenmesi için bir arka plan ileti dizisi oluşturulur:
Kotlin
fun loadBitmap(resId: Int, imageView: ImageView) { val imageKey: String = resId.toString() val bitmap: Bitmap? = getBitmapFromMemCache(imageKey)?.also { mImageView.setImageBitmap(it) } ?: run { mImageView.setImageResource(R.drawable.image_placeholder) val task = BitmapWorkerTask() task.execute(resId) null } }
Java
public void loadBitmap(int resId, ImageView imageView) { final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { mImageView.setImageBitmap(bitmap); } else { mImageView.setImageResource(R.drawable.image_placeholder); BitmapWorkerTask task = new BitmapWorkerTask(mImageView); task.execute(resId); } }
BitmapWorkerTask
ayrıca
bellek önbelleğine giriş eklemek üzere güncellendi:
Kotlin
private inner class BitmapWorkerTask : AsyncTask<Int, Unit, Bitmap>() { ... // Decode image in background. override fun doInBackground(vararg params: Int?): Bitmap? { return params[0]?.let { imageId -> decodeSampledBitmapFromResource(resources, imageId, 100, 100)?.also { bitmap -> addBitmapToMemoryCache(imageId.toString(), bitmap) } } } ... }
Java
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { ... // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); return bitmap; } ... }
Disk önbelleği kullanma
Bellek önbelleği, en son görüntülenen bitmap'lere erişimi hızlandırmak için kullanışlıdır ancak resimlerin bu önbellekte bulunacağına güvenemezsiniz. Şunları içeren GridView
gibi bileşenler:
büyük veri kümeleri, önbelleği kolayca
doldurabilir. Başvurunuz başka bir nedeniyle kesintiye uğrayabilir
yapmalarına yardımcı olabilir. Arka planda çalışırken kapanabilir ve önbelleğin
yok. Kullanıcı devam ettiğinde uygulamanızın her resmi tekrar işlemesi gerekir.
Bu durumlarda, işlenen bit eşlemleri korumak ve yüklemeyi azaltmaya yardımcı olmak için disk önbelleği kullanılabilir. görüntülerin artık bellek önbelleğinde kullanılamadığı zamanlar. Elbette, diskten resim getirme işlemi bellekten yüklemekten daha yavaştır ve disk okuma süreleri tahmin edilemez olabileceğinden arka plan iş parçacığında yapılmalıdır.
Not: ContentProvider
, sizin için daha uygun olabilir
daha sık erişilirse önbelleğe alınmış resimlerin depolanması için uygun bir yere
resim galerisi uygulamasıdır.
Bu sınıfın örnek kodunda, Android kaynağından alınan bir DiskLruCache
uygulaması kullanılmaktadır.
Mevcut bellek önbelleğine ek olarak bir disk önbelleği ekleyen güncellenmiş örnek kodu burada bulabilirsiniz:
Kotlin
private const val DISK_CACHE_SIZE = 1024 * 1024 * 10 // 10MB private const val DISK_CACHE_SUBDIR = "thumbnails" ... private var diskLruCache: DiskLruCache? = null private val diskCacheLock = ReentrantLock() private val diskCacheLockCondition: Condition = diskCacheLock.newCondition() private var diskCacheStarting = true override fun onCreate(savedInstanceState: Bundle?) { ... // Initialize memory cache ... // Initialize disk cache on background thread val cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR) InitDiskCacheTask().execute(cacheDir) ... } internal inner class InitDiskCacheTask : AsyncTask<File, Void, Void>() { override fun doInBackground(vararg params: File): Void? { diskCacheLock.withLock { val cacheDir = params[0] diskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE) diskCacheStarting = false // Finished initialization diskCacheLockCondition.signalAll() // Wake any waiting threads } return null } } internal inner class BitmapWorkerTask : AsyncTask<Int, Unit, Bitmap>() { ... // Decode image in background. override fun doInBackground(vararg params: Int?): Bitmap? { val imageKey = params[0].toString() // Check disk cache in background thread return getBitmapFromDiskCache(imageKey) ?: // Not found in disk cache decodeSampledBitmapFromResource(resources, params[0], 100, 100) ?.also { // Add final bitmap to caches addBitmapToCache(imageKey, it) } } } fun addBitmapToCache(key: String, bitmap: Bitmap) { // Add to memory cache as before if (getBitmapFromMemCache(key) == null) { memoryCache.put(key, bitmap) } // Also add to disk cache synchronized(diskCacheLock) { diskLruCache?.apply { if (!containsKey(key)) { put(key, bitmap) } } } } fun getBitmapFromDiskCache(key: String): Bitmap? = diskCacheLock.withLock { // Wait while disk cache is started from background thread while (diskCacheStarting) { try { diskCacheLockCondition.await() } catch (e: InterruptedException) { } } return diskLruCache?.get(key) } // Creates a unique subdirectory of the designated app cache directory. Tries to use external // but if not mounted, falls back on internal storage. fun getDiskCacheDir(context: Context, uniqueName: String): File { // Check if media is mounted or storage is built-in, if so, try and use external cache dir // otherwise use internal cache dir val cachePath = if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState() || !isExternalStorageRemovable()) { context.externalCacheDir.path } else { context.cacheDir.path } return File(cachePath + File.separator + uniqueName) }
Java
private DiskLruCache diskLruCache; private final Object diskCacheLock = new Object(); private boolean diskCacheStarting = true; private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB private static final String DISK_CACHE_SUBDIR = "thumbnails"; @Override protected void onCreate(Bundle savedInstanceState) { ... // Initialize memory cache ... // Initialize disk cache on background thread File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR); new InitDiskCacheTask().execute(cacheDir); ... } class InitDiskCacheTask extends AsyncTask<File, Void, Void> { @Override protected Void doInBackground(File... params) { synchronized (diskCacheLock) { File cacheDir = params[0]; diskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE); diskCacheStarting = false; // Finished initialization diskCacheLock.notifyAll(); // Wake any waiting threads } return null; } } class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { ... // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { final String imageKey = String.valueOf(params[0]); // Check disk cache in background thread Bitmap bitmap = getBitmapFromDiskCache(imageKey); if (bitmap == null) { // Not found in disk cache // Process as normal final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); } // Add final bitmap to caches addBitmapToCache(imageKey, bitmap); return bitmap; } ... } public void addBitmapToCache(String key, Bitmap bitmap) { // Add to memory cache as before if (getBitmapFromMemCache(key) == null) { memoryCache.put(key, bitmap); } // Also add to disk cache synchronized (diskCacheLock) { if (diskLruCache != null && diskLruCache.get(key) == null) { diskLruCache.put(key, bitmap); } } } public Bitmap getBitmapFromDiskCache(String key) { synchronized (diskCacheLock) { // Wait while disk cache is started from background thread while (diskCacheStarting) { try { diskCacheLock.wait(); } catch (InterruptedException e) {} } if (diskLruCache != null) { return diskLruCache.get(key); } } return null; } // Creates a unique subdirectory of the designated app cache directory. Tries to use external // but if not mounted, falls back on internal storage. public static File getDiskCacheDir(Context context, String uniqueName) { // Check if media is mounted or storage is built-in, if so, try and use external cache dir // otherwise use internal cache dir final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() : context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName); }
Not: Disk önbelleğini başlatmak için bile disk işlemleri gerekir. Bu nedenle, ana ileti dizisinde yer almamalıdır. Ancak bu, bazı durumlarda önbelleğe alma işlemi başlatmadan önce yapılır. Bu sorunu gidermek için, yukarıdaki uygulamada nesne, önbellek tamamlanana kadar uygulamanın disk önbelleğinden okuma yapmamasını sağlar. başlatıldı.
Bellek önbelleği, kullanıcı arayüzü iş parçacığında kontrol edilirken disk önbelleği arka planda kontrol edilir ileti dizisi. Disk işlemleri, kullanıcı arayüzü iş parçacığı üzerinde hiçbir zaman gerçekleşmemelidir. Resim işleme, tamamlandığında son bit eşlem, ileride kullanılmak üzere hem belleğe hem de disk önbelleğine eklenir.
Yapılandırma Değişikliklerini İşleme
Ekran yönü değişikliği gibi çalışma zamanı yapılandırma değişiklikleri, Android'in çalışan etkinliği yeni yapılandırmayla yeniden başlatın (Bu davranış hakkında daha fazla bilgi için Çalışma Zamanı Değişikliklerini İşleme bölümüne bakın). Kullanıcıların sorunsuz ve hızlı bir deneyim yaşaması için tüm resimlerinizi tekrar işlemek zorunda kalmamak istiyorsunuz. bir deneyim anlamına gelir.
Neyse ki Bellek Önbelleği Kullanma bölümünde oluşturduğunuz güzel bir bitmap bellek önbelleğiniz var. Bu önbellek, setRetainInstance(true)
çağrılarak korunan bir Fragment
kullanılarak yeni etkinlik örneğine iletilebilir. Etkinlik oluşturulduktan sonra
yeniden oluşturulursa, saklanan bu Fragment
tekrar eklenir ve siz de
mevcut önbellek nesnesi bulunur. Bu nesne, resimlerin hızlı bir şekilde getirilmesini ve ImageView
nesnelerine yeniden doldurulmasını sağlar.
Yapılandırma genelinde LruCache
nesnesinin tutulmasıyla ilgili bir örneği burada bulabilirsiniz.
Fragment
kullanılarak yapılan değişiklikler:
Kotlin
private const val TAG = "RetainFragment" ... private lateinit var mMemoryCache: LruCache<String, Bitmap> override fun onCreate(savedInstanceState: Bundle?) { ... val retainFragment = RetainFragment.findOrCreateRetainFragment(supportFragmentManager) mMemoryCache = retainFragment.retainedCache ?: run { LruCache<String, Bitmap>(cacheSize).also { memoryCache -> ... // Initialize cache here as usual retainFragment.retainedCache = memoryCache } } ... } class RetainFragment : Fragment() { var retainedCache: LruCache<String, Bitmap>? = null companion object { fun findOrCreateRetainFragment(fm: FragmentManager): RetainFragment { return (fm.findFragmentByTag(TAG) as? RetainFragment) ?: run { RetainFragment().also { fm.beginTransaction().add(it, TAG).commit() } } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) retainInstance = true } }
Java
private LruCache<String, Bitmap> memoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... RetainFragment retainFragment = RetainFragment.findOrCreateRetainFragment(getFragmentManager()); memoryCache = retainFragment.retainedCache; if (memoryCache == null) { memoryCache = new LruCache<String, Bitmap>(cacheSize) { ... // Initialize cache here as usual } retainFragment.retainedCache = memoryCache; } ... } class RetainFragment extends Fragment { private static final String TAG = "RetainFragment"; public LruCache<String, Bitmap> retainedCache; public RetainFragment() {} public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); if (fragment == null) { fragment = new RetainFragment(); fm.beginTransaction().add(fragment, TAG).commit(); } return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } }
Bunu test etmek için Fragment
cihazını tutmadan veya tutmadan cihazı döndürmeyi deneyin. Resimler neredeyse etkinliği doldurduğundan gecikmenin çok düşük olduğunu veya hiç olmadığını fark edeceksiniz.
anında bellekten otomatik olarak alırsınız. Bellek önbelleğinde bulunmayan resimler,
yoksa disk önbelleğinde bulunmayıp her zamanki gibi işlenir.