android-----帶你一步一步最佳化ListView(三)

來源:互聯網
上載者:User

標籤:

        前兩篇我們介紹了一般的最佳化ListView方法以及DiskLruCache最佳化ListView,見android-----帶你一步一步最佳化ListView(一)和android-----帶你一步一步最佳化ListView(二),這一篇我們將從記憶體緩衝的角度來完成ListView的最佳化,使用的是LruCache,它的主要演算法原理是把最近使用的對象用強引用儲存在 LinkedHashMap 中,並且把最近最少使用的對象在緩衝值達到預設定值之前從記憶體中移除,並沒有一個固定的緩衝大小是符合所有應用程式的,我們應該根據自己應用程式的狀況設定出合理的緩衝大小,下面我們通過執行個體來學習一下LruCache具體怎麼使用:

        首先定義顯示ListView的布局檔案:listview.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <ListView         android:id="@+id/listView"        android:layout_width="match_parent"        android:layout_height="match_parent">    </ListView></LinearLayout>
        接著定義每個item顯示的樣式item.xml

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <ImageView        android:id="@+id/imageView"        android:layout_width="50dp"        android:layout_height="50dp"        />    <TextView        android:id="@+id/textView"        android:layout_width="100dp"        android:layout_height="50dp"        android:layout_toRightOf="@id/imageView"        android:layout_marginTop="20dp"        android:layout_marginRight="70dp"        /></RelativeLayout>
        接下來就是最重要的ListViewAdapter適配器內容了,代碼有點長度,先貼出來,接下來慢慢解釋:

public class ListViewAdapter extends BaseAdapter{//儲存所有將要訪問的路徑public List<String> list;public LruCache<String, Bitmap> lruCache;public LayoutInflater inflater;public ListView listView;public int reqWidth;public int reqHeight;public Set<ImageAsyncTask> tasks = new HashSet<ListViewAdapter.ImageAsyncTask>();public ListViewAdapter(Context context,List<String> list,LruCache<String, Bitmap> lruCache,ListView listView,ImageView imageView){this.list = list;this.lruCache = lruCache;this.inflater = LayoutInflater.from(context);this.listView = listView;LayoutParams params = imageView.getLayoutParams();reqWidth = params.width;reqHeight = params.height;}@Overridepublic int getCount() {return list.size();}@Overridepublic String getItem(int position) {return list.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {View view = null;ViewHolder holder = null;if(convertView == null){view = inflater.inflate(R.layout.item, null);holder = new ViewHolder();holder.imageView = (ImageView) view.findViewById(R.id.imageView);holder.textView = (TextView) view.findViewById(R.id.textView);view.setTag(holder);}else{view = convertView;holder = (ViewHolder) view.getTag();}//為ImageView以及TextView設定Tag防止出現亂序holder.imageView.setTag(position);holder.textView.setTag(position+"#");return view;}/** * 添加當前key值對應的Bitmap到LruCache中 * @param key * @param bitmap */public void addBitmapToMemoryCache(String key,Bitmap bitmap){if(getBitmapFromMemoryCache(key) == null){lruCache.put(key, bitmap);}}/** * 從LruCache中擷取對應於key的Bitmap對象 * @param key * @return */public Bitmap getBitmapFromMemoryCache(String key){return lruCache.get(key);}/** * 載入某一位置上的圖片 * @param url * @param position */public void loadImage(int position){//擷取到position位置對應的urlString url = getItem(position);Bitmap bitmap = null;bitmap = getBitmapFromMemoryCache(url);if(bitmap != null){//表示緩衝中存在對應的於此url的Bitmap,則直接獲得該Bitmap並且顯示到ListView上面ImageView imageView = (ImageView) listView.findViewWithTag(position);TextView textView = (TextView) listView.findViewWithTag(position+"#");if(imageView != null)imageView.setImageBitmap(bitmap);if(textView != null)textView.setText("從緩衝中擷取的");}else{//開啟線程從網路中載入圖片ImageAsyncTask task = new ImageAsyncTask(listView, position);task.setOnImageLoadListener(new OnImageLoadListener() {@Overridepublic void onSuccessLoad(Bitmap bitmap) {System.out.println("載入圖片成功");}@Overridepublic void onFailureLoad() {System.out.println("載入圖片失敗");}});tasks.add(task);task.execute(url);//開啟線程載入圖片}}/** * 暫停所有任務(為了防止在滑動的時候仍然有線程處於請求狀態) */public void cancelTask(){if(tasks != null){for(ImageAsyncTask task: tasks)task.cancel(false);//暫停任務}}/** * 對圖片進行壓縮處理 * @param in * @param reqWidth * @param reqHeight * @return */public static Bitmap decodeSampleBitmapFromStream(InputStream in,int reqWidth,int reqHeight){//設定BitmapFactory.Options的inJustDecodeBounds屬性為true表示禁止為bitmap分配記憶體BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;byte[] data = inputStreamToByteArray(in);//這次調用的目的是擷取到原始圖片的寬、高,但是這次操作是沒有寫記憶體操作的Bitmap beforeBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);//設定這次載入圖片需要載入到記憶體中options.inJustDecodeBounds = false;Bitmap afterBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);return afterBitmap;}/** * 計算出壓縮比 * @param options * @param reqWith * @param reqHeight * @return */public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){//通過參數options來擷取真實圖片的寬、高int width = options.outWidth;int height = options.outHeight;int inSampleSize = 1;//初始值是沒有壓縮的if(width > reqWidth || height > reqHeight){//計算出原始寬與現有寬,原始高與現有高的比率int widthRatio = Math.round((float)width/(float)reqWidth);int heightRatio = Math.round((float)height/(float)reqHeight);//選出兩個比率中的較小值,這樣的話能夠保證圖片顯示完全 inSampleSize = widthRatio < heightRatio ? widthRatio:heightRatio;}return inSampleSize;}/** * 將InputStream轉換為Byte數組 * @param in * @return */public static byte[] inputStreamToByteArray(InputStream in){ByteArrayOutputStream outputStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;try {while((len = in.read(buffer)) != -1){outputStream.write(buffer, 0, len);}} catch (IOException e) {e.printStackTrace();}finally{try {in.close();outputStream.close();} catch (IOException e) {e.printStackTrace();}}return outputStream.toByteArray();}static class ViewHolder{ImageView imageView;TextView textView;}class ImageAsyncTask extends AsyncTask<String, Void, Bitmap>{public ListView listView;public OnImageLoadListener listener;public int position;public ImageAsyncTask(ListView listView,int position){this.listView = listView;this.position = position;}public void setOnImageLoadListener(OnImageLoadListener listener){this.listener = listener;}@Overrideprotected Bitmap doInBackground(String... params) {String urlString = params[0];URL url = null;InputStream in = null;HttpURLConnection connection = null;Bitmap bitmap = null;try {url = new URL(urlString);connection = (HttpURLConnection) url.openConnection();in = connection.getInputStream();bitmap = decodeSampleBitmapFromStream(in, reqWidth, reqHeight);//將當前Bitmap添加到緩衝中if(bitmap != null)lruCache.put(urlString, bitmap);} catch (Exception e) {e.printStackTrace();}return bitmap;}@Overrideprotected void onPostExecute(Bitmap result) {if(result != null){listener.onSuccessLoad(result);ImageView imageView = (ImageView) listView.findViewWithTag(position);TextView textView = (TextView)listView.findViewWithTag(position+"#");if(imageView != null)imageView.setImageBitmap(result);if(textView != null)textView.setText("從網路中擷取的");}elselistener.onFailureLoad();}}}

        其中39行的getView方法代碼就是我們平常使用ListView的常規寫法,複用convertView以及對每個view設定viewHolder,為防止圖片載入出現亂序,第55、56行我們分別對ImageView以及TextView設定了Tag標誌;

        第65行的addBitmapToMemoryCache用於向LruCache中添加當前Bitmap位元影像,因為LruCache本身實際上是由LinkedHashMap實現的,所有調用的是put方法;

        第78行的getBitmapFromMemoryCache方法用於從LruCache緩衝中讀取指定key值的Bitmap位元影像;

        第88行的loadImage就是我們的核心代碼,首先第91行會通過調用getItem方法來獲得當前item條目所要載入圖片的url,隨後93行調用getBitmapFromMemoryCache查看緩衝中是否存在指定key值的Bitmap,第94行進行判斷,如果存在Bitmap的話,則進入if語句塊中,97、98行通過findViewWithTag來獲得對應條目的ImageView以及TextView,並且將當前擷取到的緩衝圖片顯示到當前item;如果當前緩衝中不存在的話,則進入103行的else語句塊中,106行定義一個ImageAsyncTask圖片載入線程,並在119行將當前線程加入到Set<ImageAsyncTask>類型的tasks集合中,便於我們隨後對載入圖片線程進行控制,第120行調用execute方法,將當前需要載入圖片的url傳入;

       execute接下會調用ImageAsyncTask的doInBackground方法來載入圖片,這裡的圖片載入方法和之前android-----帶你一步一步最佳化ListView(二) 是一致的,不清楚的朋友可以看看上一篇部落格,另外這裡面也用到了圖片壓縮技術,不太懂的可以看看android-----解決Bitmap記憶體溢出的一種方法(圖片壓縮技術),在圖片壓縮完成之後會在第245行將當前Bitmap添加到LruCache中,隨後在onPostExecute中進行更新UI的操作即可啦;

最後就是MainActivity方法了:

public class MainActivity extends Activity implements OnScrollListener {public LruCache<String, Bitmap> memoryCache;public int start_index;public int end_index;public ListViewAdapter adapter;public boolean isInit = true;public String[] images =  { "http://img.my.csdn.net/uploads/201407/26/1406383299_1976.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383291_6518.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383291_8239.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383290_9329.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383290_1042.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383275_3977.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383265_8550.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383264_3954.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383264_4787.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383264_8243.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383248_3693.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383243_5120.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383242_3127.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383242_9576.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383242_1721.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383219_5806.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383214_7794.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383213_4418.jpg",          "http://img.my.csdn.net/uploads/201407/26/1406383213_3557.jpg",      "http://img.my.csdn.net/uploads/201407/26/1406383210_8779.jpg"};public List<String> list = new ArrayList<String>();public ListView listView;public ImageView imageView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.listview);memoryCache = getLruCache();for(int i = 0;i < 50;i++){int index = i %(images.length);list.add(images[index]);}listView = (ListView) findViewById(R.id.listView);LayoutInflater inflater = LayoutInflater.from(this);View view = inflater.inflate(R.layout.item, null);imageView = (ImageView) view.findViewById(R.id.imageView);adapter = new ListViewAdapter(this, list, memoryCache, listView, imageView);listView.setOnScrollListener(this);listView.setAdapter(adapter);}/** * 獲得LruCache對象 * @return */public LruCache<String, Bitmap> getLruCache(){LruCache<String, Bitmap> cache = null;//獲得可用的記憶體大小int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);//將其1/8設定成我們的記憶體緩衝大小int cacheSize = maxMemory/8;cache = new LruCache<String, Bitmap>(cacheSize){@Overrideprotected int sizeOf(String key, Bitmap value) {//返回當前圖片的大小(getByteCount用於返回當前位元影像所佔用的記憶體位元組數)return value.getByteCount() / 1024;}};return cache;}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {start_index = firstVisibleItem;end_index = start_index + visibleItemCount;if(isInit == true && visibleItemCount > 0){for(int i = start_index;i < end_index;i++){adapter.loadImage(i);    }isInit = false;}}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE){//表示停止滑動,這時候就可以載入圖片for(int i = start_index;i < end_index;i++){adapter.loadImage(i);    }}else{adapter.cancelTask();}}}

        這裡比較關鍵的代碼就是第55行的getLruCache方法了,首先會通過Runtime.getRuntime().maxMemory()獲得當前可用的記憶體大小,之後的cacheSize就是我們自己設定的LruCache所用的記憶體大小,第66行的sizeof方法用於返回當前圖片的大小;

        同樣,類似於上一篇中DiskLruCache的使用,這裡我們也設定了ListView的滑動事件,保證其在滑動的過程中不會進行載入圖片的請求操作,滑動停止再去載入圖片;

        另外提示一句,不要忘記添加訪問網路的許可權:

<uses-permission android:name="android.permission.INTERNET"></uses-permission>
點擊下載源碼!!!!!

我把上一篇部落格和這篇的代碼整合成了一個完整的帶有DiskLruCache和LruCache緩衝的ListView版本:

點擊下載整合源碼!!!!!


       

       

android-----帶你一步一步最佳化ListView(三)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.