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

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

列一下网络上查到的一般做法:
1.使用BitmapFactory.Options对图片进行压缩
2.优化加载图片的adapter中的getView方法,使之尽可能少占用内存
3.使用异步加载图片的方式,使图片在页面加载后慢慢载入进来。

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

测试的效果图如下:

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

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

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

		}

	}

}


到这里基本就完成了。不懂可以给我留言。

 

 

你可能感兴趣的:(Android学习)