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) {
- }
- }
- }
到这里基本就完成了。不懂可以给我留言。