標籤:
android應用對圖片處理算是比較頻繁的了,尤其是在程式載入大量圖片和高解析度圖片時,最容易產生oom異常,下面是個人平時一些省記憶體載入方法
方法一:
public Bitmap decodeFile(String filePath) { Bitmap bitmap = null; BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; try { BitmapFactory.Options.class.getField("inNativeAlloc").setBoolean( options, true); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } if (mFilePath != null) { bitmap = BitmapFactory.decodeFile(mFilePath, options); } return bitmap; }
方法二:
public Bitmap ReadBitMap(Context context, int resId){ BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.RGB_565; opt.inPurgeable = true; opt.inInputShareable = true; //擷取資源圖片 InputStream is = context.getResources().openRawResource(resId); return BitmapFactory.decodeStream(is,null,opt); }
如果你的控制項大小小於原始圖片大小,那麼就需要對圖片進行壓縮處理,來減少記憶體使用量。
現在知道了原圖片的尺寸,根據實際情況決定你要載入它縮小多少倍後的圖片。例如你用一個128x96的ImageView顯示一張1024x768的原圖,根本沒有必要把原圖讀載入到記憶體。
載入一張縮小後的圖片到記憶體,只需要把BitmapFactory.Options對象的inSampleSize設為true,
然後給inSampleSize設一個值就行了(可以理解inSampleSize為n,圖片就縮小到1/n大小)。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { if (width > height) { inSampleSize = Math.round((float)height / (float)reqHeight); } else { inSampleSize = Math.round((float)width / (float)reqWidth); } } return inSampleSize; }
方法三:使用記憶體緩衝
對於緩衝,沒有大小或者規則適用於所有應用,它依賴於你分析自己應用的記憶體使用量確定自己的方案。 緩衝太小可能只會增加額外的記憶體使用量,緩衝太大可能會導致記憶體溢出或者應用其它模組可使用記憶體太小
private LruCache<String, Bitmap> mMemoryCache;@Overrideprotected 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; mMemoryCache = 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) { mMemoryCache.put(key, bitmap); }}public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key);}
載入壓縮後的圖片到ImageView顯示
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載入圖片後,也要把圖片緩衝到記憶體中:
class BitmapWorkerTask extends AsyncTask { ... // 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; } ... }
方法四:使用磁碟緩衝
你的應用也有可能被其他任務打斷,如電話呼入,應用在後台有可能會被結束,這樣緩衝的資料也會丟失。 當使用者回到應用時,所有的圖片還需要重新擷取一遍。 磁碟緩衝可應用到這種情境中,它可以減少你擷取圖片的次數,當然,從磁碟擷取圖片比從記憶體中擷取要慢的多,所以它需要在非UI線程中完成。 範例程式碼中是磁碟緩衝的一個實現,在Android4.0源碼中(libcore/luni/src/main/java/libcore/io/DiskLruCache.java), 有更加強大和推薦的一個實現,它的向後相容使在發行過的庫中很方便使用它
private DiskLruCache mDiskCache;private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MBprivate static final String DISK_CACHE_SUBDIR = "thumbnails";@Overrideprotected void onCreate(Bundle savedInstanceState) { ... // Initialize memory cache ... File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); ...}class BitmapWorkerTask extends AsyncTask { ... // 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(String.valueOf(imageKey, bitmap); return bitmap; } ...}public void addBitmapToCache(String key, Bitmap bitmap) { // Add to memory cache as before if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } // Also add to disk cache if (!mDiskCache.containsKey(key)) { mDiskCache.put(key, bitmap); }}public Bitmap getBitmapFromDiskCache(String key) { return mDiskCache.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.public static File getCacheDir(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.getExternalStorageState() == Environment.MEDIA_MOUNTED || !Environment.isExternalStorageRemovable() ? context.getExternalCacheDir().getPath() : context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName);}
運行時的配置改變,例如螢幕橫豎屏切換了有好的使用者體驗,你可能不想在這種情況下,重新擷取一遍圖片。
幸好你可以使用上面講的記憶體緩衝。緩衝可以通過使用一個Fragment(調用setRetainInstance(true)被傳到新的Activity,
當新的Activity被建立後,只需要重新附加Fragment,你就可以得到這個Fragment並訪問到存在的緩衝,把裡面的圖片快速的顯示出來
private LruCache mMemoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) { ... RetainFragment mRetainFragment = RetainFragment.findOrCreateRetainFragment(getFragmentManager()); mMemoryCache = RetainFragment.mRetainedCache; if (mMemoryCache == null) { mMemoryCache = new LruCache(cacheSize) { ... // Initialize cache here as usual } mRetainFragment.mRetainedCache = mMemoryCache; } ...}class RetainFragment extends Fragment { private static final String TAG = "RetainFragment"; public LruCache mRetainedCache; public RetainFragment() {} public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); if (fragment == null) { fragment = new RetainFragment(); } return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); }}
關於android 圖片載入最佳化