• 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;
    
    	@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) {
    		}
    	}
    }

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

    非著名程序员可能是东半球最好的技术分享公众号。每天,每周定时推送一些有关移动开发的原创文章和教程,微信号:smart_android。
  • 相关阅读:
    POST数据中有特殊符号导致数据丢失的解决方法
    Javascript中bind()方法的使用与实现
    Vue插件写、用详解(附demo)
    js自定义事件、DOM/伪DOM自定义事件
    对象可枚举和不可枚举属性
    js 数组 map方法
    Java源码学习(JDK 11)——java.util.concurrent.CopyOnWriteArrayList
    Java源码学习(JDK 11)——java.util.Collections
    Java源码学习(JDK 11)——java.util.Arrays
    Java源码学习(JDK 11)——java.lang.Collection
  • 原文地址:https://www.cnblogs.com/loonggg/p/4981839.html
Copyright © 2020-2023  润新知