Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android異步加載全解析之引入一級緩存

Android異步加載全解析之引入一級緩存

編輯:關於Android編程

Android異步加載全解析之引入緩存


為啥要緩存

通過對圖像的縮放,我們做到了對大圖的異步加載優化,但是現在的App不僅是高清大圖,更是高清多圖,動不動就是圖文混排,以圖代文,如果這些圖片都加載到內存中,必定會OOM。因此,在用戶浏覽完圖像後,應當立即將這些廢棄的圖像回收,但是,這又帶來了另一個問題,也就是當用戶在浏覽完一次圖片後,如果還要返回去再進行重新浏覽,那麼這些回收掉的圖像又要重新進行加載,保不准就要那些無聊到蛋疼的人在那一邊看你回收GC,一邊看你重新加載。這兩件事情,肯定是互相矛盾的,也是影響性能的一個很重要的原因。

內存緩存

針對這樣一個非常需要找到一個彼此平衡點的問題,Google提供了一套內存緩存技術。內存緩存技術對那些大量占用應用程序寶貴內存的圖片提供了快速訪問的方法。其中最核心的類是LruCache 。這個類非常適合用來緩存圖片,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,並且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除。LruCache 是在support-v4中才引入的,在引入LruCache 之前,Google建議的是使用軟引用或弱引用 (SoftReference or WeakReference)來進行內存緩存。但是從Android 2.3開始,GC算法修改,軟引用與弱引用同樣會優先被GC回收,所以這種方法也就沒有太高的使用價值了,現在網上很多還在繼續使用SoftReference 和WeakReference的文章,大多都是過時的文章,建議大家跟上黨的步伐,與時俱進。

LruCache使用

內存緩存LruCache所使用的內存緩存大小是由開發者決定的,開發者需要根據圖像的使用率、分辨率、訪問頻率、設備性能等很多因素進行考慮。這個平衡點經常需要很多經驗和測試來決定。使用LruCache非常簡單:
private LruCache mMemoryCaches;

// 獲取應用內存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
// 分配cache
int cacheSize = maxMemory / 10;
mMemoryCaches = new LruCache(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };

// 從LruCache獲取中獲取緩存對象
public Bitmap getBitmapFromMemoryCaches(String url) {
    return mMemoryCaches.get(url);
}

// 增加緩存對象到LruCache
public void addBitmapToMemoryCaches(String url,Bitmap bitmap) {
    if (getBitmapFromMemoryCaches(url) == null) {
        mMemoryCaches.put(url, bitmap);
    }
}

首先,我們需要聲明LruCache,接著,通過LruCache的構造方法創建緩存對象,並為其分配cacheSize,這個cacheSize通常我們需要通過Runtime來獲取,獲取當前系統分給App的可用內存,並將這些內存的一部分用做LruCache緩存。LruCache中必須重寫sizeOf方法,通過這個方法,LruCache可以獲取每個緩存對象的大小,子類必須重寫,因為默認的LruCache獲取的是緩存的個數。。。尼瑪。 最後,我們提供兩個方法getBitmapFromMemoryCaches和addBitmapToMemoryCaches分別用來獲取和增加內存緩存到LruCache。 等等,我們好像還沒寫釋放內存的方法,對,不用你寫了,Lru算法可以保證cacheSize不會OOM,一旦超過這個大小,GC就會回收時間最長的對象,釋放空間。

為異步處理加入一級緩存

OK,在了解了關於緩存的基礎信息後,我們回到現在這個例子,想想怎麼利用緩存來進行異步處理的優化。首先,ListView、GridView這些嬌生慣養的玩意兒,碰不得摔不得,更不能在它滾的開心的時候,你還在後面拼命玩加載。所以,第一個重點,滾的時候就讓它開心的滾,滾完了再開始加載。

滾完再加載

要實現這一點,我們可以通過給Adapter增加AbsListView.OnScrollListener接口來實現。 當然,還有一點需要注意,第一次初始化的時候,一定要手動來加載圖片,不然系統判斷你沒滾,只能調用onScroll方法,不會調用onScrollStateChanged方法。而且我們也需要在onScroll方法中來不斷獲取可見的Item。特別要注意的是visibleItemCount,只要大於0的時候,才認為是開始顯示圖片了。
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (scrollState == SCROLL_STATE_IDLE) {
        mImageLoader.loadImages(mStart, mEnd);
    } else {
        mImageLoader.cancelAllTasks();
    }
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    mStart = firstVisibleItem;
    mEnd = firstVisibleItem + visibleItemCount;
    if (mFirstFlag  && visibleItemCount > 0) {
        mImageLoader.loadImages(mStart, mEnd);
        mFirstFlag = false;
    }
}

加載顯示的項目

加載數據的時候,獲取第一個能顯示的Item和最後一個可見的Item,只加載這一部分。所以我們創建一個方法——loadImages(int start, int end)。這個方法用來加載從start到end之間的Item數據。 加載的時候,先從內存緩存中去取,如果有,那說明最近已經加載過了,那直接加載就好了,如果沒有取到,那就開啟synctask去下載。
public void loadImages(int start, int end) {
    for (int i = start; i < end; i++) {
        String url = Images.IMAGE_URLS[i];
        Bitmap bitmap = getBitmapFromMemoryCaches(url);
        if (bitmap == null) {
            ASyncDownloadImage task = new ASyncDownloadImage(url);
            mTasks.add(task);
            task.execute(url);
        } else {
            ImageView imageView = (ImageView) mListView.findViewWithTag(url);
            imageView.setImageBitmap(bitmap);
        }
    }
}

這裡我們在設置圖片的時候,直接通過findViewWithTag,通過url來找到相應的Imageview,這裡與之前不同是因為我們這裡是按照start到end來進行加載,直接從ListView對象中獲取對應的Imageview比較簡單。

下載與Asynctask

下載依然是使用老方法:
private static Bitmap getBitmapFromUrl(String urlString) {
    Bitmap bitmap;
    InputStream is = null;
    try {
        URL url = new URL(urlString);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        is = new BufferedInputStream(conn.getInputStream());
        bitmap = BitmapFactory.decodeStream(is);
        conn.disconnect();
        return bitmap;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (is != null)
                is.close();
        } catch (IOException e) {
        }
    }
    return null;
}

Asynctask也與之前基本類似:
class ASyncDownloadImage extends AsyncTask {

    private String url;

    public ASyncDownloadImage(String url) {
        this.url = url;
    }

    @Override
    protected Bitmap doInBackground(String... params) {
        url = params[0];
        Bitmap bitmap = getBitmapFromUrl(url);
        if (bitmap != null) {
            addBitmapToMemoryCaches(url, bitmap);
        }
        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        ImageView imageView = (ImageView) mListView.findViewWithTag(url);
        if (imageView != null && bitmap != null) {
            imageView.setImageBitmap(bitmap);
        }
        mTasks.remove(this);
    }
}

唯一不同的是,我們在下載好圖像之後,會將圖像加載到Lrucache。

組裝

OK,萬事具備,准備刷代碼。在刷之前,我們先來重新整理下思路,首先,在Adapter中,一加載ListView,就開始下載顯示范圍內的Item的圖像,這時候緩存中當然沒有,所以都去下載了,下完了就顯示在Item中,並緩存起來,如果還沒下完,你就迫不及待的滾起來了,那麼立即取消所有task,讓ListView歡快的滾,滾完之後,繼續加載。 OK,該講的都講了,下面我們開始刷代碼了,一切盡在不言中,只有代碼最懂你。
package com.imooc.listviewacyncloader;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.LruCache;
import android.widget.ImageView;
import android.widget.ListView;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

public class ImageLoaderWithCaches {

    private Set mTasks;
    private LruCache mMemoryCaches;
    private ListView mListView;

    public ImageLoaderWithCaches(ListView listview) {
        this.mListView = listview;
        mTasks = new HashSet<>();
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 10;
        mMemoryCaches = new LruCache(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }

    public void showImage(String url, ImageView imageView) {
        Bitmap bitmap = getBitmapFromMemoryCaches(url);
        if (bitmap == null) {
            imageView.setImageResource(R.drawable.ic_launcher);
        } else {
            imageView.setImageBitmap(bitmap);
        }
    }

    public Bitmap getBitmapFromMemoryCaches(String url) {
        return mMemoryCaches.get(url);
    }

    public void addBitmapToMemoryCaches(String url,Bitmap bitmap) {
        if (getBitmapFromMemoryCaches(url) == null) {
            mMemoryCaches.put(url, bitmap);
        }
    }

    public void loadImages(int start, int end) {
        for (int i = start; i < end; i++) {
            String url = Images.IMAGE_URLS[i];
            Bitmap bitmap = getBitmapFromMemoryCaches(url);
            if (bitmap == null) {
                ASyncDownloadImage task = new ASyncDownloadImage(url);
                mTasks.add(task);
                task.execute(url);
            } else {
                ImageView imageView = (ImageView) mListView.findViewWithTag(url);
                imageView.setImageBitmap(bitmap);
            }
        }
    }

    private static Bitmap getBitmapFromUrl(String urlString) {
        Bitmap bitmap;
        InputStream is = null;
        try {
            URL url = new URL(urlString);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(conn.getInputStream());
            bitmap = BitmapFactory.decodeStream(is);
            conn.disconnect();
            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
            }
        }
        return null;
    }

    public void cancelAllTasks() {
        if (mTasks != null) {
            for (ASyncDownloadImage task : mTasks) {
                task.cancel(false);
            }
        }
    }

    class ASyncDownloadImage extends AsyncTask {

        private String url;

        public ASyncDownloadImage(String url) {
            this.url = url;
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            url = params[0];
            Bitmap bitmap = getBitmapFromUrl(url);
            if (bitmap != null) {
                addBitmapToMemoryCaches(url, bitmap);
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            ImageView imageView = (ImageView) mListView.findViewWithTag(url);
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
            }
            mTasks.remove(this);
        }
    }
}

下面是Adapter的代碼:
package com.imooc.listviewacyncloader;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;

import java.util.List;

public class MyAdapterUseCaches extends BaseAdapter implements
        AbsListView.OnScrollListener {

    private LayoutInflater mInflater;
    private List mData;
    private ImageLoaderWithCaches mImageLoader;
    private int mStart = 0, mEnd = 0;
    private boolean mFirstFlag;

    public MyAdapterUseCaches(Context context, List data, ListView listView) {
        this.mData = data;
        mInflater = LayoutInflater.from(context);
        mImageLoader = new ImageLoaderWithCaches(listView);
        mImageLoader.loadImages(mStart, mEnd);
        mFirstFlag = true;
        listView.setOnScrollListener(this);
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int position) {
        return mData.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        String url = mData.get(position);
        ViewHolder viewHolder = null;
        if (convertView == null) {
            viewHolder = new ViewHolder();
            convertView = mInflater.inflate(R.layout.listview_item, null);
            viewHolder.imageView =
                    (ImageView) convertView.findViewById(R.id.iv_lv_item);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.imageView.setTag(url);
        viewHolder.imageView.setImageResource(R.drawable.ic_launcher);
        mImageLoader.showImage(url, viewHolder.imageView);
        return convertView;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE) {
            mImageLoader.loadImages(mStart, mEnd);
        } else {
            mImageLoader.cancelAllTasks();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mStart = firstVisibleItem;
        mEnd = firstVisibleItem + visibleItemCount;
        if (mFirstFlag  && visibleItemCount > 0) {
            mImageLoader.loadImages(mStart, mEnd);
            mFirstFlag = false;
        }
    }

    public class ViewHolder {
        public ImageView imageView;
    }
}

是不是非常簡單,現在引入緩存了,下載過的圖片會暫時保存在內存中,媽媽再也不用擔心你OOM啦。 我們下拉試試,下載完的圖片再次出現也可以馬上加載了,除非滑動太多導致GC。
/

可以就看見,我們的這次利用緩存進行加載有這樣幾個特點: 1、初始化的時候加載 2、滑動的時候才加載 3、加載的內容暫存緩存中 4、只加載顯示的區域

 

 


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved