国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

設計簡單的Android圖片加載框架

2019-12-12 05:12:32
字體:
來源:轉載
供稿:網友

目前Android 發展至今優秀的圖片加載框架太多,例如: Volley ,Picasso,Imageloader,Glide等等。但是作為程序猿,懂得其中的實現原理還是相當重要的,只有懂得才能更好地使用。于是乎,今天我就簡單設計一個網絡加載圖片框架。主要就是熟悉圖片的網絡加載機制。

一般來說,一個優秀的 圖片加載框架(ImageLoader) 應該具備如下功能:

圖片壓縮

內存緩存

磁盤緩存

圖片的同步加載

圖片的異步加載

網絡拉取

那我們就從以上幾個方面進行介紹:

1.圖片壓縮(有效的降低OOM的發生概率)

圖片壓縮功能我在Bitmap 的高效加載中已經做了介紹這里不多說直接上代碼。這里直接抽象一個類用于完成圖片壓縮功能。

public class ImageResizer { private static final String TAG = "ImageResizer"; public ImageResizer() {  super();  // TODO Auto-generated constructor stub } public Bitmap decodeSampledBitmapFromResource(Resources res, int resId,   int reqWidth, int reqHeight) {  final BitmapFactory.Options options = new BitmapFactory.Options();  options.inJustDecodeBounds = true;  BitmapFactory.decodeResource(res, resId, options);  options.inSampleSize = calculateInSampleSize(options, reqWidth,    reqHeight);  options.inJustDecodeBounds = false;  return BitmapFactory.decodeResource(res, resId, options); } public Bitmap decodeSampledBitmapFromBitmapFileDescriptor(FileDescriptor fd,   int reqWidth,int reqHeight){  final BitmapFactory.Options options = new BitmapFactory.Options();  options.inJustDecodeBounds = true;  BitmapFactory.decodeFileDescriptor(fd, null, options);  options.inSampleSize = calculateInSampleSize(options, reqWidth,    reqHeight);  options.inJustDecodeBounds = false;  return BitmapFactory.decodeFileDescriptor(fd, null, options); }
 public int calculateInSampleSize(BitmapFactory.Options options,   int reqWidth, int reqHeight) {  final int width = options.outWidth;  final int height = options.outHeight;  int inSampleSize = 1;  if (height > reqHeight || width > reqWidth) {   final int halfHeight = height / 2;   final int halfWidth = width / 2;   while ((halfHeight / inSampleSize) > reqHeight     && (halfWidth / inSampleSize) > halfWidth) {    inSampleSize *= 2;   }  }  return inSampleSize; }}

2.內存緩存和磁盤緩存

緩存直接選擇 LruCache 和 DiskLruCache 來完成內存緩存和磁盤緩存工作。

首先對其初始化:

private LruCache<String, Bitmap> mMemoryCache;private DiskLruCache mDiskLruCache;public ImageLoader(Context context) {  mContext = context.getApplicationContext();  //分配內存緩存為當前進程的1/8,磁盤緩存容量為50M  int maxMemory = (int) (Runtime.getRuntime().maxMemory() * 1024);  int cacheSize = maxMemory / 8;  mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {   @Override   protected int sizeOf(String key, Bitmap value) {    return value.getRowBytes() * value.getHeight() / 1024;   }  };  File diskCacheDir = getDiskChaheDir(mContext, "bitmap");  if (!diskCacheDir.exists()) {   diskCacheDir.mkdirs();  }  if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {   try {    mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,      DISK_CACHE_SIZE);    mIsDiskLruCacheCreated = true;   } catch (IOException e) {    e.printStackTrace();   }  } }

創建完畢后,接下來則需要提供方法來視線添加以及獲取的功能。首先來看內存緩存。

private void addBitmapToMemoryCache(String key, Bitmap bitmap) {  if (getBitmapFromMemCache(key) == null) {   mMemoryCache.put(key, bitmap);  } } private Bitmap getBitmapFromMemCache(String key) {  return mMemoryCache.get(key); }

相對來說內存緩存比較簡單,而磁盤緩存則復雜的多。磁盤緩存(LruDiskCache)并沒有直接提供方法來實現,而是要通過Editor以及Snapshot 來實現對于文件系統的添加以及讀取的操作。

首先看一下,Editor,它提供了commit 和 abort 方法來提交和撤銷對文件系統的寫操作。

//將下載的圖片寫入文件系統,實現磁盤緩存 private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)   throws IOException {  if (Looper.myLooper() == Looper.getMainLooper()) {   throw new RuntimeException("can not visit network from UI Thread.");  }  if (mDiskLruCache == null)   return null;  String key = hashKeyFormUrl(url);  DiskLruCache.Editor editor = mDiskLruCache.edit(key);  if (editor != null) {   OutputStream outputStream = editor     .newOutputStream(DISK_CACHE_INDEX);   if (downloadUrlToStream(url, outputStream)) {    editor.commit();   } else {    editor.abort();   }  }  mDiskLruCache.flush();  return loadBitmapForDiskCache(url, reqWidth, reqHeight); }

Snapshot, 通過它可以獲取磁盤緩存對象對應的 FileInputStream,但是FileInputStream 無法便捷的進行壓縮,所以通過FileDescriptor 來加載壓縮后的圖片,最后將加載后的bitmap添加到內存緩存中。

public Bitmap loadBitmapForDiskCache(String url, int reqWidth, int reqHeight)   throws IOException {  if (Looper.myLooper() == Looper.getMainLooper()) {   Log.w(TAG, "load bitmap from UI Thread , it's not recommended");  }  if (mDiskLruCache == null)   return null;  Bitmap bitmap = null;  String key = hashKeyFormUrl(url);  DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);  if (snapshot != null) {   FileInputStream fileInputStream = (FileInputStream) snapshot     .getInputStream(DISK_CACHE_INDEX);   FileDescriptor fileDescriptor = fileInputStream.getFD();   bitmap = mImageResizer.decodeSampledBitmapFromBitmapFileDescriptor(     fileDescriptor, reqWidth, reqHeight);   if (bitmap != null) {    addBitmapToMemoryCache(key, bitmap);   }  }  return bitmap; }

3.同步加載

同步加載的方法需要外部在子線程中調用。

//同步加載 public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {  Bitmap bitmap = loadBitmpaFromMemCache(uri);  if (bitmap != null) {   return bitmap;  }  try {   bitmap = loadBitmapForDiskCache(uri, reqWidth, reqHeight);   if (bitmap != null) {    return bitmap;   }   bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);  } catch (IOException e) {   e.printStackTrace();  }  if (bitmap == null && !mIsDiskLruCacheCreated) {   bitmap = downloadBitmapFromUrl(uri);  }  return bitmap; }

從方法中可以看出工作過程遵循如下幾步:

首先嘗試從內存緩存中讀取圖片,接著嘗試從磁盤緩存中讀取圖片,最后才會從網絡中拉取。此方法不能再主線程中執行,執行環境的檢測是在loadBitmapFromHttp中實現的。

if (Looper.myLooper() == Looper.getMainLooper()) {   throw new RuntimeException("can not visit network from UI Thread.");  }

4.異步加載

//異步加載 public void bindBitmap(final String uri, final ImageView imageView,   final int reqWidth, final int reqHeight) {  imageView.setTag(TAG_KEY_URI, uri);  Bitmap bitmap = loadBitmpaFromMemCache(uri);  if (bitmap != null) {   imageView.setImageBitmap(bitmap);   return;  }  Runnable loadBitmapTask = new Runnable() {   @Override   public void run() {    Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);    if (bitmap != null) {     LoaderResult result = new LoaderResult(imageView, uri,       bitmap);     mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result)       .sendToTarget();    }   }  };  THREAD_POOL_EXECUTOR.execute(loadBitmapTask); }

從bindBitmap的實現來看,bindBitmap 方法會嘗試從內存緩存中讀取圖片,如果讀取成功就直接返回結果,否則會在線程池中去調用loadBitmap方法,當圖片加載成功后再將圖片、圖片的地址以及需要綁定的imageView封裝成一個LoaderResult對象,然后再通過mMainHandler向主線程發送一個消息,這樣就可以在主線程中給imageView設置圖片了。

下面來看一下,bindBitmap這個方法中用到的線程池和Handler,首先看一下線程池 THREAD_POOL_EXECUTOR 的實現。

private static final int CPU_COUNT = Runtime.getRuntime()   .availableProcessors();private static final int CORE_POOL_SIZE = CPU_COUNT + 1;private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;private static final long KEEP_ALIVE = 10L;private static final ThreadFactory sThreadFactory = new ThreadFactory() {  private final AtomicInteger mCount = new AtomicInteger();  @Override  public Thread newThread(Runnable r) {   // TODO Auto-generated method stub   return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());  } };public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(   CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,   new LinkedBlockingDeque<Runnable>(), sThreadFactory);

1.使用線程池和handler的原因。

首先不能用普通線程去實現,如果采用普通線程去加載圖片,隨著列表的滑動可能會產生大量的線程,這樣不利于效率的提升。 Handler 的實現 ,直接采用了 主線程的Looper來構造Handler 對象,這就使得 ImageLoader 可以在非主線程構造。另外為了解決由于View復用所導致的列表錯位這一問題再給ImageView 設置圖片之前會檢查他的url有沒有發生改變,如果發生改變就不再給它設置圖片,這樣就解決了列表錯位問題。

private Handler mMainHandler = new Handler(Looper.getMainLooper()) {  @Override  public void handleMessage(Message msg) {   LoaderResult result = (LoaderResult) msg.obj;   ImageView imageView = result.imageView;   imageView.setImageBitmap(result.bitmap);   String uri = (String) imageView.getTag(TAG_KEY_URI);   if (uri.equals(result.uri)) {    imageView.setImageBitmap(result.bitmap);   } else {    Log.w(TAG, "set image bitmap,but url has changed , ignored!");   }  } };

總結:

圖片加載的問題 ,尤其是大量圖片的加載,對于android 開發者來說一直是比較困擾的問題。本文只是提到了最基礎的一種解決方法,用于學習還是不錯的。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 安泽县| 宜良县| 皮山县| 镇江市| 西华县| 瓦房店市| 尼玛县| 阿拉善盟| 张家港市| 卓资县| 峨眉山市| 库伦旗| 靖远县| 呼图壁县| 崇明县| 沾化县| 长春市| 台江县| 天祝| 华宁县| 彭州市| 阿勒泰市| 德化县| 社旗县| 西宁市| 水城县| 新乡市| 奎屯市| 上高县| 南澳县| 万荣县| 古浪县| 炎陵县| 垣曲县| 清新县| 建湖县| 濮阳市| 红河县| 建昌县| 仙游县| 阿巴嘎旗|