android異步下載照片墻
版權(quán)聲明:本文出自ShengFQ的博客.
轉(zhuǎn)載請(qǐng)注明出處:http://www.reibang.com/p/1709ea24ebbb
我的問(wèn)題
我要實(shí)現(xiàn)從服務(wù)器端下載一個(gè)圖片地址列表捌袜,并異步下載圖片展示在imageview亮瓷,實(shí)現(xiàn)緩存和壓縮幢泼。如果要下載壓縮后的圖片款侵,應(yīng)該是由服務(wù)器端先壓縮后存儲(chǔ)肝集,這里只說(shuō)客戶端范圍,不做討論盾计。
需要預(yù)備的知識(shí)點(diǎn):
1.AsyncTask異步調(diào)用方法下載json數(shù)據(jù)
2.listview钙皮,viewadapter的高性能寫法
3.HandlerThread,Hander,Message異步消息機(jī)制
4.LurCache緩存
5.BitmapFactory的壓縮圖片
1.AsyncTask異步調(diào)用方法下載json數(shù)據(jù)
一個(gè)AsyncTask和一個(gè)Adapter組合曼追,從服務(wù)器端拉取包含圖片地址的json格式數(shù)據(jù)窍仰。
2.listview,viewadapter的高性能寫法
viewadapter.java 部分實(shí)現(xiàn)
Object mlock;//異步操作list時(shí)礼殊,需要對(duì)list進(jìn)行同步鎖定
Context mContext;//調(diào)用上下文
List<Model> datalist;//list數(shù)據(jù)源
int itemLayoutId;//adapterview的個(gè)性化顯示布局id
getDataList();
reload(List models);
add(model model);
addAll(List models);
render(Model model,View view,int position);//重復(fù)利用ViewHolder提升getView方法的性能
getCount();
getItem(int position);
getItemId(int position);
class ViewHolder{}
getView(int position,View convertView,ViewGroup parent);
//將一特定的數(shù)據(jù)格式添加到view中
protected void render(LinkModel linkmodel,View view,int position){
final LinkModel item=linkmodel;
ViewHolder viewHolder=(ViewHolder)view.getTag();
//重復(fù)利用視圖組件驹吮,將視圖組件存儲(chǔ)到裝載對(duì)象中
if(viewHolder==null){
viewHolder=new ViewHolder();
viewHolder.app_layout= (FrameLayout)view.findViewById(R.id.iv_app_layout);
viewHolder.app_icon_layout=(LinearLayout)view.findViewById(R.id.app_icon_layout);
viewHolder.app_log_iv=(ImageView)view.findViewById(R.id.ic_app_ico);
viewHolder.app_name_tv=(TextView)view.findViewById(R.id.ic_app_name);
view.setTag(viewHolder);
}else{
viewHolder=(ViewHolder)view.getTag();
}
if(item!=null){
viewHolder.app_log_iv.setImageResource(R.drawable.app_img_app_normal);
viewHolder.app_name_tv.setText(item.name);
if (ImageCache.getInstance(mCacheSize).isCache(item.photoUrl)) {
viewHolder.app_log_iv.setImageBitmap(ImageCache.getInstance(mCacheSize).getBitmapFromMemCache(item.photoUrl));
}else{
//后臺(tái)線程異步下載圖片,Token在這里指定了imageview的實(shí)例
mThumbnailThread.queueThumbnail(viewHolder.app_log_iv, item.photoUrl);
}
}
}
在AsyncTask中將后臺(tái)的json數(shù)據(jù)封裝到實(shí)體對(duì)象,通過(guò)上述的adapter.addAll(List models)注入到adapter中.
3.HandlerThread,Hander,Message異步消息機(jī)制
定義ThunbnailDownloader類,用來(lái)通過(guò)發(fā)送下載請(qǐng)求消息,從另一個(gè)線程去后臺(tái)下載并通過(guò)與前臺(tái)UI線程交互加載.
public class ThumbnailDownLoader<Token> extends HandlerThread {
private static final String TAG="ThumbnailDownLoader";
private static final int MESSAGE_DOWNLOAD=0;
private Handler mHandler;
private String requestToken;
private int mCacheSize;
Map<Token,String> requestMap= Collections.synchronizedMap(new HashMap<Token,String>());
/**
* 主線程中的Handler對(duì)象
* */
Handler mRespoonseHandler;
//回調(diào)接口變量
Listener<Token> mListener;
/**
* 用于通信的監(jiān)聽(tīng)器接口
* 當(dāng)下載完要執(zhí)行的事情:將圖片加載到UI線程的ImageView
* */
public interface Listener<Token>{
/**
* 后臺(tái)線程的輸出,將下載的圖片指定給前臺(tái)的ImageView
* @param token 存放圖片的容器
* @param thumbnail 圖片格式
* */
void onThumbnailDownloaded(Token token,Bitmap thumbnail);
}
public void setListener(Listener<Token> listener){
mListener=listener;
}
public ThumbnailDownLoader(String requesttoken){
super(TAG);
this.requestToken=requesttoken;
}
/**
* 主線程傳遞的Handler
* @param responseHandler 前臺(tái)UI線程handler
* @param requesttoken 遠(yuǎn)程下載請(qǐng)求的token
* @param cacheSize 緩存大小
* */
public ThumbnailDownLoader(Handler responseHandler,String requesttoken,int cacheSize){
super(TAG);
mRespoonseHandler=responseHandler;
this.requestToken=requesttoken;
this.mCacheSize=cacheSize;
}
/**
* 該方法的調(diào)用發(fā)生在Looper第一次檢查消息隊(duì)列之前
* */
@SuppressLint("handlerLeak")
@Override
protected void onLooperPrepared(){
mHandler=new Handler(){
//looper取得消息隊(duì)列中的特定消息,回調(diào)方法根據(jù)消息what屬性進(jìn)行處理
public void handleMessage(Message msg){
if(msg.what==MESSAGE_DOWNLOAD){
@SuppressWarnings("unchecked")
Token token=(Token)msg.obj;//Handler.obtainMessage(msg,obj);通過(guò)Handler發(fā)送Message傳遞了message.obj,這里處理消息時(shí),可以獲取obj,屬于約定內(nèi)容晶伦。
Log.i(TAG,"Got a request for url:"+requestMap.get(token));
handleRequest(token,requestToken);
}
}
};
}
/**
* 發(fā)送message請(qǐng)求下載圖片,將URL和Token傳遞到同步hashMap中
* 在調(diào)用getView()的時(shí)候請(qǐng)求下載
* @param token 前臺(tái)交互的UI控件
* @param url 前臺(tái)指定的下載地址
* */
public void queueThumbnail(Token token,String url){
Log.i(TAG,"Got to URL:"+url);
requestMap.put(token,url);//調(diào)用getview()的時(shí)候調(diào)用
Message message=mHandler.obtainMessage(MESSAGE_DOWNLOAD,token);//創(chuàng)建信息并傳入消息字段,自動(dòng)完成目標(biāo)handler的設(shè)置
message.sendToTarget();//將消息壓入消息隊(duì)列
}
/**
* 遠(yuǎn)程下載圖片碟狞,將后臺(tái)下載的圖片加載到前臺(tái)UI的ImageView中
* @param token 泛型參數(shù),這里指前臺(tái)的ImageView
* */
private void handleRequest(final Token token,final String requestToken){
try{
final String url=requestMap.get(token);
if(url==null)
return;
byte[] bitmapBytes=new ImageLoaderUtils().getUrlBytes(url,requestToken);
// final Bitmap bitmap= BitmapFactory.decodeByteArray(bitmapBytes,0,bitmapBytes.length);
final Bitmap bitmap= ImageCompress.decodeSampleBitmapFromBytes(bitmapBytes, 54, 54);//圖片壓縮
ImageCache.getInstance(mCacheSize).addBitmapToMemoryCache(url, bitmap);
Log.i(TAG,"Bitmap created");
//此處定義了主線程在后臺(tái)線程交互操作的UI處理
mRespoonseHandler.post(new Runnable(){
public void run(){
if(requestMap.get(token)!=url) return;
requestMap.remove(token);//
Bitmap cachebitmap =ImageCache.getInstance(mCacheSize).getBitmapFromMemCache(url);
if(cachebitmap!=null)
mListener.onThumbnailDownloaded(token,cachebitmap);
else{
mListener.onThumbnailDownloaded(token,bitmap);
}
}
});
}catch(IOException ioe){
Log.e(TAG,"Error downloading image",ioe);
}
}
public void clearQueue(){
mHandler.removeMessages(MESSAGE_DOWNLOAD);
requestMap.clear();
}
}
說(shuō)明:HandlerThread是一個(gè)消息通知處理線程類
onLooperPrepared();//初始化Handler的地方,因?yàn)榇颂幵贖andlerThread初始化后,進(jìn)入Loop()之前就會(huì)調(diào)用的方法,在這里初始化Handler有助于在消息發(fā)送之前handler已經(jīng)初始化.
Handler
handlleMessage(Message msg);//根據(jù)消息的類別和消息附帶的參數(shù)進(jìn)行異步下載
handler.obtainMessage(String msg,object) //構(gòu)建一個(gè)Message對(duì)象
message.sendToTarget();//將消息發(fā)送到消息隊(duì)列
handler.removeMessages(String msg);//清除該消息,釋放資源
如何與前臺(tái)UI線程交互
已經(jīng)將image下載下來(lái)了,要填充到UI線程的Imageview中,這就需要與前臺(tái)交互,沒(méi)錯(cuò),前臺(tái)UI線程也有Handler,而且只有一個(gè),將前臺(tái)的handler傳遞到后臺(tái)共享
/**
* 主線程中的Handler對(duì)象
*
Handler mRespoonseHandler;
/**
* 主線程傳遞的Handler
* @param responseHandler 前臺(tái)UI線程handler
* @param requesttoken 遠(yuǎn)程下載請(qǐng)求的token
* @param cacheSize 緩存大小
* */
public ThumbnailDownLoader(Handler responseHandler,String requesttoken,int cacheSize){
super(TAG);
mRespoonseHandler=responseHandler;
this.requestToken=requesttoken;
this.mCacheSize=cacheSize;
}
在下載完圖片后,通過(guò)handler.post();方法與前臺(tái)UI交互
//此處定義了主線程在后臺(tái)線程交互操作的UI處理
mRespoonseHandler.post(new Runnable(){
public void run(){
if(requestMap.get(token)!=url) return;
requestMap.remove(token);//
Bitmap cachebitmap =ImageCache.getInstance(mCacheSize).getBitmapFromMemCache(url);
if(cachebitmap!=null)
mListener.onThumbnailDownloaded(token,cachebitmap);//回調(diào)接口方法
else{
mListener.onThumbnailDownloaded(token,bitmap);
}
}
});
Listener<Token> mListener;//回調(diào)接口對(duì)象,下載圖片后,定義行為將圖片設(shè)置到imageview
/**
* 用于通信的監(jiān)聽(tīng)器接口
* 當(dāng)下載完要執(zhí)行的事情:將圖片加載到UI線程的ImageView
* */
public interface Listener<Token>{
/**
* 后臺(tái)線程的輸出,將下載的圖片指定給前臺(tái)的ImageView
* @param token 存放圖片的容器
* @param thumbnail 圖片格式
* */
void onThumbnailDownloaded(Token token,Bitmap thumbnail);
}
public void setListener(Listener<Token> listener){
mListener=listener;
}
回到主線程UI
因此在UI線程中,做兩件事,初始化HandlerThread,實(shí)現(xiàn)回調(diào)接口
//初始化后臺(tái)線程下載圖片
mThumbnailThread = new ThumbnailDownLoader<ImageView>(new Handler(),token,mCacheSize);//與主線程Looper綁定的Handler
mThumbnailThread.setListener(new ThumbnailDownLoader.Listener<ImageView>() {
public void onThumbnailDownloaded(ImageView imageView, Bitmap thumbnail) {
if (true) {
//給ImageView設(shè)置圖片Bitmap
imageView.setImageBitmap(thumbnail);
}
}
});
mThumbnailThread.start();
mThumbnailThread.getLooper();
如何觸發(fā)異步下載
我們要實(shí)現(xiàn)的效果是,下拉gridview時(shí),自動(dòng)下載圖片并加載到gridview里的imageview視圖,所以我們將焦點(diǎn)回到adapter.getView()方法里,要實(shí)現(xiàn)這個(gè)功能,需要兩個(gè)重要參數(shù)傳遞到HandlerThread中
當(dāng)前的Imageview實(shí)例和需要下載的圖片URL
mThumbnailThread.queueThumbnail(viewHolder.app_log_iv, item.photoUrl);
4.LurCache緩存
主要使用了LruCache<K,V> 類,該類的使用方法如下
一般緩存大小設(shè)置為:
UI線程中指定
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用內(nèi)存值的1/8作為緩存的大小坝辫。
mCacheSize = maxMemory / 8;
/**
* 圖片緩存
* */
public class ImageCache {
private static ImageCache instance;
/**
* 實(shí)例化篷就,緩存大小
*/
public static ImageCache getInstance(int cacheSize) {
if (instance == null) {
synchronized (ImageCache.class) {
if (instance == null) {
instance = new ImageCache(cacheSize);
}
}
}
return instance;
}
private LruCache<String,Bitmap> mMemoryCache;
private ImageCache(int cacheSize){
mMemoryCache=new LruCache<String,Bitmap>(cacheSize){
protected int sizeOf(String key,Bitmap bitmap){
return bitmap.getByteCount()/1024;
}
};
}
/**
* 加載到緩存
* */
public void addBitmapToMemoryCache(String key,Bitmap bitmap){
if(getBitmapFromMemCache(key) ==null){
mMemoryCache.put(key, bitmap);
}
}
/**
* 從緩存中讀取
* */
public Bitmap getBitmapFromMemCache(String key){
return mMemoryCache.get(key);
}
/**
* 檢查是否緩存
* */
public boolean isCache(String key){
Bitmap map=mMemoryCache.get(key);
return map!=null;
}
}
5.BitmapFactory的壓縮圖片
ImageCompress.java
/**
*
* 加載輸入流圖片 網(wǎng)絡(luò)*/
public static Bitmap decodeSampleBitmapFromBytes(byte[] data,int reqWidth,int reqHeight){
final BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;
//BitmapFactory.decodeResource(resource, resId, options);
BitmapFactory.decodeByteArray(data, 0, data.length, options);
options.inSampleSize=calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds=false;
return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}
//怎么壓縮呢射亏,按照縮放比例inSamplesize,這個(gè)值是可要計(jì)算出來(lái)的
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
//
final int height=options.outHeight;
final int width=options.outWidth;
int inSampleSize=1;
if(height>reqHeight || width>reqWidth){
final int heightRatio=Math.round((float)height/(float)reqHeight);
final int widthRatio=Math.round((float)width/(float)reqWidth);
inSampleSize=heightRatio<widthRatio ?heightRatio:widthRatio;
}
return inSampleSize;
}
參考資料