android 之图片异步加载
一.概述
本文来自"慕课网" 的学习,只是对代码做一下分析
图片异步加载有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 之图片异步加载的更多相关文章
- Android ListView 图片异步加载和图片内存缓存
开发Android应用经常需要处理图片的加载问题.因为图片一般都是存放在服务器端,需要联网去加载,而这又是一个比较耗时的过程,所以Android中都是通过开启一个异步线程去加载.为了增加用户体验,给用 ...
- Android图片异步加载框架Android-Universal-Image-Loader
版权声明:本文为博主原创文章,未经博主允许不得转载. Android-Universal-Image-Loader是一个图片异步加载,缓存和显示的框架.这个框架已经被很多开发者所使用,是最常用的几个 ...
- Android 图片异步加载的体会,SoftReference已经不再适用
在网络上搜索Android图片异步加载的相关文章,目前大部分提到的解决方案,都是采用Map<String, SoftReference<Drawable>> 这样软引用的 ...
- Android图片异步加载之Android-Universal-Image-Loader
将近一个月没有更新博客了,由于这段时间以来准备毕业论文等各种事务缠身,一直没有时间和精力沉下来继续学习和整理一些东西.最近刚刚恢复到正轨,正好这两天看了下Android上关于图片异步加载的开源项目,就 ...
- Android图片异步加载之Android-Universal-Image-Loader(转)
今天要介绍的是Github上一个使用非常广泛的图片异步加载库Android-Universal-Image-Loader,该项目的功能十分强大,可以说是我见过的目前功能最全.性能最优的图片异步加载解决 ...
- Android新浪微博客户端(七)——ListView中的图片异步加载、缓存
原文出自:方杰|http://fangjie.info/?p=193转载请注明出处 最终效果演示:http://fangjie.sinaapp.com/?page_id=54 该项目代码已经放到git ...
- [置顶] Android图片异步加载之Android-Universal-Image-Loader
将近一个月没有更新博客了,由于这段时间以来准备毕业论文等各种事务缠身,一直没有时间和精力沉下来继续学习和整理一些东西.最近刚刚恢复到正轨,正好这两天看了下Android上关于图片异步加载的开源项目,就 ...
- Android的ListView异步加载图片时,错位、重复、闪烁问题的分析及解决方法
Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图 ...
- [android] 数据的异步加载和图片保存
把从网络获取的图片数据保存在SD卡上, 先把权限都加上 网络权限 android.permission.INTERNET SD卡读写权限 android.permission.MOUNT_UNMOUN ...
随机推荐
- 动态SQL查询
if+where: 用于查询操作,where标签可以智能判断是否添加and.or.where关键词 示例: <select id="findByParam" resultTy ...
- 电脑查询pico的mac
配置好adb或者sdk后, adb shell cat /sys/class/net/wlan0/address
- javaScript基础-02 javascript表达式和运算符
一.原始表达式 原始表达式是表达式的最小单位,不再包含其他表达式,包含常量,直接量,关键字和变量. 二.对象和数组的初始化表达式 对象和数组初始化表达式实际上是一个新创建的对象和数组. 三.函数表达式 ...
- ABAP:如何等待小数秒数
WAIT UP TO x SECONDS. 和CALL FUNCTION 'ENQUE_SLEEP'都只能支持整数的秒数(如果是非整数,则四舍五入),如果要WAIT非整数的描述,可以如下写法:
- Sqlmap过waf命令tamper各脚本的适用环境
0x00 相信很多小伙伴和我一样感同身受,站上明明有注入可是被万恶的WAF拦截了或者过滤了,这时候就需要用到SQLMAP强大的tamper了. 0x01 使用方法--tamper xxx.py apo ...
- Kafka 系列(三)—— Kafka 生产者详解
一.生产者发送消息的过程 首先介绍一下 Kafka 生产者发送消息的过程: Kafka 会将发送消息包装为 ProducerRecord 对象, ProducerRecord 对象包含了目标主题和要发 ...
- ATX agent+UIautomation2 自动化测试介绍
纯搬运贴,内容几乎来源于作者的几篇介绍文章,这里做了整合 目前ATX+UIautomator2 处于自动化界的浪口风尖,现在有幸终于有时间对ATX进行了粗浅的了解 为什么要用ATX ATX+UIaut ...
- React预备知识点
1.react中的状态提升 react的状态提升就是用户对子组件操作,子组件不改变自己的状态,而是通过自己的props把操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的 ...
- 解决php - Laravel rules preg_match(): No ending delimiter '/' found 问题
### 说明解决php - Laravel preg_match(): No ending delimiter '/' found 一.遇到问题的原因本正常添加如下 public function r ...
- 【JVM从小白学成大佬】3.深入解析强引用、软引用、弱引用、幻象引用
关于强引用.软引用.弱引用.幻象引用的区别,在很多公司的面试题中经常出现,可能有些小伙伴觉得这个知识点比较冷门,但其实大家在开发中经常用到,如new一个对象的时候就是强引用的应用. 在java语言中, ...