Android编程学习之异步加载图片的方法

时间:2022-06-10 06:31:26

本文实例讲述了android编程学习之异步加载图片的方法。分享给大家供大家参考,具体如下:

最近在android开发中碰到比较棘手的问题,就是加载图片内存溢出。我开发的是一个新闻应用,应用中用到大量的图片,一个界面中可能会有上百张图片。开发android应用的朋友可能或多或少碰到加载图片内存溢出问题,一般情况下,加载一张大图就会导致内存溢出,同样,加载多张图片内存溢出的概率也很高。

列一下网络上查到的一般做法:

1.使用bitmapfactory.options对图片进行压缩
2.优化加载图片的adapter中的getview方法,使之尽可能少占用内存
3.使用异步加载图片的方式,使图片在页面加载后慢慢载入进来。

1、2步骤是必须做足的工作,但是对于大量图片的列表仍然无法解决内存溢出的问题,采用异步加载图片的方式才能有效解决图片加载内存溢出问题。

测试的效果图如下:

Android编程学习之异步加载图片的方法

在这里我把主要的代码贴出来,给大家分享一下。

1、首先是mainactivity和activity_main.xml布局文件的代码。

(1)、mainactivity的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package net.loonggg.test;
import java.util.list;
import net.loonggg.adapter.myadapter;
import net.loonggg.bean.menu;
import net.loonggg.util.httputil;
import net.loonggg.util.utils;
import android.app.activity;
import android.app.progressdialog;
import android.os.asynctask;
import android.os.bundle;
import android.view.window;
import android.widget.listview;
public class mainactivity extends activity {
 private listview lv;
 private myadapter adapter;
 private progressdialog pd;
 @override
 protected void oncreate(bundle savedinstancestate) {
  requestwindowfeature(window.feature_no_title);
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.activity_main);
  lv = (listview) findviewbyid(r.id.lv);
  pd = new progressdialog(this);
  pd.settitle("加载菜单");
  pd.setmessage("正在加载");
  adapter = new myadapter(this);
  new mytask().execute("1");
 }
 public class mytask extends asynctask<string, void, list<menu>> {
  @override
  protected void onpreexecute() {
   super.onpreexecute();
   pd.show();
  }
  @override
  protected void onpostexecute(list<menu> result) {
   super.onpostexecute(result);
   adapter.setdata(result);
   lv.setadapter(adapter);
   pd.dismiss();
  }
  @override
  protected list<menu> doinbackground(string... params) {
   string menuliststr = getlistdishesinfo(params[0]);
   return utils.getinstance().parsemenusjson(menuliststr);
  }
 }
 private string getlistdishesinfo(string sortid) {
  // url
  string url = httputil.base_url + "servlet/menuinfoservlet?sortid="
    + sortid + "&flag=1";
  // 查询返回结果
  return httputil.querystringforpost(url);
 }
}

(2)、activity_main.xml的布局文件如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
<linearlayout 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"
 android:background="#ffffff"
 android:orientation="vertical" >
 <listview
  android:id="@+id/lv"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content" >
 </listview>
</linearlayout>

2、这是自定义的listview的adapter的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package net.loonggg.adapter;
import java.util.list;
import net.loonggg.bean.menu;
import net.loonggg.test.r;
import net.loonggg.util.imageloader;
import android.app.activity;
import android.content.context;
import android.view.layoutinflater;
import android.view.view;
import android.view.viewgroup;
import android.widget.baseadapter;
import android.widget.imageview;
import android.widget.textview;
public class myadapter extends baseadapter {
 private list<menu> list;
 private context context;
 private activity activity;
 private imageloader imageloader;
 private viewholder viewholder;
 public myadapter(context context) {
  this.context = context;
  this.activity = (activity) context;
  imageloader = new imageloader(context);
 }
 public void setdata(list<menu> list) {
  this.list = list;
 }
 @override
 public int getcount() {
  return list.size();
 }
 @override
 public object getitem(int position) {
  return list.get(position);
 }
 @override
 public long getitemid(int position) {
  return position;
 }
 @override
 public view getview(int position, view convertview, viewgroup parent) {
  if (convertview == null) {
   convertview = layoutinflater.from(context).inflate(
     r.layout.listview_item, null);
   viewholder = new viewholder();
   viewholder.tv = (textview) convertview.findviewbyid(r.id.item_tv);
   viewholder.iv = (imageview) convertview.findviewbyid(r.id.item_iv);
   convertview.settag(viewholder);
  } else {
   viewholder = (viewholder) convertview.gettag();
  }
  viewholder.tv.settext(list.get(position).getdishes());
  imageloader.displayimage(list.get(position).getpicpath(), activity,
    viewholder.iv);
  return convertview;
 }
 private class viewholder {
  private imageview iv;
  private textview tv;
 }
}

3、这是最重要的一部分代码,这就是异步加载图片的一个类,这里我就不解释了,代码中附有注释。代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package net.loonggg.util;
import java.io.file;
import java.io.fileinputstream;
import java.io.filenotfoundexception;
import java.io.fileoutputstream;
import java.io.inputstream;
import java.io.outputstream;
import java.net.httpurlconnection;
import java.net.url;
import java.util.collections;
import java.util.map;
import java.util.stack;
import java.util.weakhashmap;
import net.loonggg.test.r;
import android.app.activity;
import android.content.context;
import android.graphics.bitmap;
import android.graphics.bitmapfactory;
import android.widget.imageview;
/**
 * 异步加载图片类
 *
 * @author loonggg
 *
 */
public class imageloader {
 // 手机中的缓存
 private memorycache memorycache = new memorycache();
 // sd卡缓存
 private filecache filecache;
 private picturesloader pictureloaderthread = new picturesloader();
 private picturesqueue picturesqueue = new picturesqueue();
 private map<imageview, string> imageviews = collections
   .synchronizedmap(new weakhashmap<imageview, string>());
 public imageloader(context context) {
  // 设置线程的优先级
  pictureloaderthread.setpriority(thread.norm_priority - 1);
  filecache = new filecache(context);
 }
 // 在找不到图片时,默认的图片
 final int stub_id = r.drawable.stub;
 public void displayimage(string url, activity activity, imageview imageview) {
  imageviews.put(imageview, url);
  bitmap bitmap = memorycache.get(url);
  if (bitmap != null)
   imageview.setimagebitmap(bitmap);
  else {// 如果手机内存缓存中没有图片,则调用任务队列,并先设置默认图片
   queuephoto(url, activity, imageview);
   imageview.setimageresource(stub_id);
  }
 }
 private void queuephoto(string url, activity activity, imageview imageview) {
  // 这imageview可能之前被用于其它图像。所以可能会有一些旧的任务队列。我们需要清理掉它们。
  picturesqueue.clean(imageview);
  picturetoload p = new picturetoload(url, imageview);
  synchronized (picturesqueue.picturestoload) {
   picturesqueue.picturestoload.push(p);
   picturesqueue.picturestoload.notifyall();
  }
  // 如果这个线程还没有启动,则启动线程
  if (pictureloaderthread.getstate() == thread.state.new)
   pictureloaderthread.start();
 }
 /**
  * 根据url获取相应的图片的bitmap
  *
  * @param url
  * @return
  */
 private bitmap getbitmap(string url) {
  file f = filecache.getfile(url);
  // 从sd卡缓存中获取
  bitmap b = decodefile(f);
  if (b != null)
   return b;
  // 否则从网络中获取
  try {
   bitmap bitmap = null;
   url imageurl = new url(url);
   httpurlconnection conn = (httpurlconnection) imageurl
     .openconnection();
   conn.setconnecttimeout(30000);
   conn.setreadtimeout(30000);
   inputstream is = conn.getinputstream();
   outputstream os = new fileoutputstream(f);
   // 将图片写到sd卡目录中去
   imageutil.copystream(is, os);
   os.close();
   bitmap = decodefile(f);
   return bitmap;
  } catch (exception ex) {
   ex.printstacktrace();
   return null;
  }
 }
 // 解码图像和缩放以减少内存的消耗
 private bitmap decodefile(file f) {
  try {
   // 解码图像尺寸
   bitmapfactory.options o = new bitmapfactory.options();
   o.injustdecodebounds = true;
   bitmapfactory.decodestream(new fileinputstream(f), null, o);
   // 找到正确的缩放值。这应该是2的幂。
   final int required_size = 70;
   int width_tmp = o.outwidth, height_tmp = o.outheight;
   int scale = 1;
   while (true) {
    if (width_tmp / 2 < required_size
      || height_tmp / 2 < required_size)
     break;
    width_tmp /= 2;
    height_tmp /= 2;
    scale *= 2;
   }
   // 设置恰当的insamplesize可以使bitmapfactory分配更少的空间
   // 用正确恰当的insamplesize进行decode
   bitmapfactory.options o2 = new bitmapfactory.options();
   o2.insamplesize = scale;
   return bitmapfactory.decodestream(new fileinputstream(f), null, o2);
  } catch (filenotfoundexception e) {
  }
  return null;
 }
 /**
  * picturetoload类(包括图片的地址和imageview对象)
  *
  * @author loonggg
  *
  */
 private class picturetoload {
  public string url;
  public imageview imageview;
  public picturetoload(string u, imageview i) {
   url = u;
   imageview = i;
  }
 }
 public void stopthread() {
  pictureloaderthread.interrupt();
 }
 // 存储下载的照片列表
 class picturesqueue {
  private stack<picturetoload> picturestoload = new stack<picturetoload>();
  // 删除这个imageview的所有实例
  public void clean(imageview image) {
   for (int j = 0; j < picturestoload.size();) {
    if (picturestoload.get(j).imageview == image)
     picturestoload.remove(j);
    else
     ++j;
   }
  }
 }
 // 图片加载线程
 class picturesloader extends thread {
  public void run() {
   try {
    while (true) {
     // 线程等待直到有图片加载在队列中
     if (picturesqueue.picturestoload.size() == 0)
      synchronized (picturesqueue.picturestoload) {
       picturesqueue.picturestoload.wait();
      }
     if (picturesqueue.picturestoload.size() != 0) {
      picturetoload phototoload;
      synchronized (picturesqueue.picturestoload) {
       phototoload = picturesqueue.picturestoload.pop();
      }
      bitmap bmp = getbitmap(phototoload.url);
      // 写到手机内存中
      memorycache.put(phototoload.url, bmp);
      string tag = imageviews.get(phototoload.imageview);
      if (tag != null && tag.equals(phototoload.url)) {
       bitmapdisplayer bd = new bitmapdisplayer(bmp,
         phototoload.imageview);
       activity activity = (activity) phototoload.imageview
         .getcontext();
       activity.runonuithread(bd);
      }
     }
     if (thread.interrupted())
      break;
    }
   } catch (interruptedexception e) {
    // 在这里允许线程退出
   }
  }
 }
 // 在ui线程中显示bitmap图像
 class bitmapdisplayer implements runnable {
  bitmap bitmap;
  imageview imageview;
  public bitmapdisplayer(bitmap bitmap, imageview imageview) {
   this.bitmap = bitmap;
   this.imageview = imageview;
  }
  public void run() {
   if (bitmap != null)
    imageview.setimagebitmap(bitmap);
   else
    imageview.setimageresource(stub_id);
  }
 }
 public void clearcache() {
  memorycache.clear();
  filecache.clear();
 }
}

4、紧接着是几个实体类,一个是缓存到sd卡中的实体类,还有一个是缓存到手机内存中的实体类。代码如下:

(1)、缓存到sd卡的实体类:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package net.loonggg.util;
import java.io.file;
import android.content.context;
public class filecache {
 private file cachedir;
 public filecache(context context) {
  // 找到保存缓存的图片目录
  if (android.os.environment.getexternalstoragestate().equals(
    android.os.environment.media_mounted))
   cachedir = new file(
     android.os.environment.getexternalstoragedirectory(),
     "newnews");
  else
   cachedir = context.getcachedir();
  if (!cachedir.exists())
   cachedir.mkdirs();
 }
 public file getfile(string url) {
  string filename = string.valueof(url.hashcode());
  file f = new file(cachedir, filename);
  return f;
 }
 public void clear() {
  file[] files = cachedir.listfiles();
  for (file f : files)
   f.delete();
 }
}

(2)、缓存到手机内存的实体类:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package net.loonggg.util;
import java.lang.ref.softreference;
import java.util.hashmap;
import android.graphics.bitmap;
public class memorycache {
 private hashmap<string, softreference<bitmap>> cache=new hashmap<string, softreference<bitmap>>();
 public bitmap get(string id){
  if(!cache.containskey(id))
   return null;
  softreference<bitmap> ref=cache.get(id);
  return ref.get();
 }
 public void put(string id, bitmap bitmap){
  cache.put(id, new softreference<bitmap>(bitmap));
 }
 public void clear() {
  cache.clear();
 }
}

5、这个是输入输出流转换的类,及方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package net.loonggg.util;
import java.io.inputstream;
import java.io.outputstream;
public class imageutil {
 public static void copystream(inputstream is, outputstream os) {
  final int buffer_size = 1024;
  try {
   byte[] bytes = new byte[buffer_size];
   for (;;) {
    int count = is.read(bytes, 0, buffer_size);
    if (count == -1)
     break;
    os.write(bytes, 0, count);
   }
  } catch (exception ex) {
  }
 }
}

到这里基本就完成了。

希望本文所述对大家android程序设计有所帮助。