本文实例讲述了Android编程学习之异步加载图片的方法。分享给大家供大家参考,具体如下:
最近在android开发中碰到比较棘手的问题,就是加载图片内存溢出。我开发的是一个新闻应用,应用中用到大量的图片,一个界面中可能会有上百张图片。开发android应用的朋友可能或多或少碰到加载图片内存溢出问题,一般情况下,加载一张大图就会导致内存溢出,同样,加载多张图片内存溢出的概率也很高。
列一下网络上查到的一般做法:
1.使用BitmapFactory.Options对图片进行压缩
2.优化加载图片的adapter中的getView方法,使之尽可能少占用内存
3.使用异步加载图片的方式,使图片在页面加载后慢慢载入进来。
1、2步骤是必须做足的工作,但是对于大量图片的列表仍然无法解决内存溢出的问题,采用异步加载图片的方式才能有效解决图片加载内存溢出问题。
测试的效果图如下:

在这里我把主要的代码贴出来,给大家分享一下。
1、首先是MainActivity和activity_main.xml布局文件的代码。
(1)、MainActivity的代码如下:
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;@Overrideprotected 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的布局文件如下:
<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的代码:
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;}@Overridepublic int getCount() { return list.size();}@Overridepublic Object getItem(int position) { return list.get(position);}@Overridepublic long getItemId(int position) { return position;}@Overridepublic 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、这是最重要的一部分代码,这就是异步加载图片的一个类,这里我就不解释了,代码中附有注释。代码如下:
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进行decodeBitmapFactory.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卡的实体类:
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"); elsecacheDir = 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)、缓存到手机内存的实体类:
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、这个是输入输出流转换的类,及方法:
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程序设计有所帮助。