一.概述

本文来自"慕课网" 的学习,只是对代码做一下分析

图片异步加载有2种方式:  (多线程/线程池) 或者 用其实AsyncTask , 其实AsyncTask底层也是用的多线程.

使用缓存的好处是 , 提高流畅度, 节约流量.

二.代码

1.先看图片加载工具类

public class ImageLoader {
private ImageView mImageview;
private String mUrl;
//创建缓存
private LruCache<String, Bitmap> mCaches;
private ListView mListView;
private Set<NewsAsyncTask> mTask; public ImageLoader(ListView listView) {
mListView = listView;
mTask = new HashSet<>();
//获得最大的缓存空间
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//赋予缓存区最大缓存的四分之一进行缓存
int cacheSize = maxMemory / 4;
mCaches = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
//在每次存入缓存的时候调用
return value.getByteCount();
}
};
} //将图片通过url与bitmap的键值对形式添加到缓存中
public void addBitmapToCache(String url, Bitmap bitmap) {
if (getBitmapFromCache(url) == null) {
mCaches.put(url, bitmap);
}
} //通过缓存得到图片
public Bitmap getBitmapFromCache(String url) {
return mCaches.get(url);
} private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (mImageview.getTag().equals(mUrl))
mImageview.setImageBitmap((Bitmap) msg.obj);
}
}; //通过线程的方式去展示图片
public void showImageByThread(ImageView imageView, String url) {
mImageview = imageView;
mUrl = url;
new Thread() {
@Override
public void run() {
super.run();
Bitmap bitmap = getBitmapFromUrl(mUrl);
Message message = Message.obtain();
message.obj = bitmap;
mHandler.sendMessage(message);
}
}.start();
} //通过异步任务的方式去加载图片 public void showImageByAsyncTask(ImageView imageView, String url) {
//先从缓存中获取图片
Bitmap bitmap = getBitmapFromCache(url);
if (bitmap == null) {
imageView.setImageResource(R.mipmap.ic_launcher);
} else {
imageView.setImageBitmap(bitmap);
}
} private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap> { // private ImageView mImageView;
private String mUrl; public NewsAsyncTask( String url) {
// mImageview = imageView;
mUrl = url;
} @Override
protected Bitmap doInBackground(String... params) {
String url = params[0];
//从网络获取图片
Bitmap bitmap = getBitmapFromUrl(url);
//将图片加入缓存中
if (bitmap != null) {
addBitmapToCache(url, bitmap);
}
return bitmap;
} @Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
if (imageView!=null&&bitmap!=null){
imageView.setImageBitmap(bitmap);
}
mTask.remove(this);
}
} //滑动时加载图片
public void loadImages(int start, int end) {
for (int i = start; i < end; i++) {
String url = NewsAdapter.URLS[i];
//先从缓存中获取图片
Bitmap bitmap = getBitmapFromCache(url);
if (bitmap == null) {
NewsAsyncTask task = new NewsAsyncTask(url);
task.execute(url);
mTask.add(task);
} else {
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
imageView.setImageBitmap(bitmap);
}
}
} //停止时取消所有任务加载
public void cancelAllTasks(){
if (mTask!=null){
for (NewsAsyncTask task :mTask){
task.cancel(false);
}
}
}
//网络获取图片
private Bitmap getBitmapFromUrl(String urlString) {
Bitmap bitmap;
InputStream is = null;
try {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
is = new BufferedInputStream(connection.getInputStream());
bitmap = BitmapFactory.decodeStream(is);
connection.disconnect();
return bitmap;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert is != null;
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}

需要注意的几个部分:

<1

LruCache<String, Bitmap> mCaches 这是创建一个集合去存储缓存的图片,底层是HashMap实现的,其实和我们之前java中用到HashMap  弱引用/软引用比较类似, 但是
自2.3以后Android将更频繁的调用GC,导致软引用缓存的数据极易被释放。所以不能用之前的方式来缓存图片了,
LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用。如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。
我们可以在构造方法中,先得到当前应用所占总缓存大小,然后分出1/4用于存储图片,对应代码如下:
 //获得当前应用最大的缓存空间
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//赋予缓存区最大缓存的四分之一进行缓存
int cacheSize = maxMemory / 4;
mCaches = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
//在每次存入缓存的时候调用
return value.getByteCount();
}
};
<2
Set<NewsAsyncTask> mTask
定义一个Task任务集合,每个任务对应一个图片,当该图片被加载后要是否这个对应的task,对应代码如下:
 ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
if (imageView!=null&&bitmap!=null){
imageView.setImageBitmap(bitmap);
}
mTask.remove(this);

<3代码中根据 adapter 给每个图片设置 Tag 标识来获取图片,作用是: 避免 listview滚动时,由于convertView缓存造成图片错位显示, 对应代码如下: ----------> adapter代码后面给出

 private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (mImageview.getTag().equals(mUrl))//--------------根据adapter设置的tag获取
mImageview.setImageBitmap((Bitmap) msg.obj);
}
};

<4

当listview一边滚动,一边加载图片会造成一个问题,可能会出现暂时的卡顿现象,尽管这个现象是偶尔发生,如果网络不好情况下,会加重这种情况,这是为什么呢?

因为listview滚动时,对画面流畅度要求比较高
虽然异步加载是在新线程中执行的,并未阻塞UI线程,当加载好图片后,去更新UI线程
就会导致UI线程发生一次重绘,如果这次重绘正好发生在listview滚动的时候
就会导致这个listview滚动过程中卡顿一下, 这样用户体验大大滴不好

为解决该问题:

我们可以在 listview滚动停止后 才去加载可见项, listview滚动过程中,取消加载项(滚动过程中不加载图片数据)
就能解决这个问题, 因为我们在滚动过程中,其实我并不关心 滚动的内容,我只会关心 滚动停止后要显示的内容,所以这么做是 完全OK的.  对应代码如下:

 //滑动时加载图片 , 这里的 start 和end是 listview第一个和最后一个可见项
// adapter代码中会有详述
public void loadImages(int start, int end) {
for (int i = start; i < end; i++) {
String url = NewsAdapter.URLS[i];
//先从缓存中获取图片
Bitmap bitmap = getBitmapFromCache(url);
if (bitmap == null) {
NewsAsyncTask task = new NewsAsyncTask(url);
task.execute(url);
mTask.add(task);
} else {
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
imageView.setImageBitmap(bitmap);
}
}
}

以上就是图片工具类比较重点的部分 ,下面介绍adapter

常常的分割线-------------------------------------------------------------------------------------------------------------

public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{

    private List<NewsBeans> mList;
private LayoutInflater mInflater;
private ImageLoader mImageLoader;
private int mStart;
private int mEnd;
//创建静态数组保存图片的url地址
public static String[] URLS;
private boolean mFirstIn; public NewsAdapter(Context context, List<NewsBeans> data,ListView listView) {
mList = data;
mInflater = LayoutInflater.from(context);
mImageLoader = new ImageLoader(listView);
URLS = new String[data.size()];
for(int i=0;i<data.size();i++){
URLS[i] = data.get(i).iv_title;
}
listView.setOnScrollListener(this);
mFirstIn = true;
} @Override
public int getCount() {
return mList.size();
} @Override
public Object getItem(int position) {
return mList.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = mInflater.inflate(R.layout.list_item, null);
viewHolder.iv_title = (ImageView) convertView.findViewById(R.id.iv_icon);
viewHolder.tv_title = (TextView) convertView.findViewById(R.id.tv_title);
viewHolder.tv_content = (TextView) convertView.findViewById(R.id.tv_content);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
//设置默认显示的图片
viewHolder.iv_title.setImageResource(R.mipmap.ic_launcher);
//避免缓存影响使同一位置图片加载多次混乱
String url = mList.get(position).iv_title;
viewHolder.iv_title.setTag(url);
// new ImageLoader().showImageByThread(viewHolder.iv_title, url);
mImageLoader.showImageByAsyncTask(viewHolder.iv_title, url);
viewHolder.tv_content.setText(mList.get(position).tv_content);
viewHolder.tv_title.setText(mList.get(position).tv_title);
return convertView;
} @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if(scrollState==SCROLL_STATE_IDLE){
//加载可见项
mImageLoader.loadImages(mStart,mEnd);
}else{
//停止加载
mImageLoader.cancelAllTasks();
}
} @Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mStart = firstVisibleItem;
mEnd = firstVisibleItem + visibleItemCount;
if (mFirstIn && visibleItemCount>0){
mImageLoader.loadImages(mStart,mEnd);
}
} class ViewHolder {
private ImageView iv_title;
private TextView tv_title;
private TextView tv_content;
}
}

这里我们只需要注意3点

1.设置图片唯一标识Tag,避免图片错位显示

viewHolder.iv_title.setTag(url);

2.滚动过程中不加载图片,只有滚动停止后加载,下面重点分析2个方法

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if(scrollState==SCROLL_STATE_IDLE){
//加载可见项
mImageLoader.loadImages(mStart,mEnd);
}else{
//停止加载
mImageLoader.cancelAllTasks();
}
} @Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mStart = firstVisibleItem;
mEnd = firstVisibleItem + visibleItemCount;
if (mFirstIn && visibleItemCount>0){
mImageLoader.loadImages(mStart,mEnd);
}
}
onScrollStateChanged()该方法,在listview第一次出现的时候,并不会执行,注意"并不会执行".

oncroll()该方法在listview创建的时候就会执行,所以我们定义一个标志mFirstIn,在构造方法中初始为true,表示我们是第一次启动listview
对 mFirstIn && visibleItemCount>0 判断的解释:
"当前列表时第一次显示,并且listview的item已经展示出来",然后mFirstIn =false ,保证此段代码只有listview第一次显示的时候才会执行,之后滚动过程中不再执行 这里为什么要判断visibleItemCount>0 呢?
其实 oncroll会被多次回调的, 但是初次调用 
visibleItemCount 是 等于0的,也就是说此时item还未被加载
所以我们要判断 >0 跳过==0的情况,因为==0 item未被加载,当然 也就不会显示网络图片了
分割线--------------------------------------------------------------------------------------------------------------------------------------

最后是 MainActivity代码,比较简单不再分析
public class MainActivity extends AppCompatActivity {

    private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30";
private ListView mListView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.list_main);
new LoadImageAsync().execute(URL);
} //异步加载所有的网络数据
class LoadImageAsync extends AsyncTask<String, Void, List<NewsBeans>> { @Override
protected List<NewsBeans> doInBackground(String... params) {
return getJsonData(params[0]);
} @Override
protected void onPostExecute(List<NewsBeans> newsBeans) {
super.onPostExecute(newsBeans);
NewsAdapter adapter = new NewsAdapter(MainActivity.this, newsBeans,mListView);
mListView.setAdapter(adapter);
}
} //得到JSON数据
private List<NewsBeans> getJsonData(String url) {
List<NewsBeans> data = new ArrayList<>();
try {
//读取流得到json数据
String jsonList = readStream(new URL(url).openStream());
JSONObject jsonObject;
NewsBeans newsBeans;
try {
//解析JSON数据
jsonObject = new JSONObject(jsonList);
JSONArray jsonArray = jsonObject.getJSONArray("data");
for (int i = 0; i <= jsonArray.length(); i++) {
jsonObject = jsonArray.getJSONObject(i);
newsBeans = new NewsBeans();
newsBeans.iv_title = jsonObject.getString("picSmall");
newsBeans.tv_title = jsonObject.getString("name");
newsBeans.tv_content = jsonObject.getString("description");
data.add(newsBeans);
}
} catch (JSONException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return data;
} //读取输入流
private String readStream(InputStream is) {
InputStreamReader isr;
String result = "";
try {
String line;
//读取输入流
isr = new InputStreamReader(is, "utf-8");
//输入流转换成字节流
BufferedReader br = new BufferedReader(isr);
//逐行读取
while ((line = br.readLine()) != null) {
result += line;
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
}
以上代码只是做了内存缓存,如果想做二级缓存,可以用
DiskLruCache 硬盘缓存.最后来张图吧


												

android 之图片异步加载的更多相关文章

  1. Android ListView 图片异步加载和图片内存缓存

    开发Android应用经常需要处理图片的加载问题.因为图片一般都是存放在服务器端,需要联网去加载,而这又是一个比较耗时的过程,所以Android中都是通过开启一个异步线程去加载.为了增加用户体验,给用 ...

  2. Android图片异步加载框架Android-Universal-Image-Loader

    版权声明:本文为博主原创文章,未经博主允许不得转载. Android-Universal-Image-Loader是一个图片异步加载,缓存和显示的框架.这个框架已经被很多开发者所使用,是最常用的几个 ...

  3. Android 图片异步加载的体会,SoftReference已经不再适用

      在网络上搜索Android图片异步加载的相关文章,目前大部分提到的解决方案,都是采用Map<String, SoftReference<Drawable>>  这样软引用的 ...

  4. Android图片异步加载之Android-Universal-Image-Loader

    将近一个月没有更新博客了,由于这段时间以来准备毕业论文等各种事务缠身,一直没有时间和精力沉下来继续学习和整理一些东西.最近刚刚恢复到正轨,正好这两天看了下Android上关于图片异步加载的开源项目,就 ...

  5. Android图片异步加载之Android-Universal-Image-Loader(转)

    今天要介绍的是Github上一个使用非常广泛的图片异步加载库Android-Universal-Image-Loader,该项目的功能十分强大,可以说是我见过的目前功能最全.性能最优的图片异步加载解决 ...

  6. Android新浪微博客户端(七)——ListView中的图片异步加载、缓存

    原文出自:方杰|http://fangjie.info/?p=193转载请注明出处 最终效果演示:http://fangjie.sinaapp.com/?page_id=54 该项目代码已经放到git ...

  7. [置顶] Android图片异步加载之Android-Universal-Image-Loader

    将近一个月没有更新博客了,由于这段时间以来准备毕业论文等各种事务缠身,一直没有时间和精力沉下来继续学习和整理一些东西.最近刚刚恢复到正轨,正好这两天看了下Android上关于图片异步加载的开源项目,就 ...

  8. Android的ListView异步加载图片时,错位、重复、闪烁问题的分析及解决方法

    Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图 ...

  9. [android] 数据的异步加载和图片保存

    把从网络获取的图片数据保存在SD卡上, 先把权限都加上 网络权限 android.permission.INTERNET SD卡读写权限 android.permission.MOUNT_UNMOUN ...

随机推荐

  1. Markdown 基本语法(后面继续补充)

    1.1 Markdown 基础语法 有序内容和无序内容 有序内容:输入1.然后按tab键 无序内容:输入' * ' 或 ' - ' 然后后按tab键 字体的样式 *** 内容 *** 加粗加斜(中间没 ...

  2. String关键字

    关于String和new String()见我写的前一篇博客 String和new String()的区别 1.String的"+"运算 a.String str = " ...

  3. ZooKeeper实现同步屏障(Barrier)

    按照维基百科的解释:同步屏障(Barrier)是并行计算中的一种同步方法.对于一群进程或线程,程序中的一个同步屏障意味着任何线程/进程执行到此后必须等待,直到所有线程/进程都到达此点才可继续执行下文. ...

  4. Kubernetes容器云平台建设实践

    [51CTO.com原创稿件]Kubernetes是Google开源的一个容器编排引擎,它支持自动化部署.大规模可伸缩.应用容器化管理.伴随着云原生技术的迅速崛起,如今Kubernetes 事实上已经 ...

  5. File Compression and Archiving in linux (linux 中文件的归档)

    1. Compressing Files at the Shell Prompt Red Hat Enterprise Linux provides the bzip2, gzip, and zip ...

  6. Hive 系列(一)—— Hive 简介及核心概念

    一.简介 Hive 是一个构建在 Hadoop 之上的数据仓库,它可以将结构化的数据文件映射成表,并提供类 SQL 查询功能,用于查询的 SQL 语句会被转化为 MapReduce 作业,然后提交到 ...

  7. jmeter之beanshell使用

    beanshell官网:http://www.BeanShell.org/ 一.beanshell介绍 是一种完全符合Java语法规范的轻量级的脚本语言: 相当于一个小巧免费嵌入式的Java源代码解释 ...

  8. 写论文的第四天 Spark安装 使用sparkshell

    Spark分布式安装 Spark安装注意:需要和本机的hadoop版本对应 前往spark选择自己相对应的版本下载之后进行解压 命令:tar –zxf spark-2.4.0-bin-hadoop2. ...

  9. python学习之路(3)---列表

    列表定义: 列表就是一个数据的集合,列表是可以重复的,可以对存储的数据进行增删改查, 列表的写法: list_name = ['ljwang','wangwu'] 列表的嵌套 a = ['1',['2 ...

  10. 2018年蓝桥杯b组国赛真题

    1.标题:换零钞x星球的钞票的面额只有:100元,5元,2元,1元,共4种.小明去x星旅游,他手里只有2张100元的x星币,太不方便,恰好路过x星银行就去换零钱.小明有点强迫症,他坚持要求200元换出 ...