仿微信倒序加载图片
实现效果
这个一个仿微信倒序加载图片的照片墙,虽然不怎么难,但是牵扯到的技术点还是蛮多的,有必要一起学习下,总结下,这样以后妈妈在也不担心Android图片加载了。
网络请求和压缩
这里为了方便使用了volley,volley虽然是google官方推荐的网络加载库,但是对于图片加载的效果还是不错的,当然重量级的还是使用glide框架,这是google推荐的图片加载库。volley就不用多说了,郭神的博客一大把,可以去看看。来看看图片压缩:
public Bitmap compressSizeFromBmp(Bitmap image,int desWidth,int desHeight,Context context){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options = 100;
image.compress(Bitmap.CompressFormat.JPEG, 30, baos);
// * 特点: 通过设置采样率, 减少图片的像素, 达到对内存中的Bitmap进行压缩
while (baos.toByteArray().length / 1024 > 100) {
baos.reset();
options -= 10;
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
// ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
// Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;//只读边,不读内容
// Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
Bitmap bm = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.size(), newOpts);
if(!image.isRecycled()){
image.recycle();
}
//下次读内容,压缩大小
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
float hh = dip2px(context, desWidth);
float ww = dip2px(context, desHeight);
int be = 1;
if (w > h && w > ww) {
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;//设置采样率
newOpts.inPreferredConfig = Config.ARGB_8888;//该模式是默认的,可不设
newOpts.inPurgeable = true;// 同时设置才会有效
newOpts.inInputShareable = true;//。当系统内存不够时候图片自动被回收
bm = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.size(), newOpts);
return bm;
}
这里使用了对图片的像素和大小分别进行了压缩,代码也很常用,值得注意的是,讲inJustDecodeBounds 属性设置为false,则是读取图片的边界信息,是一个轻量级的操作。
双缓存
当构建volley的ImageLoader的时候:
mQueue = Volley.newRequestQueue(this);
myImageCache = new MyImageCache();
imageLoader = new ImageLoader(mQueue, myImageCache);
要指定一个图片缓存类,volley指定了使用L1 cache:
/**
* Constructs a new ImageLoader.
* @param queue The RequestQueue to use for making image requests.
* @param imageCache The cache to use as an L1 cache.
*/
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}
我们这里用双缓存,就是内存和硬盘缓存,用的都是Lru算法,即近期最少使用算法。这些都是很常见的东西,这里就不多赘述。
class MyImageCache implements ImageCache{
AsyncImageLoader asyncImageLoader= AsyncImageLoader.getInstance(MainActivity.this);
public Bitmap getBitmap(String url) {
Bitmap bitmap = null;
try {
bitmap = asyncImageLoader.getBitmapFromMem(url);
if(bitmap == null){
bitmap = asyncImageLoader.getBitmapFromDisk(url);
}
} catch (Exception e) {
Log.e("MyImageCache", "can't get bitmap from MyImageCache!");
}
return bitmap;
}
public void putBitmap(String url, Bitmap bitmap) {
//对图片进行压缩
bitmap = ImageUtil.compressBmpFromBmp(bitmap);
//put image into disk and memory.
asyncImageLoader.putBitmapinDisk(bitmap, url);
}
public void clearAllBitmap(){
//只要删除内存中的图片资源就行了
asyncImageLoader.clearBitmapInMemory();
asyncImageLoader.clearContext();
}
}
MyImageCache所做的事情就是,首先将网络获取到的图片进行压缩,然后分别放入硬盘和内存,取得时候也是,先从内存取,取不到再从硬盘取,再取不到就重新加载…而AsyncImageLoader类则对LruCache和DiskLruCache进行了一个封装。详细见demo。
异步倒序加载
下面来实现这个需求,首先,大量的图片加载首先要解决的OOM的问题,我们前面几步都是为了它再做准备,其次,解决的是用户体验问题,比如说用户快速滑动的时候是否会出现卡顿现象。那么这个demo,是对这方便做了很多的工作,比如,当用户快速滑动的时候,我们不加载图片,等到停止的时候,我们在去加载,而且只加载显示在用户面前的图片。那么如何做实现,先贴代码:
public MyAdapter() {
// TODO Auto-generated constructor stub
inflater = MainActivity.this.getLayoutInflater();
mGridView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
isTouch = false;
isFlying = true;
break;
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
isTouch = false;
isFlying = false;
if (adapter != null) {
adapter.notifyDataSetChanged();
}
break;
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
isFlying = false;
isTouch = true;
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
});
if(null == workThread){
workThread = new Thread(){
public void run() {
while(isLoad){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(! vector.isEmpty() ){
String url = vector.remove(vector.size()-1);
mHandler.sendMessage(mHandler.obtainMessage(UPDATE_IMAGE, url));
}
else{
try {
synchronized (workThread) {
workThread.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
workThread.start();
}
这个是GridView的Adapter的构造器,这里面我们多做了两步工作,第一个是监听了GridView的滚动事件,需知GridView是否是flying的状态,如果是就不加载,第二个是开启了一个线程,那么这个线程的作用是,控制加载图片的任务。
iv.setTag(Images.imageThumbUrls[position]);
ImageContainer image = ImageMaps.get(Images.imageThumbUrls[position]);
if(image != null){
if(image.getBitmap() != null){
iv.setImageBitmap(image.getBitmap());
}
}else{
if(!isFlying){
vector.add(Images.imageThumbUrls[position]);
synchronized (workThread) {
workThread.notify();
}
}
}
在Adapter的getView操作中,我们给vector添加了加载图片的任务,如果正常滚动或者静止状态下,我们都是去发送异步加载图片的指令的。并且打开workThread的堵塞状态,让其工作。那么如果是飞快的滑动了呢,那么静止下来,怎么去加载图片呢,可以看到我们在监听器里面调用了adapter.notifydataSetChange的方法。接下来就是加载了:
private static class MyHandler extends Handler{
private final WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
public void handleMessage(Message msg) {
if(msg.what == UPDATE_IMAGE){
try {
String url = (String) msg.obj;
SquareImageView iv = (SquareImageView ) mActivity.get().mGridView.findViewWithTag(url);
/**
* 在做一层缓存,让已经加载过的ImageView就不要重复请求网络了
*/
boolean isHasUrl = false;
Iterator<String> it = mActivity.get().ImageMaps.keySet().iterator();
while(it.hasNext()){
if(url.equals(it.next()))
{
isHasUrl = true;
break;
}
}
if(!isHasUrl)
{
mActivity.get().ImageMaps.put(url,
mActivity.get().imageLoader.get(
url,
ImageLoader.getImageListener(iv, R.drawable.aio_image_default, R.drawable.aio_image_fail))
);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这里就是调用了ImageLoader的方法去加载图片,加载成功后,将图片放到做好标记的imageview上面就Ok了,那么倒序是如何实现的呢,就是每次加载Vector的最后一个任务,这样就实现了倒序的效果,让线程堵塞(休眠)1秒钟的原因就是有这样的一个效果,不然,加载太快了,还是看不出来。另外我们还可以做成,随机加载,都是很容易实现的。
源码下载