Android编程学习之异步加载图片的方法

本文实例讲述了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) {
  }
 }
}

到这里基本就完成了。

希望本文所述对大家Android程序设计有所帮助。

(0)

相关推荐

  • Android ListView异步加载图片方法详解

    本文实例讲述了Android ListView异步加载图片方法.分享给大家供大家参考,具体如下: 先说说这篇文章的优点把,开启线程异步加载图片,然后刷新UI显示图片,而且通过弱引用缓存网络加载的图片,节省了再次连接网络的开销. 这样做无疑是非常可取的方法,但是加载图片时仍然会感觉到轻微的卡屏现象,特别是listview里的item在进行快速滑动的时候. 我找了一下原因,可能是在listview快速滑动屏幕的时候划过的item太多 而且每次调用getView方法后就会异步的在过去某个时间内用han

  • 深入理解Android中的Handler异步通信机制

    一.问题:在Android启动后会在新进程里创建一个主线程,也叫UI线程(非线程安全)这个线程主要负责监听屏幕点击事件与界面绘制.当Application需要进行耗时操作如网络请求等,如直接在主线程进行容易发生ANR错误.所以会创建子线程来执行耗时任务,当子线程执行完毕需要通知UI线程并修改界面时,不可以直接在子线程修改UI,怎么办? 解决方法:Message Queue机制可以实现子线程与UI线程的通信. 该机制包括Handler.Message Queue.Looper.Handler可以把

  • Android 中使用 AsyncTask 异步读取网络图片

     1.新建Android工程AsyncLoadPicture 新建布局文件activity_main.xml主界面为一个GridView,还有其子项布局文件gridview_item.xml 2.功能主界面MainActivity.java,主代码如下 package com.example.asyncloadpicture; import java.util.ArrayList; import android.app.Activity; import android.content.Conte

  • Android中异步类AsyncTask用法总结

    本文总结分析了Android中异步类AsyncTask用法.分享给大家供大家参考,具体如下: 最近整理笔记的时候,看到有关AsyncTask不是很理解,重新疏导了一下,有在网上找了一些资料,个人不敢独享,一并发在这里与大家共勉 这里有两种解释的方法,各有侧重点: 第一种解释: Async Task 简介: AsyncTask的特点是任务在主线程之外运行,而回调方法是在主线程中执行,这就有效地避免了使用Handler带来的麻烦 AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Pa

  • Android异步加载数据和图片的保存思路详解

    把从网络获取的图片数据保存在SD卡上, 先把权限都加上 网络权限 android.permission.INTERNET SD卡读写权限 android.permission.MOUNT_UNMOUNT_FILESYSTEMS android.permission.WRITE_EXTERNAL_STORAGE 总体布局 写界面,使用ListView,创建条目的布局文件,水平摆放的ImageView TextView 在activity中获取到ListView对象,调用setAdapter()方法

  • android异步加载图片并缓存到本地实现方法

    在android项目中访问网络图片是非常普遍性的事情,如果我们每次请求都要访问网络来获取图片,会非常耗费流量,而且图片占用内存空间也比较大,图片过多且不释放的话很容易造成内存溢出.针对上面遇到的两个问题,首先耗费流量我们可以将图片第一次加载上面缓存到本地,以后如果本地有就直接从本地加载.图片过多造成内存溢出,这个是最不容易解决的,要想一些好的缓存策略,比如大图片使用LRU缓存策略或懒加载缓存策略.今天首先介绍一下本地缓存图片. 首先看一下异步加载缓存本地代码: 复制代码 代码如下: public

  • 全面总结Android中线程的异步处理方式

    一.概述 Handler . Looper .Message 这三者都与Android异步消息处理线程相关的概念.那么什么叫异步消息处理线程呢? 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环.若消息队列为空,线程则会阻塞等待. 说了这一堆,那么和Handler . Looper .Message有啥关系?其实Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断

  • Android实现图片缓存与异步加载

    ImageManager2这个类具有异步从网络下载图片,从sd读取本地图片,内存缓存,硬盘缓存,图片使用动画渐现等功能,已经将其应用在包含大量图片的应用中一年多,没有出现oom. Android程序常常会内存溢出,网上也有很多解决方案,如软引用,手动调用recycle等等.但经过我们实践发现这些方案,都没能起到很好的效果,我们的应用依然会出现很多oom,尤其我们的应用包含大量的图片.android3.0之后软引用基本已经失效,因为虚拟机只要碰到软引用就回收,所以带不来任何性能的提升. 我这里的解

  • Android实现图片异步加载及本地缓存

    在android项目中访问网络图片是非常普遍性的事情,如果我们每次请求都要访问网络来获取图片,会非常耗费流量,而且图片占用内存空间也比较大,图片过多且不释放的话很容易造成内存溢出.针对上面遇到的两个问题,首先耗费流量我们可以将图片第一次加载上面缓存到本地,以后如果本地有就直接从本地加载.图片过多造成内存溢出,这个是最不容易解决的,要想一些好的缓存策略,比如大图片使用LRU缓存策略或懒加载缓存策略,首先介绍一下本地缓存图片. 首先看一下异步加载缓存本地代码: public class AsyncB

  • Android实现图片异步加载并缓存到本地

    在android应用开发的时候,加载网络图片是一个非常重要的部分,很多图片不可能放在本地,所以就必须要从服务器或者网络读取图片. 软引用是一个现在非常流行的方法,用户体验比较好,不用每次都需要从网络下载图片,如果下载后就存到本地,下次读取时首先查看本地有没有,如果没有再从网络读取. 下面就分享一下异步加载网络图片的方法吧. FileCache.java import java.io.File; import android.content.Context; public class FileCa

随机推荐