【安卓筆記】非同步載入大量圖片
阿新 • • 發佈:2019-02-06
3.使用lrucache對bitmap進行記憶體快取的類package cn.edu.chd.utils; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; /** * @author Rowand jj * 壓縮圖片 */ public class BitmapUtils { /** * 根據資源id獲取到圖片,並進行壓縮 * @param res * @param resId * @param reqWidth * @param reqHeight * @return */ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, opts); int inSampleSize = cacluateInSampleSize(opts, reqWidth, reqHeight); opts.inSampleSize = inSampleSize; opts.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeResource(res, resId, opts); return bitmap; } /** * 從byte陣列中獲取圖片並壓縮 * @param data * @param reqWidth * @param reqHeight * @return */ public static Bitmap decodeSampledBitmapFromByteArray(byte[] data, int reqWidth, int reqHeight) { BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(data, 0, data.length, opts); int inSampleSize = cacluateInSampleSize(opts, reqWidth, reqHeight); opts.inJustDecodeBounds = false; opts.inSampleSize = inSampleSize; Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opts); return bitmap; } private static int cacluateInSampleSize(BitmapFactory.Options opts, int reqWidth, int reqHeight) { if (opts == null) return 1; int inSampleSize = 1; int realWidth = opts.outWidth; int realHeight = opts.outHeight; if (realHeight > reqHeight || realWidth > reqWidth) { int heightRatio = realHeight / reqHeight; int widthRatio = realWidth / reqWidth; inSampleSize = (heightRatio > widthRatio) ? widthRatio : heightRatio; } return inSampleSize; } }
4.檔案快取的類package cn.edu.chd.utils; import android.graphics.Bitmap; import android.support.v4.util.LruCache; import android.util.Log; /** * @author Rowand jj * *使用lrucache快取圖片到記憶體,做成了單例模式 */ public class BitmapLruCacheHelper { private static final String TAG = null; private static BitmapLruCacheHelper instance = new BitmapLruCacheHelper(); private LruCache<String,Bitmap> cache = null; private BitmapLruCacheHelper() { int maxSize = (int) (Runtime.getRuntime().maxMemory()/8); cache = new LruCache<String, Bitmap>(maxSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes()*value.getHeight(); } }; } /** *加入快取 * @param key * @param value */ public void addBitmapToMemCache(String key,Bitmap value) { if(key == null || value == null) { return; } if(cache!=null && getBitmapFromMemCache(key)==null) { cache.put(key, value); Log.i(TAG,"put to lrucache success"); } } /** * 從快取中獲取圖片 * @param key * @return */ public Bitmap getBitmapFromMemCache(String key) { if(key == null) { return null; } Bitmap bitmap = cache.get(key); Log.i(TAG,"from lrucache,bitmap="+bitmap); return bitmap; } /** * 獲取例項 * @return */ public static BitmapLruCacheHelper getInstance() { return instance; } }
5.一個圖片下載器的類package cn.edu.chd.utils; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Comparator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.os.Environment; import android.os.StatFs; import android.util.Log; /** * @author Rowand jj * *檔案快取 */ public class FileCacheUtils { /** *圖片快取的相對路徑 */ private static final String IMG_CACH_DIR = "/imgCache"; /** * 手機快取目錄 */ private static String DATA_ROOT_PATH = null; /** * sd卡根目錄 */ private static String SD_ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath(); /** *快取的副檔名 */ private static final String CACHE_TAIL = ".cach"; /** * 最大快取空間,單位是mb */ private static final int CACHE_SIZE = 4; /** * sd卡記憶體低於此值時將會清理快取,單位是mb */ private static final int NEED_TO_CLEAN = 10; /** * 上下文 */ private Context context; private static final String TAG = "BitmapFileCacheUtils"; public FileCacheUtils(Context context) { this.context = context; DATA_ROOT_PATH = context.getCacheDir().getAbsolutePath(); } /** * 從快取中獲取一張圖片 */ public Bitmap getBitmapFromFile(String key) { if(key==null) { return null; } String filename = getCacheDirectory()+File.separator+convertKeyToFilename(key); File file = new File(filename); if(file.exists()) { Bitmap bitmap = BitmapFactory.decodeFile(filename); if(bitmap == null) { file.delete(); } else { updateFileModifiedTime(filename); Log.i(TAG,"get file from sdcard cache success..."); return bitmap; } } return null; } /** * 將圖片存入檔案快取 */ public void addBitmapToFile(String key,Bitmap bm) { if(bm == null || key == null) { return; } //視情況清除部分快取 removeCache(getCacheDirectory()); String filename = convertKeyToFilename(key); File dir = new File(getCacheDirectory()); if(!dir.exists()) { dir.mkdirs(); } File file = new File(dir, filename); try { OutputStream out = new FileOutputStream(file);//這裡需要注意,如果指定目錄不存在,應該先呼叫mkdirs生成目錄,否則可能建立檔案失敗 bm.compress(CompressFormat.JPEG,100, out); out.close(); Log.i(TAG,"add file to sdcard cache success..."); } catch (Exception e) { e.printStackTrace(); } } /** * 獲取檔案快取路徑 * @return */ private String getCacheDirectory() { String cachePath = null; if(isSdcardAvailable()) { cachePath = SD_ROOT_PATH+IMG_CACH_DIR; }else { cachePath = DATA_ROOT_PATH+IMG_CACH_DIR; } return cachePath; } /** * * 清除40%的快取,這些快取被刪除的優先順序根據近期使用時間排列,越久沒被使用,越容易被刪除 */ private void removeCache(String dirPath) { File dir = new File(dirPath); File[] files = dir.listFiles(); if(files == null) { return; } double total_size = 0; for(File file : files) { total_size+=file.length(); } total_size = total_size/1024/1024; if(total_size > CACHE_SIZE || getSdCardFreeSpace() <= NEED_TO_CLEAN) { Log.i(TAG,"remove cache from sdcard cache..."); int removeFactor = (int) (files.length*0.4); Arrays.sort(files, new FileLastModifiedComparator()); for(int i = 0; i < removeFactor; i++) { files[i].delete(); } } } /** *獲取sd卡可用空間 */ private int getSdCardFreeSpace() { StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); double freespace = stat.getAvailableBlocks()*stat.getBlockSize(); return (int) (freespace/1024/1024); } /** *判斷sd卡是否可用 * @return */ private boolean isSdcardAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } /** * 將關鍵字轉化為檔名 */ private String convertKeyToFilename(String key) { if(key == null) { return ""; } return key.hashCode()+CACHE_TAIL; } /** * 更新檔案最後修改時間 */ private void updateFileModifiedTime(String path) { File file = new File(path); file.setLastModified(System.currentTimeMillis()); } private class FileLastModifiedComparator implements Comparator<File> { @Override public int compare(File lhs, File rhs) { if(lhs.lastModified() > rhs.lastModified()) { return 1; }else if(lhs.lastModified() == rhs.lastModified()) { return 0; }else { return -1; } } } }
package cn.edu.chd.myimageloader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import cn.edu.chd.utils.BitmapLruCacheHelper;
import cn.edu.chd.utils.BitmapUtils;
import cn.edu.chd.utils.FileCacheUtils;
/**
* @author Rowand jj
*下載圖片的工具類
* 通過downloadImage方法下載圖片,並將圖片儲存到快取中(使用執行緒池)。對下載得到的圖片交由一個回撥介面OnImageDownloadListener處理
* 通過showCacheImage方法獲取快取中的圖片
*/
public class ImageDownloader
{
/**
* 下載image的執行緒池
*/
private ExecutorService mImageThreadPool = null;
/**
* 檔案快取的工具類
*/
private FileCacheUtils fileCacheUtils = null;
/**
* 執行緒池中執行緒的數量
*/
private static final int THREAD_NUM = 2;
/**
* 縮圖的寬
*/
private static final int REQ_WIDTH = 90;
/**
* 縮圖的高
*/
private static final int REQ_HEIGHT = 90;
protected static final int DOWNLOAD = 1;
private Context context;
/**
* 構造器
* @param context
*/
public ImageDownloader(Context context)
{
this.context = context;
fileCacheUtils = new FileCacheUtils(context);
}
/**
* 下載一張圖片,先從記憶體快取中找,如果沒有則去檔案快取中找,如果還沒有就從網路中下載
* @param url
* @param listener
* @return
*/
public Bitmap downloadImage(final String url,final OnImageDownloadListener listener)
{
final String subUrl = url.replaceAll("[^\\w]", "");
Bitmap bitmap = showCacheBitmap(subUrl);
if(bitmap!=null)//快取中找到
{
return bitmap;
}else//快取中未找到,則開啟執行緒下載
{
// new AsyncTask<String, Void, Bitmap>()
// {
// @Override
// protected Bitmap doInBackground(String... params)
// {
// Bitmap bitmap = getImageFromUrl(url);//從網路上下載圖片
// fileCacheUtils.addBitmapToFile(subUrl,bitmap);//加到檔案快取
// BitmapLruCacheHelper.getInstance().addBitmapToMemCache(subUrl, bitmap);//加到記憶體快取
// return bitmap;
// }
// protected void onPostExecute(Bitmap result)
// {
// listener.onImageDownload(url, result);
// }
// }.execute(url);
final Handler handler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
if(msg.what == DOWNLOAD)
{
listener.onImageDownload(url,(Bitmap)msg.obj);//對下載後的圖片的操作交由listener實現類處理
}
}
};
getThreadPool().execute(new Runnable()//從執行緒池中獲取一個執行緒執行下載操作並將下載後的圖片加到檔案快取和記憶體快取
{
@Override
public void run()
{
Bitmap bitmap = getImageFromUrl(url);//從網路上下載圖片
Message msg = Message.obtain(handler, DOWNLOAD, bitmap);
msg.sendToTarget();//傳送訊息
//加到快取中
fileCacheUtils.addBitmapToFile(subUrl,bitmap);
BitmapLruCacheHelper.getInstance().addBitmapToMemCache(subUrl, bitmap);
}
});
}
return null;
}
/**
* 顯示快取中的圖片
* @param url
* @return
*/
public Bitmap showCacheBitmap(String url)
{
Bitmap bitmap = BitmapLruCacheHelper.getInstance().getBitmapFromMemCache(url);
if(bitmap!=null)//首先從記憶體快取中找
{
return bitmap;
}else
{
bitmap = fileCacheUtils.getBitmapFromFile(url);
if(bitmap!=null)//在檔案快取中找到
{
BitmapLruCacheHelper.getInstance().addBitmapToMemCache(url, bitmap);//加入記憶體快取
return bitmap;
}
}
return null;
}
/**
* 獲取執行緒池例項
*/
public ExecutorService getThreadPool()
{
if (mImageThreadPool == null)
{
synchronized (ExecutorService.class)
{
if (mImageThreadPool == null)
{
mImageThreadPool = Executors.newFixedThreadPool(THREAD_NUM);
}
}
}
return mImageThreadPool;
}
/**
* 從url中獲取bitmap
* @param url
* @return
*/
public Bitmap getImageFromUrl(String url)
{
HttpURLConnection conn = null;
try
{
URL target = new URL(url);
conn = (HttpURLConnection) target.openConnection();
conn.setReadTimeout(3000);
conn.setConnectTimeout(10 * 1000);
conn.setDoInput(true);
if (conn.getResponseCode() == 200)
{
InputStream is = conn.getInputStream();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int len = 0;
byte[] buf = new byte[1024];
while((len = is.read(buf))!=-1)
{
bout.write(buf, 0, len);
}
is.close();
byte[] data = bout.toByteArray();
return BitmapUtils.decodeSampledBitmapFromByteArray(data,REQ_WIDTH, REQ_HEIGHT);//返回的是壓縮後的縮圖
}
} catch (Exception e)
{
e.printStackTrace();
}
return null;
}
/**
* 取消當前的任務
*/
public synchronized void cancellTask()
{
if(mImageThreadPool != null)
{
mImageThreadPool.shutdownNow();
mImageThreadPool = null;
}
}
/**
*操作下載後的圖片的回撥介面
*/
public interface OnImageDownloadListener
{
void onImageDownload(String url,Bitmap bitmap);
}
}
6.GridView的介面卡:
當GridView滑動時停止下載圖片,GridView停止滑動時下載圖片。
package cn.edu.chd.myimageloader;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import cn.edu.chd.myimageloader.ImageDownloader.OnImageDownloadListener;
public class ImageAdapter extends BaseAdapter implements OnScrollListener
{
private GridView gridView;
private Context context;
private String[] imageThumUrls;
private ImageDownloader mImageDownloader;
private boolean isFirstEnter = true;
private int mFirstVisibleItem;
private int mVisibleItemCount;
public ImageAdapter(Context context,String[] imageThumUrls,GridView gridView)
{
this.context = context;
this.gridView = gridView;
this.imageThumUrls = imageThumUrls;
this.mImageDownloader = new ImageDownloader(context);
gridView.setOnScrollListener(this);
}
@Override
public int getCount()
{
return imageThumUrls.length;
}
@Override
public Object getItem(int position)
{
return imageThumUrls[position];
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
ImageView mImageView;
String imageUrl = imageThumUrls[position];
if(convertView == null)
{
mImageView = new ImageView(context);
}else
{
mImageView = (ImageView) convertView;
}
mImageView.setLayoutParams(new GridView.LayoutParams(90,90));
mImageView.setTag(imageUrl);
//只顯示快取圖片,如果快取中沒有則設定一張預設的圖片
Bitmap bitmap = mImageDownloader.showCacheBitmap(imageUrl.replaceAll("[^\\w]",""));
if(bitmap != null)
{
mImageView.setImageBitmap(bitmap);
}else
{
mImageView.setImageResource(R.drawable.ic_launcher);
}
return mImageView;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
{
if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE)//滑動停止時啟動下載圖片
{
showImage(mFirstVisibleItem, mVisibleItemCount);
}else
{
cancellTask();
}
}
/**
* 滾動時執行此方法
* 第一次進入會呼叫showImage顯示圖片
* */
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount)
{
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
if(isFirstEnter && visibleItemCount>0)
{
showImage(firstVisibleItem, visibleItemCount);
isFirstEnter = false;
}
}
/**
* 顯示圖片,先從快取中找,如果沒找到就開啟執行緒下載
* @param firstVisibleItem 第一個可見項的id
* @param visibleItemCount 可見項的總數
*/
private void showImage(int firstVisibleItem,int visibleItemCount)
{
for(int i = firstVisibleItem; i < firstVisibleItem+visibleItemCount;i++)
{
String mImageUrl = imageThumUrls[i];
final ImageView mImageView = (ImageView) gridView.findViewWithTag(mImageUrl);
mImageDownloader.downloadImage(mImageUrl, new OnImageDownloadListener()
{
@Override
public void onImageDownload(String url, Bitmap bitmap)
{
if(mImageView != null && bitmap!=null)
{
mImageView.setImageBitmap(bitmap);//下載後直接設定到view物件上
}
}
});
}
}
/**
* 取消下載任務
*/
public void cancellTask()
{
mImageDownloader.cancellTask();
}
}
7MainActivitypackage cn.edu.chd.myimageloader;
import cn.edu.chd.datasource.Images;
import android.app.Activity;
import android.os.Bundle;
import android.widget.GridView;
public class MainActivity extends Activity
{
private GridView gridView = null;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gridView = (GridView) findViewById(R.id.gridView);
gridView.setAdapter(new ImageAdapter(this, Images.imageThumbUrls, gridView));
}
}
8.佈局:<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<GridView
android:id="@+id/gridView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@android:color/transparent"
android:columnWidth="90dip"
android:horizontalSpacing="5dip"
android:numColumns="2"
android:verticalSpacing="5dip" >
</GridView>
</RelativeLayout>
9.許可權:<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
大功告成~