編輯:關於Android編程
隔了很久沒寫博客,現在必須快速脈動回來。今天我還是接著上一個多線程中的異步加載系列中的最後一個使用異步加載實現ListView中的圖片緩存及其優化。具體來說這次是一個綜合Demo.但是個人覺得裡面還算有點價值的就是裡面的圖片的緩存的實現。因為老實說它確實能在實際的項目中得到很好的應用。主要學習來源於慕課網中的異步加載學習,來自徐宜生大神的靈感。本次也就是對大神所講知識的一個總結及一些個人的感受吧。
這次是一個綜合的Demo,主要裡面涉及到的知識主要有:網絡編程、異步加載、JSON解析、圖片緩存、通用ListAdapter的使用。最後實現一個加載網絡數據的圖文混排listView的效果。當然這裡面涉及到的知識比較多,但是本次的重點就是圖片緩存和異步加載,當然類似網絡編程中的HttpURLConnection,JSON解析、打造通用適配器等知識將會在後續博客中給出,這裡也就是使用我以前自己封裝好的,因為為了簡化開發。
這次的重點是異步加載和圖片緩存,至於異步加載因為在前兩個博客中已經寫得很清楚了,這次主要是用一下異步加載,看看異步加載在實際項目是怎麼使用的。主要是使用異步加載進行耗時網絡請求,並且自定義一個監聽器用於當獲得數據後,立即將獲得的數據回調出去。然後重點介紹的就是圖片緩存。
說到圖片緩存下面將通過以下幾個方面認識一下圖片緩存:
1、為什麼要使用圖片緩存?
很簡單“消耗流量特別大”,這個相信很多人都感同深受吧,因為我們可能都寫過一個類似網絡請求數據的ListView的圖文混排的Demo,但是如果我們直接通過網絡請求圖片,然後拿到的圖片顯示在ListView上,當滑動ListView,下次將已經滑過Item,會發現圖片重新請求一個網絡數據,重新加載一次,也就是滑到哪就請求一次網絡,不管是否重復。可想而知這流量消耗太大,估計這樣滑一晚上,第二天早上醒來,發現自己的房子都成中國移動的了。還有一個弊端就是每請求一次網絡都是一次異步和耗時過程,所以你會發現在滑動ListView會有卡頓情況出現。
2、圖片緩存原理是什麼?
圖片緩存是基於LRU算法來實現的,LRU即Least Recently Used,中文意思是最近最少未使用算法,學過操作系統原理就知道這是操作系統中頁面置換算法之一。
說到這,不妨來看看LruCache源碼是怎麼介紹的。
/** * A cache that holds strong references to a limited number of values. Each time * a value is accessed, it is moved to the head of a queue. When a value is * added to a full cache, the value at the end of that queue is evicted and may * become eligible for garbage collection. * *
If your cached values hold resources that need to be explicitly released, * override {@link #entryRemoved}. * *
If a cache miss should be computed on demand for the corresponding keys, * override {@link #create}. This simplifies the calling code, allowing it to * assume a value will always be returned, even when there's a cache miss. * *
By default, the cache size is measured in the number of entries. Override * {@link #sizeOf} to size the cache in different units. For example, this cache * is limited to 4MiB of bitmaps: *
{@code * int cacheSize = 4 * 1024 * 1024; // 4MiB * LruCache* *bitmapCache = new LruCache (cacheSize) { * protected int sizeOf(String key, Bitmap value) { * return value.getByteCount(); * } * }}
This class is thread-safe. Perform multiple cache operations atomically by * synchronizing on the cache:
{@code * synchronized (cache) { * if (cache.get(key) == null) { * cache.put(key, value); * } * }}* *
This class does not allow null to be used as a key or value. A return * value of null from {@link #get}, {@link #put} or {@link #remove} is * unambiguous: the key was not in the cache. * *
This class appeared in Android 3.1 (Honeycomb MR1); it's available as part * of Android's * Support Package for earlier releases. */
LruCache主要原理:緩存是限制了緩存的數目的,也就是說緩存的容量是有限的,可以把緩存的邏輯內存結構想象一個隊列,當緩存中一個緩存值被訪問後,它將會被置換到隊列的隊頭,當一個緩存值需要加到隊尾時,但是此時隊列已滿了,也即此時緩存空間已滿,那麼就需要將處於隊列隊尾一個緩存值出隊列,也即是釋放隊列隊尾一部分緩存空間,因為基於LRU算法處於隊尾的,肯定最近最少未使用。也就是因為緩存空間是有限的,才會基於這樣算法,及時並合適地將一些數據空間釋放。
LruCache類是線程安全的,它支持多個緩存操作自動通過異步來實現,並且這個類不允許用空值去作為key或者value,並且注意LruCache的key不是保存在緩存中的。
LurCache類出現在Android3.1版本。
3、LruCache如何創建:
LruCache實際在操作上很類似於Map的操作,初學者實際上就可以把它當做一個Map,因為它是key-value成對的,並且有put(),get()方法非常類似Map
個人覺得使用圖片緩存使用率很高,為了下次方便使用,索性直接將它封裝成一個工具類。
package com.mikyou.utils; import android.graphics.Bitmap; import android.util.LruCache; public class LruCacheUtils { //創建Cache緩存,第一個泛型表示緩存的標識key,第二個泛型表示需要緩存的對象 private LruCachemCaches; public LruCacheUtils() { int maxMemory=(int) Runtime.getRuntime().maxMemory();//獲取最大的應用運行時的最大內存 //通過獲得最大的運行時候的內存,合理分配緩存的內存空間大小 int cacheSize=maxMemory/4;//取最大運行內存的1/4; mCaches=new LruCache (cacheSize){ @Override protected int sizeOf(String key, Bitmap value) {//加載正確的內存大小 return value.getByteCount();//在每次存入緩存的時候調用 } }; } //將圖片保存在LruCache中 public void addBitmapToCache(String url,Bitmap bitmap){ if (getBitmapFromCache(url)==null) {//判斷當前的Url對應的Bitmap是否在Lru緩存中,如果不在緩存中,就把當前url對應的Bitmap對象加入Lru緩存 mCaches.put(url, bitmap); } } //將圖片從LruCache中讀取出來 public Bitmap getBitmapFromCache(String url){ Bitmap bitmap=mCaches.get(url);//實際上LruCache就是一個Map,底層是通過HashMap來實現的 return bitmap; } }
通過以上知識的講解,相信已經對LruCache有了一定的了解了,那麼接下來我們就開始我們的Demo吧。
1、首先、我們既然是加載網絡數據,所以得解決網絡數據來源問題,主要來自於慕課網的一個課程列表的API的地址,返回的數據是JSON格式的數據。
地址是:http://www.imooc.com/api/teacher?type=4&num=60。可以先用浏覽器來測試一下數據,測試結果如下:
注意:大家可能看到這裡面中文全部都亂碼了,這是因為Unicode編碼,我會在代碼中使用一個工具類將這些轉化成中文。
2、數據解決後,那麼接著就是布局,布局很簡單,主布局就是一個ListView,listItem布局也很簡單。
3、自己封裝的HttpURLConnection網絡請求框架,返回的是整個JSON數據
package com.mikyou.utils; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import org.json.JSONObject;import android.R.interpolator; public class MikyouHttpUrlConnectionUtils { private static StringBuffer buffer; public static String getData(String urlString,String apiKeyValue,ListstringList){ buffer=new StringBuffer(); String jsonOrXmlString=null; if (stringList!=null) { for (int i = 0; i
4、封裝課程對象的javaBean類對象即每個Item為一個對象
package com.mikyou.bean; import java.io.Serializable; import android.R.id; public class Course implements Serializable{ private String cName; private String cImgURl; private String cDescriptor; private String cLearner; public String getcName() { return cName; } public void setcName(String cName) { this.cName = cName; } public String getcImgURl() { return cImgURl; } public void setcImgURl(String cImgURl) { this.cImgURl = cImgURl; } public String getcDescriptor() { return cDescriptor; } public void setcDescriptor(String cDescriptor) { this.cDescriptor = cDescriptor; } public String getcLearner() { return cLearner; } public void setcLearner(String cLearner) { this.cLearner = cLearner; } }
5、通用適配器實現的子類
package com.mikyou.adapter; import java.util.List; import com.lidroid.xutils.BitmapUtils; import com.mikyou.async.ImageLoader; import com.mikyou.bean.Course; import com.mikyou.cache.R; import com.mikyou.tools.ViewHolder; import android.content.Context; import android.widget.ImageView; public class MyListAdapter extends CommonAdapter{ private ImageLoader loader; public MyListAdapter(Context context, List listBeans, int layoutId) { super(context, listBeans, layoutId); loader=new ImageLoader(); } @Override public void convert(ViewHolder holder, Course course) { holder.setText(R.id.c_name, course.getcName()).setText(R.id.c_learner, course.getcLearner()); ImageView iv= holder.getView(R.id.c_img); iv.setTag(course.getcImgURl());//首先、需要將相應的url和相應的iv綁定在一起,為了防止圖片和請求URL不對應 loader.showImageByAsyncTask(iv, course.getcImgURl()); } }
7、核心實現代碼:
package com.mikyou.async; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import com.mikyou.bean.Course; import com.mikyou.utils.LruCacheUtils; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.util.Log; import android.util.LruCache; import android.widget.ImageView; public class ImageLoader { private ImageView iv; private String url; private LruCacheUtils mCacheUtils; public ImageLoader() { mCacheUtils=new LruCacheUtils(); } /** * @author mikyou * 實現的主要思路: * 首先、加載圖片的時候,先去LruCache緩存中根據傳入的url作為key去取相應的Bitmap對象 * ,如果緩存中存在相應的key對應的value,那麼就直接取出key對應緩存中的Bitmap對象 * 並設置給ImageView,如果緩存中沒有,那麼就需要通過異步加載請求網絡中的數據和圖片信息, * 然後通過監聽器中的asyncImgListener回調方法將網絡請求得到的Bitmap對象,首先得通過iv.getTag() * 比較url如果對應就將該Bitmap對象設置給iv,並且還需要將這個Bitmap對象和相應的url以key-value形式 * 通過put方法,加入LruCache緩存中。 * */ public void showImageByAsyncTask(final ImageView iv,final String url){ //首先,從緩存中讀取圖片,如果有就直接使用緩存,如果沒有就直接加載網絡圖片 Bitmap bitmap=mCacheUtils.getBitmapFromCache(url); Log.d("url", url); if (bitmap==null) {//表示緩存中沒有,就去訪問網絡下載圖片,並記住將下載到的圖片放入緩存中 ImageAsyncTask imageAsyncTask=new ImageAsyncTask(); imageAsyncTask.execute(url); imageAsyncTask.setOnImgAsyncTaskListener(new OnAsyncListener() { @Override public void asyncListener(ListmCourseList) { } @Override public void asyncImgListener(Bitmap bitmap) {//圖片請求網絡數據的回調方法 if (iv.getTag().equals(url)) {//判斷url和iv是否對應 iv.setImageBitmap(bitmap); Log.d("addLru", "網絡加載並加入緩存--->"+url); mCacheUtils.addBitmapToCache(url, bitmap);//由於是網絡請求得到的數據,所以緩存中肯定沒有,所以還需要將該Bitmap對象加入到緩存中 } } }); }else{//否則就直接從緩存中獲取 iv.setImageBitmap(mCacheUtils.getBitmapFromCache(url));//直接讀取緩存中的Bitmap對象 Log.d("getLru", "url讀出緩存--->"+url); } } //HttpURLConnection網絡請求方式來得到網絡圖片輸入流,並且將輸入流轉換成一個Bitmap對象 public Bitmap getBitmapFromURL(String url){ Bitmap bitmap = null; try { URL mURL=new URL(url); HttpURLConnection conn=(HttpURLConnection) mURL.openConnection(); bitmap = BitmapFactory.decodeStream(conn.getInputStream()); conn.disconnect(); } catch (Exception e) { e.printStackTrace(); } return bitmap; } }
8、異步加載類實現,這裡主要有兩個:一個是請求整個網絡的JSON數據,另一個就是請求加載網絡圖片,並且自定義一個監聽器接口。
package com.mikyou.async; import java.util.ArrayList; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.mikyou.bean.Course; import com.mikyou.utils.MikyouHttpUrlConnectionUtils; import android.os.AsyncTask; import android.util.Log; public class MikyouAsyncTask extends AsyncTask{ private List mCourseList; private OnAsyncListener listener;//自定義監聽器接口對象引用 @Override protected void onPreExecute() { mCourseList=new ArrayList (); super.onPreExecute(); } @Override protected String doInBackground(String... params) { String data=MikyouHttpUrlConnectionUtils.getData(params[0], null, null);//網絡請求JSON數據 return data; } @Override protected void onPostExecute(String result) {//解析JSON數據 Log.d("info", result); try { JSONObject object=new JSONObject(result); JSONArray array=object.getJSONArray("data"); for (int i = 0; i < array.length(); i++) { Course mCourse=new Course(); JSONObject object2=array.getJSONObject(i); mCourse.setcName(object2.getString("name")); mCourse.setcImgURl(object2.getString("picSmall")); mCourse.setcLearner(object2.getInt("learner")+""); mCourse.setcDescriptor(object2.getString("description")); mCourseList.add(mCourse); } if (listener!=null) {//判斷是否注冊了監聽器 listener.asyncListener(mCourseList);//通過監聽器中的回調方法將異步加載得到的數據後經過解析、封裝的對象集合回調出去 } } catch (JSONException e) { e.printStackTrace(); } super.onPostExecute(result); } public void setOnAsyncTaskListener(OnAsyncListener listener){//公布一個注冊監聽器的方法 this.listener=listener; } }
ImgAsyncTask異步加載類:
package com.mikyou.async; import java.net.HttpURLConnection; import java.net.URL; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.Image; import android.os.AsyncTask; import android.text.GetChars; import android.util.LruCache; import android.widget.ImageView; public class ImageAsyncTask extends AsyncTask{ private OnAsyncListener listener; @Override protected Bitmap doInBackground(String... params) { return getBitmapFromURL(params[0]); } @Override protected void onPostExecute(Bitmap result) { if (listener!=null) { listener.asyncImgListener(result); } super.onPostExecute(result); } public Bitmap getBitmapFromURL(String url){ Bitmap bitmap = null; try { URL mURL=new URL(url); HttpURLConnection conn=(HttpURLConnection) mURL.openConnection(); bitmap = BitmapFactory.decodeStream(conn.getInputStream()); conn.disconnect(); } catch (Exception e) { e.printStackTrace(); } return bitmap; } public void setOnImgAsyncTaskListener(OnAsyncListener listener){ this.listener=listener; } }
自定義監聽器:
監聽器接口:
package com.mikyou.async; import java.util.List; import com.mikyou.bean.Course; import android.graphics.Bitmap; public interface OnAsyncListener { public void asyncListener(ListmCourseList); public void asyncImgListener(Bitmap bitmap); }
運行結果:
沒有加入圖片緩存的運行結果會發現無論什麼時候滑動都會請求網絡,會發現圖片加載有個延遲時間:
加入圖片緩存後的運行結果會發現,非常流暢,並且直接讀緩存的圖片時沒有圖片加載的延遲
最後,圖片緩存LruCache實際上運用很流行,並且運用在很多流行網絡框架中,我們都知道很流行的Xutils框架,其中就有一個BitmapUtils,它裡面實現緩存原理也就是基於LruCache來實現的。
今天做項目時,要求更改程序的包名。於是經過查資料與摸索。情況1:直接修改包名的“尾巴”,也就是包名的最後一級。比如:一個包名叫zzjr.com.t
android 異步任務的一個後台方法本質是開啟一個線程完成耗時操作,其他onPostExecute方法和onPreExecute方法運行在UI主線程用於更新UI界面。為
最近項目裡面需要支付功能,boos一致決定用微信支付,所以在網上查了很多資料,說的不全,完了就找以前的同事指教。算是成功集成上去了。在這裡做個總結記錄。1、在APP上集成
概述:項目地址:https://github.com/nostra13/Android-Universal-Image-Loader UIL(Universal-Imag