异步批量加载网络图片,并使用二级缓存.
来源:互联网 发布:自动伴奏音源软件 编辑:程序博客网 时间:2024/06/04 18:01
1.实际需求
现在要在一个客户端的一个listview上显示一个列表,信息包括歌曲图像,歌曲的名称,和歌曲的演唱者.数据从百度音乐接口获取.
2.具体问题分析和所用的技术
2.1 由于下载图片属于耗时操作,所以应该在工作线程中完成.由于要加载很多的图片,如果加载一个就启动一个线程.这样就会造成启动过多的线程,从而会使得主线程出现卡顿(掉帧)
的现象.针对这种情况,我们可以采用把所有的加载图片的工作放到一个单工作线程中完成,而这个单工作线程通过轮循任务集合的方式批量加载图片.也即是在adapter的getView当中,一旦要显示图片的时候就在那里向任务集合中添加一个任务.而由工作线程去完成这个任务,之后再将当前完成的任务移除.
2.2 图片优化:
图片压缩:
图片国语庞大的时候,我们就需要对图片进行压缩.因为我们的手机不需要显示那么大的图片.
图片缓存:
分为内存缓存和文件缓存两种.
下面通过一个在listview中显示百度音乐接口中热歌榜的例子来说明以上知识的运用.
下面上代码: 一共4个类.然后再解析代码的执行流程,以及各个类的作用:
Music.java -> 封装Music信息的实体类
package com.fioman.my21_asynctaskright;public class Music{ /** * 歌曲对应的图片,相对比较大.这是一个网络路径 */ private String pic_big; /** * 歌曲对应的图片,相对比较小.这是一个网络路径 */ private String pic_small; /** * 歌曲的名字 */ private String title; /** * 歌曲的演唱者 */ private String author; public String getPic_big() { return pic_big; } public void setPic_big(String pic_big) { this.pic_big = pic_big; } public String getPic_small() { return pic_small; } public void setPic_small(String pic_small) { this.pic_small = pic_small; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @Override public String toString() { return "Music [pic_big=" + pic_big+",pic_small="+pic_small + ", title=" + title +",author=" + author + "]"; }}
MainActivity.java 主Activity类
package com.fioman.my21_asynctaskright;import java.util.ArrayList;import java.util.List;import android.os.Bundle;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentActivity;import android.support.v4.app.FragmentManager;import android.support.v4.app.FragmentPagerAdapter;import android.support.v4.view.ViewPager;public class MainActivity extends FragmentActivity{ private ViewPager vpPages; private Fragment fragment; private List<Fragment> fragments; private FragmentPagerAdapter pageAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化控件 vpPages = (ViewPager) findViewById(R.id.vp_music); //准备要显示的Fragmeng数据源 fragments = new ArrayList<Fragment>(); fragment = new HotMusicFragment(); fragments.add(fragment); //为ViewPager配置Adapter pageAdapter = new MainPagerAdapter(getSupportFragmentManager()); vpPages.setAdapter(pageAdapter); } class MainPagerAdapter extends FragmentPagerAdapter { public MainPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return fragments.get(position); } @Override public int getCount() { return fragments.size(); } }}
HotMusicFragment.java 主界面中要用到的Fragment类
package com.fioman.my21_asynctaskright;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;import java.util.ArrayList;import java.util.List;import org.xmlpull.v1.XmlPullParser;import android.os.AsyncTask;import android.os.Bundle;import android.support.v4.app.Fragment;import android.util.Log;import android.util.Xml;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ListView;/** * 热歌榜的fragment,也即是当主Activity显示这个碎片界面的时候会执行它里面的onCreateView()生命周期方法 */public class HotMusicFragment extends Fragment { private ListView lsvMusic; private List<Music> musics =new ArrayList<Music>(); private MusicAdapter adapter; /** * 该生命周期方法由系统自动调用,当viewPager需要获取Fragment的view对象时 */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) { View view = inflater.inflate(R.layout.music_list_fragment, null); //初始化Fragment中的控件 lsvMusic = (ListView) view.findViewById(R.id.lsv_music); //调用方法,获取热歌榜的数据.前面20首歌曲 getHotMusicList(0,20); return view; } /** * 获取新歌榜列表, 需要发送http请求.该操作需要在工作线程中执行.这里采用异步任务的方式. * @param offset 起始位置 * @param size 下载的歌曲的数量 */ private void getHotMusicList(final int offset, final int size) { AsyncTask< String, String, List<Music>> task = new AsyncTask<String, String, List<Music>>() { @Override protected List<Music> doInBackground(String... params) { try { //发送http请求,准备url String path = "http://tingapi.ting.baidu.com/v1/restserver/ting?from=qianqian&version=2.1.0&method=baidu.ting.billboard.billList&format=xml&type=2&offset="+offset+"&size="+size; URL url = new URL(path); //根据url打开获取httpUrlconnection HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //"GET" conn.setRequestMethod("GET"); //获取输入流 InputStream is = conn.getInputStream(); //根据输入流解析数据源,获取输入流 XmlPullParser parser = Xml.newPullParser(); parser.setInput(is, "utf-8"); //根据时间类型循环读取xml文件,解析出数据 int event = parser.getEventType(); Music music = null; while(event != XmlPullParser.END_DOCUMENT) { switch(event) { case XmlPullParser.START_TAG: String tagName = parser.getName(); if(tagName.equals("song")) { music = new Music(); musics.add(music); //Log.i("toLook", music.toString()); } else if(tagName.equals("pic_big")) { music.setPic_big(parser.nextText()); } else if(tagName.equals("pic_small")) { music.setPic_small(parser.nextText()); } else if(tagName.equals("title")) { music.setTitle(parser.nextText()); } else if(tagName.equals("author")) { music.setAuthor(parser.nextText()); } break; } //手动触发下一个事件 event = parser.next(); } return musics; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 当doInBackground()方法执行完毕之后,在主线程执行的方法 */ @Override protected void onPostExecute(List<Music> result) { Log.i("toLook", result.toString()); /** * 更新UI,也即是为ListView配置Adapter */ adapter = new MusicAdapter(getActivity(), lsvMusic,result); lsvMusic.setAdapter(adapter); Log.i("toLook", "onPostExecute()->执行完毕"); } }; task.execute(); }}
MusicAdapter.java listview配置的adapter
package com.fioman.my21_asynctaskright;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;import java.lang.ref.SoftReference;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.net.URLConnection;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import javax.crypto.spec.IvParameterSpec;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Bitmap.CompressFormat;import android.graphics.BitmapFactory.Options;import android.os.Handler;import android.os.Message;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.webkit.WebView.FindListener;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.ListView;import android.widget.TextView;public class MusicAdapter extends BaseAdapter{ /** * 声明缓存 */ private Map<String, SoftReference<Bitmap>> cache = new HashMap<String, SoftReference<Bitmap>>(); /** * 声明用于轮循的任务集合 */ private List<ImageLoaderTask> tasks = new ArrayList<MusicAdapter.ImageLoaderTask>(); /** * 声明一个工作线程,用于轮循任务集合 */ private Thread workThread; private boolean isLoop = true; /** * 消息类型,工作线程发送的消息.当其处理完图片之后发送这个消息给主线程.让其更新ImageView */ private static final int HANDLER_IAMGE_LOADED = 10; /** * Handler 用于处理工作线程发送的消息的Handler */ private Handler handler = new Handler() { public void handleMessage(Message msg) { switch(msg.what) { case HANDLER_IAMGE_LOADED: //更新ui,设置对应的图片 ImageLoaderTask task = (ImageLoaderTask) msg.obj; ImageView ivAlbum = (ImageView) lsvMusic.findViewWithTag(task.path); if(ivAlbum != null) //找到了对应的ImageView控件 { Bitmap bitmap = task.bitmap; if(bitmap != null) //图片加载处理成功 { // 更新UI ivAlbum.setImageBitmap(bitmap); /** * 先将其存入缓存当中 */ SoftReference<Bitmap> ref = new SoftReference<Bitmap>(bitmap); cache.put(task.path, ref); /** * 在存入文件当中 */ String filepath = task.path.substring(task.path.indexOf("/")); File file = new File(context.getCacheDir(),"images"+filepath); if(file.exists()) { //存入文件当中 try { FileOutputStream fos = new FileOutputStream(file); //将一个bitmap压缩到一个文件中的方法,先打开文件输出流.让后调用bitmap.compress()方法即可 bitmap.compress(CompressFormat.JPEG, 100, fos); } catch (FileNotFoundException e) { e.printStackTrace(); } } } } break; } } }; private Context context; private ListView lsvMusic; private List<Music> musics ; private LayoutInflater inflater; public MusicAdapter(Context context, ListView lsvMusic, List<Music> musics) { super(); this.context = context; this.lsvMusic = lsvMusic; this.musics = musics; this.inflater = LayoutInflater.from(context); this.lsvMusic = lsvMusic; //在构造方法中创建一个线程,用于轮循任务集合.加载图片 workThread = new Thread() { @Override public void run() { while(isLoop) { if(!tasks.isEmpty()) { //获取并移除当前第一个任务 ImageLoaderTask task = tasks.remove(0); String path = task.path; //根据网络路径访问网络 try { //1.准备URL URL url = new URL(path); //2.HTTPURLConnection HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //3.设置访问方式为"GET" conn.setRequestMethod("GET"); //4.设置网络输入流 InputStream is = conn.getInputStream(); //5.根据这个输入流,按照用户需要的大小进行图片压缩,这里压缩的高和宽分别设定为50,50 //5.1 从网络输入流中读取byte[],把输入流中的数据,写到字节输出流中 ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024*10];//字节缓存 int length = 0; while((length = is.read(buffer))!= -1) { bos.write(buffer, 0, length); bos.flush(); } //5.2 从输出流中得到byte[],描述一个完整的bitmap数据 byte[] bytes = bos.toByteArray(); bos.close(); //5.3 解析bitmap的byte[],获取图片的原始宽和高 Options opts = new Options(); //5.3 仅仅加载图片的bounds(边界属性),以计算压缩比例 opts.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts); int widScale = opts.outWidth / 50; int heiScale = opts.outHeight / 50; //5.4 压缩比例为宽和高的比例的最大值 int scale = widScale > heiScale ? widScale : heiScale; //5.5根据所获取的压缩比例,再次解析byte[] 获取压缩后的图片 opts.inJustDecodeBounds = false; opts.inSampleSize = scale; Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts); //封装到ImageLoadTask中 task.bitmap = bitmap; //发送消息给主线程: 将bitmap设置到ImageView当中 Message msg = new Message(); msg.what = HANDLER_IAMGE_LOADED; msg.obj = task; handler.sendMessage(msg); } catch (Exception e) { e.printStackTrace(); } } else //集合中没有任务的时候 { //调用wait方法,将会让当前线程进入等待 synchronized (workThread) { try { workThread.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }; workThread.start(); } @Override public int getCount() { return musics.size(); } @Override public Music getItem(int position) { return musics.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { //准备饺子皮 ViewHolder vHolder; if(convertView == null) { convertView = inflater.inflate(R.layout.music_list_item, null); vHolder = new ViewHolder(); vHolder.ivAlbum = (ImageView) convertView.findViewById(R.id.iv_album); vHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title); vHolder.tvSinger = (TextView) convertView.findViewById(R.id.tv_author); convertView.setTag(vHolder); } vHolder = (ViewHolder) convertView.getTag(); //准备饺子馅 Music music = musics.get(position); //包饺子,注意这里要用到handler,因为这里要显示图片的时候.图片还没有,图片只是一个地址.要去加载图片.而图片的加载是一个耗时 //任务,不可能在UI线程中去做.只有异步批量加载图片. vHolder.tvTitle.setText(music.getTitle()); vHolder.tvSinger.setText(music.getAuthor()); //通过图片的网络路径,下载图片.然后设置图片 //1.先获取图片的网络路径 String picSmallPath = music.getPic_small(); String picBigPath = music.getPic_big(); //2.把路径设置成ImageView的tag,因为在handler中需要使用这个imageView控件. vHolder.ivAlbum.setTag(picSmallPath); //3.先去内存缓存中寻找,是否已经加载过 SoftReference<Bitmap> ref = cache.get(picSmallPath); Bitmap bitmap = null; if(ref != null) //前面已经缓存过 { bitmap = ref.get(); if(bitmap != null) //已经缓存过,并且没有被销毁.直接从缓存中取出图片 { vHolder.ivAlbum.setImageBitmap(bitmap); return convertView; } } else //如果内存中没有缓存过,就去看看文件中是否有缓存. { String filename = picSmallPath.substring(picSmallPath.lastIndexOf("/")); File file = new File(context.getCacheDir(), "images"+filename); if(file.exists()) //如果文件是存在的就去根据文件解析图片 { bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); //根据文件路径去加载 vHolder.ivAlbum.setImageBitmap(bitmap); //把从文件中读取的bitmap存入内存缓存 cache.put(picSmallPath, new SoftReference<Bitmap>(bitmap)); return convertView; } } //如果内存缓存中和文件中都没有的时候,就通过任务轮循的方式去网络中加载 //1.创建ImageLoadTask对象,添加到任务集合中 ImageLoaderTask task = new ImageLoaderTask(); task.path = picSmallPath; tasks.add(task); //2.任务集合中有任务了,唤醒工作线程起来干活 synchronized (workThread) { workThread.notify(); } return convertView; } class ImageLoaderTask { String path; //图片路径,封装到task任务中,传递给工作线程.让工作线程根据这个路径,而获取一个bitmap格式图片. Bitmap bitmap; //根据图片路径下载到的图片 } class ViewHolder { ImageView ivAlbum; TextView tvTitle; TextView tvSinger; }}
下面是3个布局文件:
activity.main
<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" tools:context="com.fioman.my21_asycntask.MainActivity" tools:ignore="HardcodedText" > <TextView android:id="@+id/tv_title" android:layout_width="match_parent" android:layout_height="50dp" android:textSize="18sp" android:textColor="#000000" android:background="#234de1" android:gravity="center" android:text="百度音乐热歌榜" /> <android.support.v4.view.ViewPager android:id="@+id/vp_music" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/tv_title" /></RelativeLayout>
music_list_fragment.xml fragmeng布局文件.里面就是放了一个ListView
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:id="@+id/lsv_music" android:layout_width="match_parent" android:layout_height="match_parent" ></ListView></RelativeLayout>
music_list_item.xml 最后呈现listview的模板资源文件
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:id="@+id/lsv_music" android:layout_width="match_parent" android:layout_height="match_parent" ></ListView></RelativeLayout>
下面分析一下这个程序的执行流程:
1>程序启动 -> 在主Activity中会加载Fragment,然后Fragment在显示的时候会执行Fragment的
生命周期方法,由android容器来调用:
onCreateView()方法会得到执行:
2>在onCreateView()方法中,我们显示获取一个view对象,根据fragment模板,然后初始化
fragment中的listView控件;
然后调用getHotMusicList(0,20);
3>接着我们看看我们在getHotMusicList(0,20)方法中做了什么事情:
这里采用异步任务的方式:
在doInBackground()方法中我们做了什么:
向网络发送请求,然后解析xml,把从网络上获取的音乐数据封装到音乐实体对象中.
当doInBackground()执行完毕之后:
onPostExcute(List result)方法,会根据doInBackGround() 方法中的返回值
即获取的音乐数据列表,去在主线程中更新adapter
adapter = new MusicAdapter(getActivity(),lsvMusic,result);
lsvMusic.setAdapter(adapter);
4>而在MusicAdapter的构造方法中,我们启动一个工作线程去以轮循的方式去加载图片.
因为图片可能会从网络上获取,这样是耗时的.
5> 分析MusicAdapter中的代码,然后看一下他的执行流程:
首先在setAdapter(adapter)方法中的时候,
首先系统会调用getView方法获取view:
在getView中,我们显示歌曲名称和歌曲的演唱者,很容易.
因为音乐实体类中已经封装过了.但是图片的获取就没那么简单,因为实体类中封装的只是一个
网络路径地址,我们要根据这个网络路径地址去获取这张图片:
5.1>首先是判断内存的缓存是否有这张图片
//3.先去内存缓存中寻找,是否已经加载过
SoftReference ref = cache.get(picSmallPath);
Bitmap bitmap = null;
if(ref != null) //前面已经缓存过
{
bitmap = ref.get();
if(bitmap != null) //已经缓存过,并且没有被销毁.直接从缓存中取出图片
{
vHolder.ivAlbum.setImageBitmap(bitmap);
return convertView;
}
}
5.2 然后是从文件中查看是否之前已经保存过:
String filename = picSmallPath.substring(picSmallPath.lastIndexOf(“/”));
File file = new File(context.getCacheDir(), “images”+filename);
if(file.exists()) //如果文件是存在的就去根据文件解析图片
{
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); //根据文件路径去加载
vHolder.ivAlbum.setImageBitmap(bitmap);
//把从文件中读取的bitmap存入内存缓存
cache.put(picSmallPath, new SoftReference(bitmap));
return convertView;
}
5.3:如果内存中和文件中都没有,这个时候就要去网络中获取.这个时候采用任务集合的方式轮循获取:
这里根据图片的网络路径创建一个task,然后将task添加到list tasks 集合当中.
然后通知正在等待干活的workThread()起来加载图片了.
5.4: 工作线程加载完图片之后,把图片保存起来.而后把其保存到缓存当中,保存到文件缓存当中.
最后再显示图片:
- 异步批量加载网络图片,并使用二级缓存.
- Android批量图片加载经典系列——采用二级缓存、异步加载网络图片
- Android批量图片加载经典系列——使用LruCache、AsyncTask缓存并异步加载图片
- Android批量图片加载经典系列——使用xutil框架缓存、异步加载网络图片
- 异步批量加载图片
- SDWebImage图片二级缓存异步加载基本原理
- 使用Imageloader异步加载网络图片
- 使用Volley异步加载网络图片
- 异步加载网络图片
- 网络图片异步加载
- 异步加载网络图片
- 异步加载网络图片
- Android 异步加载网络图片并缓存到本地
- Android 异步加载网络图片并缓存到本地
- Android 异步加载网络图片并缓存到本地
- Android 异步加载网络图片并缓存到本地
- Android 图片异步加载 加载网络图片
- 使用异步加载图片
- MySQL 5.6 for Windows 解压缩版配置安装
- Hadoop1中如何确保HDFS的高可靠(HA)
- java测试Junit框架
- IOS中tableView的cell的复用机制-许笑欢
- python--循环、列表、字典、元组
- 异步批量加载网络图片,并使用二级缓存.
- 聚类(一)
- BZOJ4010: [HNOI2015]菜肴制作 解题报告
- correlation filter 目标跟踪论文集
- 杂谈
- Why you should embrace functional programming in Java 8
- hdu 1171
- MUi 框架ajax请求WebService接口
- Java 8: No more loops