1 在异步加载之前的代码的和普通加载代码一样,只需要在GirdView的Adapter的public View getView(int position, View convertView, ViewGroupparent)方法使用异步加载的方式返回ImageView。
2 如果能把加载过的图片给缓存起来,而不用每次都从sd卡上读取,这样效率应该会提高不少。所以可以先建一个缓存类,MemoryCache,为了能尽可能缓存,又尽可能的不抛出OOM的异常,可以使用SoftReference<Bitmap>,软引用来缓冲图片。MemoryCache类的代码如下:
[java]
view plain
copy
print
?
- /**
- * 缓存类(图片的路径和图片)
- */
- public class MemoryCache {
- private HashMap<String, SoftReference<Bitmap>> cache=new HashMap<String, SoftReference<Bitmap>>();
- //获取缓存图片
- public Bitmap get(String path) {
- if(cache.get(path)==null){
- return null;
- }else{
- return cache.get(path).get();
- }
- }
- //新增缓存
- public void put(String url,Bitmap bitmap) {
- cache.put(url, new SoftReference<Bitmap>(bitmap));
- }
- //清空缓存
- public void clear() {
- cache.clear();
- }
- }
3 弄完了缓存类,接下来就可以弄异步加载的类,这个类主要在维护一个Stack,每次向这个类请求一个ImageView时,就要传入对应ImageView和图片的路径。异步类首先会根据图片的路径先去缓存中查找是否有缓存对应的BItMap,如果有就把他放到ImageView返回,如果没有就把这个ImageView和图片路径放到Stack中,并唤醒加载图片的线程。而加载图片的线程(线程优先权低于UI线程),会无限循环查看Stack大小,如果为0,就进入等待。如果不为0,就依次出栈Stack中的元素进行处理。(感觉像生产者-消费者模式)。
3.1 接下来,就写这个异步类的变量和构造函数了:
[java]
view plain
copy
print
?
- private MemoryCache cache;
- private PhotosStack photosStack;
- private LoadImageThread loadImageThread;
- //用来保存每个月ImageView和对应应加载的图片
- private Map<ImageView, String> viewPath=Collections.synchronizedMap(new HashMap<ImageView, String>());
- public LoadImage(Activity activity) {
- super();
- cache=new MemoryCache();
- photosStack=new PhotosStack();
- loadImageThread=new LoadImageThread(activity);
- //设置异步加载线程优先权低于UI线程
- loadImageThread.setPriority(Thread.NORM_PRIORITY);
- }
其中MemoryCache是缓存类,PhotosStack类中维护一个Stack,LoadImageThread类是负责异步加载图片的。而viewPath这个变量是为了要保证GridView对应Position放对应的ImageView,如果没有这个变量,GridView中排列就会无序,在处理GridView的点击事件时候,就不好处理。而对viewPath的操作的异步,所以就需要线程安全咯。
PhotosStack代码如下:
[java]
view plain
copy
print
?
- //维护需要被填充的Image和对应图片路径的栈
- class PhotosStack{
- Stack<MyPhoto> stack=new Stack<LoadImage.MyPhoto>();
- //移除指定ImageView
- public void remove(ImageView iView) {
- for(int i=0;i<stack.size();i++){
- if(stack.get(i).imageView==iView){
- stack.remove(i);
- break;
- }
- }
- }
- }
其中MyPhoto是一个照片的实体类,有两个属性,图片的路径和对应的ImageView。
[java]
view plain
copy
print
?
- /**
- * 照片的实体类
- */
- class MyPhoto{
- public String path;
- public ImageView imageView;
- public MyPhoto(String path, ImageView imageView){
- this.path=path;
- this.imageView=imageView;
- }
- }
3.2 接下来就可以实现给Adapter的调用的方法loadImage(Stringpath,ImageView iv ),接收两参数——图片路径和对应的ImageView。这个方法,首先会去缓存中查看是否有缓存对应的图片,如果有就把它设给ImageView。如果没有,就先为ImageView设个默认图片,然后以同步块(锁为PhotosStack中的stack)的方式加入PhotosStack中的stack中,并唤醒加载图片的线程。最后还要判断下加载图片的线程是否已经被启动了,如果没有,就启动。
[java]
view plain
copy
print
?
- /**
- * 填充ImageView
- * @param activity
- * @param path 图片的路径
- * @param iv 被填充的ImageView
- */
- public void loadImage(String path,ImageView iv ) {
- viewPath.put(iv, path);
- Bitmap momeryBitmap=cache.get(path);
- if(momeryBitmap!=null){//有缓存这张图片
- iv.setImageBitmap(momeryBitmap);
- }else{//没有缓存
- iv.setImageResource(R.drawable.ic_launcher);
- //有可以这个ImageVIew还没出栈,所以需要先出栈
- photosStack.remove(iv);
- //由于photosStack中的stack,出栈入栈操作时不会在异步的,所以需要在同步块中完成出入栈,并以photosStack.stack作为锁
- synchronized (photosStack.stack) {
- photosStack.stack.push(new MyPhoto(path, iv));
- //唤醒持有锁的线程
- photosStack.stack.notifyAll();
- }
- }
- if(loadImageThread.getState()==Thread.State.NEW){
- loadImageThread.start();
- }
- }
3.3 最后可以实现异步加载图片的方法了,主要是循环判断stack中元素的数量,如果为0 ,说明所有的图片已经被加载完毕了,可以进入等待状态。如果不为0,说明还有图片等待加载,就依次出栈这些元素,依次加载图片,并放到缓存中。代码如下:
[java]
view plain
copy
print
?
- //异步加载图片的线程
- class LoadImageThread extends Thread{
- Activity activity;
- public LoadImageThread(Activity activity) {
- this.activity = activity;
- }
- @Override
- public void run() {
- while(true){
- try {
- if(photosStack.stack.size()>0){
- final MyPhoto photo;
- //得到栈顶的MyPhoto
- synchronized (photosStack.stack) {
- photo= photosStack.stack.pop();
- }
- cache.put(photo.path,getSmallBitmap(photo.path) );
- String ivPathString=viewPath.get(photo.imageView);
- if(ivPathString!=null&& ivPathString.equalsIgnoreCase(photo.path)){
- Runnable runableRunnable=new Runnable() {
- @Override
- public void run() {
- photo.imageView.setImageBitmap(getSmallBitmap(photo.path));
- }
- };
- activity.runOnUiThread(runableRunnable);
- }
- }
- if(photosStack.stack.size()==0){
- synchronized (photosStack.stack) {
- photosStack.stack.wait();
- }
- }
- } catch (InterruptedException e) {
- // TODO 自动生成的 catch 块
- e.printStackTrace();
- }
- }
- }
- }
其中缩小图片方法getSmallBitmap(String path),代码如下:
[java]
view plain
copy
print
?
- //缩小图片
- public Bitmap getSmallBitmap(String path) {
- Options options=new Options();
- options.inJustDecodeBounds=true;
- BitmapFactory.decodeFile(path,options);
- int REQUIRE_SIZE=80;
- int scare=1;
- while(true){
- if(options.outWidth<=REQUIRE_SIZE|| options.outHeight<=REQUIRE_SIZE){
- break;
- }else{
- options.outWidth=options.outWidth/2;
- options.outHeight=options.outHeight/2;
- scare++;
- }
- }
- Options newoptions=new Options();
- newoptions.inSampleSize=scare;
- return BitmapFactory.decodeFile(path, newoptions);
GridView加载大量图片卡顿问题