您的位置:首页 > 移动开发 > Android开发

Android学习笔记(二)之异步加载图片

2013-07-29 14:19 417 查看
最近在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;

	@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的布局文件如下:

<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;
	}

	@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、这是最重要的一部分代码,这就是异步加载图片的一个类,这里我就不解释了,代码中附有注释。代码如下:

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卡的实体类:

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)、缓存到手机内存的实体类:

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) {
		}
	}
}


到这里基本就完成了。不懂可以给我留言。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: