1.图片压缩:

package com.example.imageloader;import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;import java.io.FileDescriptor;/*** Created by 1013369768 on 2017/5/27.* 压缩图片工具类*/public class ImageResizer {public Bitmap decodeFormStream(FileDescriptor fd, int reqWidth, int reqHeight){BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFileDescriptor(fd,null,options);options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);options.inJustDecodeBounds = false;return BitmapFactory.decodeFileDescriptor(fd,null,options);}/*** 计算采样率* @param options* @param reqWidth* @param reqHeight* @return*/public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,int reqHeight){if(reqWidth == 0 || reqHeight == 0){return 1;}int inSampleSize = 1;int width = options.outWidth;int height = options.outHeight;if(width>reqWidth || height>reqHeight){final int halfHeight = height / 2;final int halfWidth = width / 2;while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {inSampleSize *= 2;}}return inSampleSize;}
}

2.内存缓存和磁盘缓存的实现

    private ImageLoader(Context context){mContext = context.getApplicationContext();//获取当前进程最大可用内存,单位为KBint maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);//以当前进程最大可用内存的1/8作为内存缓存的总容量大小int cacheSize = maxMemory/8;mMemoryCache = new LruCache<String,Bitmap>(cacheSize){@Overrideprotected int sizeOf(String key, Bitmap bitmap) {//返回每个图片的大小,单位为KBreturn bitmap.getByteCount()/1024;}};File diskCacheDir = getDiskCacheDir(context,"bitmap");if(!diskCacheDir.exists()){diskCacheDir.mkdirs();}//判断磁盘剩余空间和磁盘缓存空间大小if(getUsableSpace(diskCacheDir) >DISK_CACHE_SIZE){try {mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);mIsDiskLruCacheCreated = true;} catch (IOException e) {e.printStackTrace();}}}

3.添加图片到内存缓存和磁盘缓存

    //添加到磁盘缓存private Bitmap loadBitmapFromHttp(String url,int reqWidth,int reqHeight) throws IOException {//通过检查当前线程的Looper是否和主线程的Looper相同if(Looper.myLooper() == Looper.getMainLooper()){throw new RuntimeException("不能再主线程访问网络");}if(mDiskLruCache == null){return null;}String key = hashKeyForUrl(url);DiskLruCache.Editor editor = mDiskLruCache.edit(key);if(editor!=null) {OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);if (downloadUrlToStream(url, outputStream)) {editor.commit();} else {editor.abort();}mDiskLruCache.flush();}return loadBitmapFormDiskCache(url,reqWidth,reqHeight);}----------//添加到内存缓存     private void addBitmapToMemoryCache(String key,Bitmap bitmap){if(getBitmapFormMemoryCache(key) == null){mMemoryCache.put(key, bitmap);}}

4.读取图片

    //从磁盘中获取图片private Bitmap loadBitmapFormDiskCache(String url,int reqWidth,int reqHeight) throws IOException {if(Looper.myLooper() == Looper.getMainLooper()){Log.d("TAG","不允许在主线程加载图片");}if(mDiskLruCache == null){return null;}Bitmap bitmap = null;String key = hashKeyForUrl(url);DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);if(snapshot !=null){FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);FileDescriptor fileDescriptor = fileInputStream.getFD();bitmap = mImageResizer.decodeFormStream(fileDescriptor,reqWidth,reqHeight);if(bitmap != null){addBitmapToMemoryCache(key,bitmap);}}return bitmap;}
----------//从内存中获取图片private Bitmap getBitmapFormMemoryCache(String key){return mMemoryCache.get(key);}

5.同步加载图片
同步加载接口需要在外部的子线程中调用,若在主线程中调用会抛出异常,通过loadBitmapFromHttp中进行检查,查看当前线程的Looper是否为主线程的Looper,判断当前线程是否为主线程。

    public Bitmap loadBitmap(String url,int reqWidth,int reqHeight){Bitmap bitmap = loadBitmapFormMemoryCache(url);Log.d("TAG","BITMAP:    "+bitmap);if(bitmap!=null){Log.d("TAG", "loadBitmapFromMemCache,url:" + url);return bitmap;}try {bitmap = loadBitmapFormDiskCache(url,reqWidth,reqHeight);if(bitmap !=null){Log.d("TAG", "loadBitmapFromDisk,url:" + url);return bitmap;}bitmap = loadBitmapFromHttp(url,reqWidth,reqHeight);Log.d("TAG", "loadBitmapFromHttp,url:" + url);} catch (IOException e) {e.printStackTrace();}if(bitmap == null && !mIsDiskLruCacheCreated){Log.d("TAG", "encounter error, DiskLruCache is not created.");bitmap = downloadBitmapFormUrl(url);}return bitmap;}

5.异步加载
这里采用线程池加载图片,由于随着列表的滑动会产生大量线程,故不能使用普通线程加载

    public void bindBitmap(final String url, final ImageView imageView, final int reqWidth, final int reqHeight){// View中的setTag(int key, Object tag)表示给View添加额外数据,key值必须是唯一值,必须使用应用程序资源中声明的id,故需要在res/values中新建xml文件进行添加imageView.setTag(TAG_KEY_URI,url);Bitmap bitmap = null;bitmap = loadBitmapFormMemoryCache(url);if(bitmap!=null){imageView.setImageBitmap(bitmap);return;}Runnable loadBitmapTask = new Runnable() {@Overridepublic void run() {Bitmap bitmap = loadBitmap(url, reqWidth, reqHeight);if(bitmap!=null){//把imageView、url、bitmap封装成一个LoaderBitmap对象LoaderResult result = new LoaderResult(imageView,url,bitmap);mMainHandler.obtainMessage(MESSAGE_POST_RESULT,result).sendToTarget();}}};THTREAD_POOL_EXECUTOR.execute(loadBitmapTask);}----------private static ThreadFactory sThreadFactory = new ThreadFactory() {// AtomicInteger可以在并发情况下达到原子化更新,避免使用了synchronizedprivate AtomicInteger mCount = new AtomicInteger(1);@Overridepublic Thread newThread(@NonNull Runnable r) {return new Thread(r,"ImageLoader#"+mCount.getAndIncrement());}};public static final Executor THTREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,KEEP_ALIVE, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(),sThreadFactory);

6.Handler实现
采用主线程的Looper构造Handler对象,这样既可以在主线程更新UI,也可以在其他线程更新UI

    private Handler mMainHandler = new Handler(Looper.getMainLooper()){@Overridepublic void handleMessage(Message msg) {LoaderResult result = (LoaderResult) msg.obj;ImageView imageView = result.imageView;String url = (String) imageView.getTag(TAG_KEY_URI);if(url.equals(result.url)){imageView.setImageBitmap(result.bitmap);}else {Log.d("TAG", "set image bitmap,but url has changed, ignored!");}}};

Handeler实例化时:
① 更新UI,Handler要使用主线程中的Looper:
I、在主线程中可以使用Handler handler = new Handler()构造Handler对象

Handler h = new Handler(){public void handleMessage (Message msg){//在这里进行UI更新  }
}

II、如果在其他线程构造Handler对象,则需要:Handler handler = new Handler(Looper.getMainLooper())

    private Handler mMainHandler = new Handler(Looper.getMainLooper()){@Overridepublic void handleMessage(Message msg) {//在这里处理UI更新}};

② 不更新UI,只处理消息:
I、 如果当前线程是主线程Handler handler = new Handler()

Handler h = new Handler(){public void handleMessage (Message msg){//在这里进行消息处理  }
}

II、如果当前线程不是主线程则使用Looper.prepare(); Handler handler = new Handler();Looper.loop()

class LooperThread extends Thread { public Handler mHandler;  public void run() {  Looper.prepare();  mHandler = new Handler() {  public void handleMessage(Message msg) {  //在这里处理消息}  };  Looper.loop();  } }  

③ 注意:若是实例化的时候用Looper.getMainLooper()就表示放到UI线程去处理。
因为只有UI线程默认调用过:Loop.prepare();Loop.loop();故处理消息时不再需要调用。在其他线程需要手动调用这两个,否则会报错。

由于ListView和GridView存在View的复用机制,移出屏幕的View会进入到RecycleBin当中,而新进入屏幕的元素则会从RecycleBin中获取View控件。当有新的元素进入界面时就会回调getView()方法,而在getView()方法中会开启异步请求从网络上获取图片,注意网络操作都是比较耗时的,也就是说当我们快速滑动ListView的时候就很有可能出现这样一种情况,某一个位置上的元素进入屏幕后开始从网络上请求图片,但是还没等图片下载完成,它就又被移出了屏幕。这种情况下会产生什么样的现象呢?根据GridView和ListView的工作原理,被移出屏幕的控件将会很快被新进入屏幕的元素重新利用起来,而如果在这个时候刚好前面发起的图片请求有了响应,就会将刚才位置上的图片显示到当前位置上,因为虽然它们位置不同,但都是共用的同一个ImageView实例,这样就出现了乱序的情况。但是还没完,新进入屏幕的元素它也会发起一条网络请求来获取当前位置的图片,等到图片下载完的时候会设置到同样的ImageView上面,因此就会出现先显示一张图片,然后又变成了另外一张图片的情况。

解决方案:在异步请求网络前我们可以使用ImageView的setTag (int key, Object tag)为当前位置的ImageView绑定相关的数据,这里可以传入URL作为tag,然后再异步请求网络后使用getTag(int key)获取URL,若相同则加载图片。
由于GridView和ListView中的ImageView控件都是重用的,移出屏幕的控件很快会被进入屏幕的图片重新利用起来,那么getView()方法就会再次得到执行,而在getView()方法中会为这个ImageView控件设置新的Tag,这样老的Tag就会被覆盖掉,于是这时当老的网络请求完成后调用getTag(int key)获取URL,就只能得到老的URL了,而我们判断只有ImageView的URL相同的时候才会加载图片,这样图片乱序的问题也就不存在了。

照片墙功能的实现
适配器:

public class ImageAdapter extends BaseAdapter {private LayoutInflater mInflater;private List<String> mUrList;private Drawable mDefaultBitmapDrawable;private ImageLoader mImageLoader;private boolean mIsGridViewIdle;public ImageAdapter(List<String> urList, Context context,Boolean mIsGridViewIdle){mImageLoader = ImageLoader.build(context);mUrList = urList;mInflater = LayoutInflater.from(context);mDefaultBitmapDrawable = ContextCompat.getDrawable(context,R.drawable.image_default);this.mIsGridViewIdle = mIsGridViewIdle;}@Overridepublic int getCount() {return mUrList.size();}@Overridepublic Object getItem(int position) {return mUrList.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder viewHolder = null;if(convertView == null){convertView = mInflater.inflate(R.layout.item,parent,false);viewHolder = new ViewHolder();viewHolder.imageView = (ImageView)convertView.findViewById(R.id.image1);convertView.setTag(viewHolder);}else {viewHolder = (ViewHolder) convertView.getTag();}ImageView imageView = viewHolder.imageView;final String tag = (String)imageView.getTag();final String url = (String)getItem(position);if(!url.equals(tag)){imageView.setImageDrawable(mDefaultBitmapDrawable);}if(mIsGridViewIdle){imageView.setTag(url);//设置压缩后为100*100的像素,填充ImageViewmImageLoader.bindBitmap(url,imageView,100,100);}return convertView;}private static class ViewHolder{public ImageView imageView;}
}

在getView中通过ImageLoader的bindBitmap方法异步加载图片,当用户刻意频繁
滑动就会瞬间产生大量的异步任务,这些异步任务会造成线程池的拥堵,并进行大量的UI更新操作,他们运行在主线程中,因此会造成一定的卡顿;可以考虑在滑动时停止加载图片,等滑动停止后再次加载。

    public void onScrollStateChanged(AbsListView view, int scrollState) {// 判断滑动事件是否结束,只触发一次  if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE){mIsGridViewIdle = true;mImageAdapter.notifyDataSetChanged();}else {mIsGridViewIdle = false;}}

demo下载

参考:

Android ListView异步加载图片乱序问题,原因分析及解决方案

Android 照片墙功能实现相关推荐

  1. Android照片墙完整版,完美结合 内存方案 LruCache 和 硬盘方案 DiskLruCache

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/34093441 在上一篇文章当中,我们学习了DiskLruCache的概念和基本用法 ...

  2. Android实现分享代码,Android 分享功能的实现代码

    Android 分享功能的实现代码 一个Activity中,取出设备上安装的所有支持分享动作的Activity,在grid中显示. 实例代码: /** * 分享activity */ public c ...

  3. Android P(3)---Android P 功能和 API

    Android P 功能和 API Android P 为用户和开发者引入众多新特性和新功能. 本文重点介绍面向开发者的新功能. 要了解新 API,请阅读 API 差异报告或访问 Android AP ...

  4. android支持平台,Android 平台功能

    Android 平台功能Android Platform Features 12/11/2019 本文内容 开发 Xamarin.Forms 适用于 Android 的应用程序需要 Visual St ...

  5. Android Q功能

    Finally, Android Q Beta 1 has been released and we are very eager to share what it has in store for ...

  6. Android P功能

    Android P Preview was launched a few months back and we're still in awe of the new features that hav ...

  7. android计算器开发论文,基于Android计算器功能的实现毕业设计论文

    <基于Android计算器功能的实现毕业设计论文.doc>由会员分享,可免费在线阅读全文,更多与<基于Android计算器功能的实现毕业设计论文>相关文档资源请在帮帮文库(ww ...

  8. android远程打电话,Android打电话功能 Android实战教程第三篇之简单实现拨打电话功能...

    想了解Android实战教程第三篇之简单实现拨打电话功能的相关内容吗,杨道龙在本文为您仔细讲解Android打电话功能的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:Android拨打电话 ...

  9. android 首字母 验证码,Android 验证码功能实现代码

    先给大家展示下效果图,如果大家感觉还不错,请参考实现代码 很简单的一个例子,点击刷新验证码,刷新当前显示的验证码,点击确定,如果输入的和显示的匹配,就会跳转到下一个界面中,这里只是实现了跳转,并没有进 ...

最新文章

  1. ubuntu18安装很慢_双硬盘装Win 10+Ubuntu 18.04双系统中的一些坑
  2. AWS — AWS Snow 系列
  3. Apache Iceberg 快速入门
  4. python token 访问控制_python 产生token及token验证
  5. telephone 为空 唯一索引_记一次线上唯一索引失效没有起效的场景
  6. 出现这些迹象,说明你面试可能没戏了
  7. idea项目结构树状展示_「软件项目管理入门」(26)如何做功能结构设计?
  8. 【Python3爬虫】破解时光网登录加密参数并实现模拟登录
  9. Linux新加硬盘添加一个新的LVM磁盘组
  10. VUE 生命周期图示
  11. 解决阿里云主机受到攻击的问题 2
  12. 【汇编语言】通用数据处理指令——算术运算类指令
  13. 转载自Kevin-Tong
  14. 华为太极magisk安装教程_教程:如何升级太极内部的应用
  15. 软件工程计算机专业基于SSM的汽车租赁管理系统 I的毕业论文
  16. threejs的shader材质 颜色混合函数mix
  17. 【原创】/Restarting/ Splay树 (普通平衡树 文艺平衡树 bzoj1895 poj 2580 SuperMemo 题解)
  18. Maven Helper 插件介绍
  19. iOS程序员为啥都爱用Mac电脑
  20. 苹果Mac系统彻底关闭开机声音duang~

热门文章

  1. 2022最新 zblog 资源站主题
  2. SQL Server数据库系列——锁和并发
  3. vb2005 rnd 函数的应用
  4. 电脑各种文件自动加密软件用哪个好?选择文件加密软件评判因素主要有哪些?
  5. 杰里之内置FM 灵敏度调节方法篇
  6. Braintree-国外支付对接(一)
  7. 在Linux使用脚本实现TC控制少数人的下载带宽,如果是网管,正好用得着。
  8. iOS APP 跳转 第三方 APP
  9. android开发渠道包,Android应用开发之gradle打不同渠道的包
  10. Android 多渠道打包 签名