Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 緩存淺談(一)

Android 緩存淺談(一)

編輯:關於Android編程

Android開發本質上就是手機和web服務器之間進行通信,從服務端需要獲取數據,但是當訪問的數據比較大,比較多,並且是重復數據時,會極大影響性能,甚至應用崩潰,手機卡死,這時候就要考慮緩存機制了!Android中可通過緩存來減少頻繁的網絡操作,減少流量、提升性能。

在實際開發中,緩存機制使用最頻繁的便是圖片緩存!目前大部分的App都是圖文結合,從web服務器獲取文字和圖片,文字顯示很快,圖片基本上是先下載到手機本地,然後再顯示,如果圖片很多、很大,每次加載同一張圖片,都去網絡下載,那麼App渲染的速度是比較慢的,這樣的體驗很差!所以,類似這樣的場景,便要使用緩存機制!

目前緩存機制使用大致流程是,當App需要加載某一張圖片時,先去手機內存中去找該圖片,如果有,那麼直接顯示,如果無,則去手機sd卡或者手機外部存儲中找該圖片,如果有,那麼直接顯示,如果無,那麼此時才去網絡下載該圖片。這種機制常稱為三級緩存策略。

Android的緩存機制是基於Java的緩存機制。Java的緩存機制有四種,強引用、軟引用、弱引用和虛引用。著重看看軟引用(SoftReference)和弱引用(WeakReference)。

1. 軟引用(SoftReference)。

如果一個對象具有軟引用,內存空間足夠,垃 圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高 速緩存。使用軟引用能防止內存洩露,增強程序的健壯性。

2.弱引用(WeakReference)。

如果一個對象只具有弱引用,那麼在垃圾回收器線程掃描的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。弱引用也可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

兩者的區別

弱引用與軟引用的根本區別在於:只具有弱引用的對象擁有更短暫的生命周期,可能隨時被回收。而只具有軟引用的對象只有當內存不夠的時候才被回收,在內存足夠的時候,通常不被回收。

以上的描述是從Java方面給出的,但是在2.3版本後,Google不建議使用這兩種緩存機制!那麼看看Google官網具體是如何描述的,

原文,

	Note: In the past, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, 
	however this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive 
	with collecting soft/weak references which makes them fairly ineffective. In addition, prior to Android 3.0 (API Level 11), 
	the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing 
	an application to briefly exceed its memory limits and crash.
翻譯,

 

	Note: 在過去,一種比較流行的內存緩存實現方法是使用軟引用(SoftReference)或弱引用(WeakReference)對Bitmap進行緩存,
	然而我們並不推薦這樣的做法。從Android 2.3 (API Level 9)開始,垃圾回收機制變得更加頻繁,這使得釋放軟(弱)引用的頻率也
	隨之增高,導致使用引用的效率降低很多。而且在Android 3.0 (API Level 11)之前,備份的Bitmap會存放在Native Memory中,
	它不是以可預知的方式被釋放的,這樣可能導致程序超出它的內存限制而崩潰。

 

Google 建議我們使用強引用(strong referenced)。

原文,

	A memory cache offers fast access to bitmaps at the cost of taking up valuable application memory. 
	The LruCache class (also available in the Support Library for use back to API Level 4) is particularly 
	well suited to the task of caching bitmaps, keeping recently referenced objects in a strong referenced 
	LinkedHashMap and evicting the least recently used member before the cache exceeds its designated size.
翻譯,
	內存緩存以花費寶貴的程序內存為前提來快速訪問位圖。LruCache類(在API Level 4的Support Library中也可以找到)
	特別適合用來緩存Bitmaps,它使用一個強引用(strong referenced)的LinkedHashMap保存最近引用的對象,
	並且在緩存超出設置大小的時候剔除(evict)最近最少使用到的對象。

並且在API level 12中已經引入了LruCache類。接下來,就看看LruCache的介紹以及使用。LruCache便是上面提到的內存緩存策略。

一、LruCache介紹。

1. LRU。

LRU是Least Recently Used 近期最少使用算法。內存管理的一種頁面置換算法,對於在內存中但又不用的數據塊(內存塊)叫做LRU,操作系統會根據哪些數據屬於LRU而將其移出內存而騰出空間來加載另外的數據。什麼是LRU算法? LRU即最近最少使用,常用於頁面置換算法,是為虛擬頁式存儲管理服務的。

2.LruCach。

LruCache這個類在android.util包下,是API level 12引入的,對於API level 12之前的系統可以使用support library中的LruCache。這個類非常適合用來緩存圖片,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,並且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除。

二、使用。

要實現LruCache緩存策略的步驟有:

(1).要先設置緩存圖片的內存大小,基本上設置為手機內存的1/8,
手機內存的獲取方式:int MAXMEMONRY = (int) (Runtime.getRuntime() .maxMemory() / 1024);
(2).LruCache裡面的鍵值對分別是URL和對應的圖片;
(3).重寫了一個叫做sizeOf的方法,返回的是圖片數量。

下面通過一個實例看看如何使用,實現一個ListView中包含圖片,具體的代碼實例,

(1). 新建布局adapter_item_layout.xml,該布局文件是ListView的item的布局,只有一個ImageView控件,顯示圖片。

 



    

(2). 新建一個ImageAdapter,繼承自BaseAdapter,具體代碼如下,

 

 

/**
 * 圖片適配器
 */
public class ImageAdapter extends BaseAdapter implements AbsListView.OnScrollListener {

    private Context mContext;
    private ImageLoader mImageLoader;//圖片處理類,包含圖片緩存 下載等
    private int mStart;// 第一張可見圖片的下標 
    private int mEnd;// 最後一張可見圖片的下標 
    public static String[] URLS;//圖片下載路徑集合
    private boolean mFirstIn;//記錄是否剛打開程序

    public ImageAdapter(Context context, String[] data, ListView listView) {
        URLS = data;
        mContext = context;
        mImageLoader = new ImageLoader(listView);
        mFirstIn = true;
        listView.setOnScrollListener(this);
    }

    @Override
    public int getCount() {
        return URLS.length;
    }

    @Override
    public Object getItem(int position) {
        return URLS[position];
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolader holader = null;
        if (convertView == null) {
            holader = new ViewHolader();
            convertView = LayoutInflater.from(mContext).inflate(R.layout.adapter_item_layout, null);
            convertView.setTag(holader);
        } else {
            holader = (ViewHolader) convertView.getTag();
        }
        holader.iv = (ImageView) convertView.findViewById(R.id.iv_icon);
        holader.iv.setImageResource(R.mipmap.ic_launcher);
        String imageUrl = URLS[position];
        holader.iv.setTag(imageUrl);
        mImageLoader.showImage(holader.iv, imageUrl);
        return convertView;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE) {
            //加載可見項
            mImageLoader.showIamges(mStart, mEnd);
        } else {
            // 停止任務
            mImageLoader.cancelAllTask();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mStart = firstVisibleItem;
        mEnd = firstVisibleItem + visibleItemCount;
        //第一次顯示時候調用
        if (mFirstIn && visibleItemCount > 0) {
            mImageLoader.showIamges(mStart, mEnd);
            mFirstIn = false;
        }
    }

    class ViewHolader {
        public ImageView iv;
    }

}
ImageAdapter還實現了AbsListView.OnScrollListener,用於滑動監聽,第一次顯示的時候,直接加載可見項圖片,當滑動時,取消所有下載項,當滑動停止,加載可見項圖片。代碼比較簡單。下面再看看ImageLoader類,
/**
 * 圖片處理類
 */
public class ImageLoader {

    private LruCache mCache;//LruCache緩存對象
    private ListView mListView;// ListView的實例
    private Set mTask;//下載任務的集合
    public ImageLoader(ListView listView) {
        mListView = listView;
        mTask = new HashSet<>();
        //獲取最大可用內存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        //設置緩存的大小
        int cacheSize = maxMemory / 8;
        mCache = new LruCache(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //在每次存入緩存的時候調用
                return value.getByteCount();
            }
        };
    }

    /**
     * 將bitmap加入到緩存中
     *
     * @param url LruCache的鍵,即圖片的下載路徑
     * @param bitmap LruCache的值,即圖片的Bitmap對象
     */
    public void addBitmapToCache(String url, Bitmap bitmap) {
        if (getBitmapByImageUrl(url) == null) {
            mCache.put(url, bitmap);
        }
    }

    /**
     * 從緩存中獲取bitmap
     *
     * @param url LruCache的鍵,即圖片的下載路徑
     * @return 對應傳入鍵的Bitmap對象,或者null
     */
    public Bitmap getBitmapFromCache(String url) {
        Bitmap bitmap = mCache.get(url);
        return bitmap;
    }

    /**
     * 加載Bitmap對象。
     *
     * @param start 第一個可見的ImageView的下標
     * @param end   最後一個可見的ImageView的下標
     */
    public void showIamges(int start, int end) {
        for (int i = start; i < end; i++) {
            String imageUrl = ImageAdapter.URLS[i];
            //從緩存中取圖片
            Bitmap bitmap = getBitmapFromCache(imageUrl);
            //如果緩存中沒有,則去下載
            if (bitmap == null) {
                IamgeLoaderTask task = new IamgeLoaderTask(imageUrl);
                task.execute();
                mTask.add(task);
            } else {
                ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);
                imageView.setImageBitmap(bitmap);
            }

        }
    }

    /**
     * 取消所有下載任務
     */
    public void cancelAllTask() {
        if (mTask != null) {
            for (IamgeLoaderTask task : mTask) {
                task.cancel(false);
            }
        }
    }

    /**
     * 顯示圖片
     *
     * @param imageView
     * @param imageUrl
     */
    public void showImage(ImageView imageView, String imageUrl) {
        //從緩存中取圖片
        Bitmap bitmap = getBitmapFromCache(imageUrl);
        //如果緩存中沒有,則去下載
        if (bitmap == null) {
            imageView.setImageResource(R.mipmap.ic_launcher);
        } else {
            imageView.setImageBitmap(bitmap);
        }
    }

    /**
     * 下載並顯示圖片
     */
    private class IamgeLoaderTask extends AsyncTask {
        private String mImageUrl;


        IamgeLoaderTask(String imageUrl) {
            mImageUrl = imageUrl;
        }

        @Override
        protected Bitmap doInBackground(Void... params) {
            Bitmap bitmap = getBitmapByImageUrl(mImageUrl);
            if (bitmap != null) {
                addBitmapToCache(mImageUrl, bitmap);
            }
            return bitmap;
        }

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

    /**
     * 根據圖片路徑下載圖片Bitmap
     *
     * @param imageUrl 圖片網絡路徑
     * @return
     */
    public Bitmap getBitmapByImageUrl(String imageUrl) {
        Bitmap bitamp = null;
        HttpURLConnection con = null;
        try {
            URL url = new URL(imageUrl);
            con = (HttpURLConnection) url.openConnection();
            con.setConnectTimeout(10 * 1000);
            con.setReadTimeout(10 * 1000);
            bitamp = BitmapFactory.decodeStream(con.getInputStream());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (con != null) {
                con.disconnect();
            }
        }
        return bitamp;
    }
}
ImageLoader有圖片下載,緩存處理。思路是,當要顯示一張圖片時,先去緩存中查找,如果緩存中沒有,那麼開啟異步任務去下載該圖片,下載成功後,顯示並加入到緩存中;如果緩存中有,則取出直接顯示。

 

最後在看看MainActivity的代碼,代碼如下,

 

public class MainActivity extends Activity {


    ImageAdapter adapter;


   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ListView lv = (ListView) findViewById(R.id.lv);
        adapter = new ImageAdapter(MainActivity.this, Images.imageUrls, lv);
        lv.setAdapter(adapter);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        adapter.mImageLoader.cancelAllTask();
    }
}
還有布局文件activity_main.xml,只有一個ListView,

 

 




   

MainActivity中的代碼非常簡單,就不多說了,在Activity被銷毀時取消掉了所有的下載任務。另外由於我們使用了網絡,還需要在AndroidManifest.xml中加入網絡權限的聲明。

 

運行後,效果如下:

\

至此,有關LruCache的使用就結束了!

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