zoukankan      html  css  js  c++  java
  • Android学习笔记(二)之异步加载图片

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

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

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

    测试的效果图如下:


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

    1、首先是MainActivity和activity_main.xml布局文件的代码。

    (1)、MainActivity的代码如下:

    1. package net.loonggg.test;  
    2.   
    3. import java.util.List;  
    4.   
    5. import net.loonggg.adapter.MyAdapter;  
    6. import net.loonggg.bean.Menu;  
    7. import net.loonggg.util.HttpUtil;  
    8. import net.loonggg.util.Utils;  
    9. import android.app.Activity;  
    10. import android.app.ProgressDialog;  
    11. import android.os.AsyncTask;  
    12. import android.os.Bundle;  
    13. import android.view.Window;  
    14. import android.widget.ListView;  
    15.   
    16. public class MainActivity extends Activity {  
    17.     private ListView lv;  
    18.     private MyAdapter adapter;  
    19.     private ProgressDialog pd;  
    20.   
    21.     @Override  
    22.     protected void onCreate(Bundle savedInstanceState) {  
    23.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
    24.         super.onCreate(savedInstanceState);  
    25.         setContentView(R.layout.activity_main);  
    26.         lv = (ListView) findViewById(R.id.lv);  
    27.         pd = new ProgressDialog(this);  
    28.         pd.setTitle("加载菜单");  
    29.         pd.setMessage("正在加载");  
    30.         adapter = new MyAdapter(this);  
    31.         new MyTask().execute("1");  
    32.     }  
    33.   
    34.     public class MyTask extends AsyncTask<String, Void, List<Menu>> {  
    35.   
    36.         @Override  
    37.         protected void onPreExecute() {  
    38.             super.onPreExecute();  
    39.             pd.show();  
    40.         }  
    41.   
    42.         @Override  
    43.         protected void onPostExecute(List<Menu> result) {  
    44.             super.onPostExecute(result);  
    45.             adapter.setData(result);  
    46.             lv.setAdapter(adapter);  
    47.             pd.dismiss();  
    48.         }  
    49.   
    50.         @Override  
    51.         protected List<Menu> doInBackground(String... params) {  
    52.             String menuListStr = getListDishesInfo(params[0]);  
    53.             return Utils.getInstance().parseMenusJSON(menuListStr);  
    54.         }  
    55.   
    56.     }  
    57.   
    58.     private String getListDishesInfo(String sortId) {  
    59.         // url  
    60.         String url = HttpUtil.BASE_URL + "servlet/MenuInfoServlet?sortId="  
    61.                 + sortId + "&flag=1";  
    62.         // 查询返回结果  
    63.         return HttpUtil.queryStringForPost(url);  
    64.     }  
    65.   
    66. }  


    (2)、activity_main.xml的布局文件如下:

    1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     android:background="#ffffff"  
    6.     android:orientation="vertical" >  
    7.   
    8.     <ListView  
    9.         android:id="@+id/lv"  
    10.         android:layout_width="fill_parent"  
    11.         android:layout_height="wrap_content" >  
    12.     </ListView>  
    13.   
    14. </LinearLayout>  


    2、这是自定义的ListView的adapter的代码:

    1. package net.loonggg.adapter;  
    2.   
    3. import java.util.List;  
    4.   
    5. import net.loonggg.bean.Menu;  
    6. import net.loonggg.test.R;  
    7. import net.loonggg.util.ImageLoader;  
    8. import android.app.Activity;  
    9. import android.content.Context;  
    10. import android.view.LayoutInflater;  
    11. import android.view.View;  
    12. import android.view.ViewGroup;  
    13. import android.widget.BaseAdapter;  
    14. import android.widget.ImageView;  
    15. import android.widget.TextView;  
    16.   
    17. public class MyAdapter extends BaseAdapter {  
    18.     private List<Menu> list;  
    19.     private Context context;  
    20.     private Activity activity;  
    21.     private ImageLoader imageLoader;  
    22.   
    23.     private ViewHolder viewHolder;  
    24.   
    25.     public MyAdapter(Context context) {  
    26.         this.context = context;  
    27.         this.activity = (Activity) context;  
    28.         imageLoader = new ImageLoader(context);  
    29.     }  
    30.   
    31.     public void setData(List<Menu> list) {  
    32.         this.list = list;  
    33.     }  
    34.   
    35.     @Override  
    36.     public int getCount() {  
    37.         return list.size();  
    38.     }  
    39.   
    40.     @Override  
    41.     public Object getItem(int position) {  
    42.         return list.get(position);  
    43.     }  
    44.   
    45.     @Override  
    46.     public long getItemId(int position) {  
    47.         return position;  
    48.     }  
    49.   
    50.     @Override  
    51.     public View getView(int position, View convertView, ViewGroup parent) {  
    52.         if (convertView == null) {  
    53.             convertView = LayoutInflater.from(context).inflate(  
    54.                     R.layout.listview_item, null);  
    55.             viewHolder = new ViewHolder();  
    56.             viewHolder.tv = (TextView) convertView.findViewById(R.id.item_tv);  
    57.             viewHolder.iv = (ImageView) convertView.findViewById(R.id.item_iv);  
    58.             convertView.setTag(viewHolder);  
    59.         } else {  
    60.             viewHolder = (ViewHolder) convertView.getTag();  
    61.         }  
    62.         viewHolder.tv.setText(list.get(position).getDishes());  
    63.         imageLoader.DisplayImage(list.get(position).getPicPath(), activity,  
    64.                 viewHolder.iv);  
    65.         return convertView;  
    66.     }  
    67.   
    68.     private class ViewHolder {  
    69.         private ImageView iv;  
    70.         private TextView tv;  
    71.     }  
    72.   
    73. }  


    3、这是最重要的一部分代码,这就是异步加载图片的一个类,这里我就不解释了,代码中附有注释。代码如下:

    1. package net.loonggg.util;  
    2.   
    3. import java.io.File;  
    4. import java.io.FileInputStream;  
    5. import java.io.FileNotFoundException;  
    6. import java.io.FileOutputStream;  
    7. import java.io.InputStream;  
    8. import java.io.OutputStream;  
    9. import java.net.HttpURLConnection;  
    10. import java.net.URL;  
    11. import java.util.Collections;  
    12. import java.util.Map;  
    13. import java.util.Stack;  
    14. import java.util.WeakHashMap;  
    15.   
    16. import net.loonggg.test.R;  
    17. import android.app.Activity;  
    18. import android.content.Context;  
    19. import android.graphics.Bitmap;  
    20. import android.graphics.BitmapFactory;  
    21. import android.widget.ImageView;  
    22.   
    23. /** 
    24.  * 异步加载图片类 
    25.  *  
    26.  * @author loonggg 
    27.  *  
    28.  */  
    29. public class ImageLoader {  
    30.     // 手机中的缓存  
    31.     private MemoryCache memoryCache = new MemoryCache();  
    32.     // sd卡缓存  
    33.     private FileCache fileCache;  
    34.     private PicturesLoader pictureLoaderThread = new PicturesLoader();  
    35.     private PicturesQueue picturesQueue = new PicturesQueue();  
    36.     private Map<ImageView, String> imageViews = Collections  
    37.             .synchronizedMap(new WeakHashMap<ImageView, String>());  
    38.   
    39.     public ImageLoader(Context context) {  
    40.         // 设置线程的优先级  
    41.         pictureLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);  
    42.         fileCache = new FileCache(context);  
    43.     }  
    44.   
    45.     // 在找不到图片时,默认的图片  
    46.     final int stub_id = R.drawable.stub;  
    47.   
    48.     public void DisplayImage(String url, Activity activity, ImageView imageView) {  
    49.         imageViews.put(imageView, url);  
    50.         Bitmap bitmap = memoryCache.get(url);  
    51.         if (bitmap != null)  
    52.             imageView.setImageBitmap(bitmap);  
    53.         else {// 如果手机内存缓存中没有图片,则调用任务队列,并先设置默认图片  
    54.             queuePhoto(url, activity, imageView);  
    55.             imageView.setImageResource(stub_id);  
    56.         }  
    57.     }  
    58.   
    59.     private void queuePhoto(String url, Activity activity, ImageView imageView) {  
    60.         // 这ImageView可能之前被用于其它图像。所以可能会有一些旧的任务队列。我们需要清理掉它们。  
    61.         picturesQueue.Clean(imageView);  
    62.         PictureToLoad p = new PictureToLoad(url, imageView);  
    63.         synchronized (picturesQueue.picturesToLoad) {  
    64.             picturesQueue.picturesToLoad.push(p);  
    65.             picturesQueue.picturesToLoad.notifyAll();  
    66.         }  
    67.   
    68.         // 如果这个线程还没有启动,则启动线程  
    69.         if (pictureLoaderThread.getState() == Thread.State.NEW)  
    70.             pictureLoaderThread.start();  
    71.     }  
    72.   
    73.     /** 
    74.      * 根据url获取相应的图片的Bitmap 
    75.      *  
    76.      * @param url 
    77.      * @return 
    78.      */  
    79.     private Bitmap getBitmap(String url) {  
    80.         File f = fileCache.getFile(url);  
    81.   
    82.         // 从SD卡缓存中获取  
    83.         Bitmap b = decodeFile(f);  
    84.         if (b != null)  
    85.             return b;  
    86.   
    87.         // 否则从网络中获取  
    88.         try {  
    89.             Bitmap bitmap = null;  
    90.             URL imageUrl = new URL(url);  
    91.             HttpURLConnection conn = (HttpURLConnection) imageUrl  
    92.                     .openConnection();  
    93.             conn.setConnectTimeout(30000);  
    94.             conn.setReadTimeout(30000);  
    95.             InputStream is = conn.getInputStream();  
    96.             OutputStream os = new FileOutputStream(f);  
    97.             // 将图片写到sd卡目录中去  
    98.             ImageUtil.CopyStream(is, os);  
    99.             os.close();  
    100.             bitmap = decodeFile(f);  
    101.             return bitmap;  
    102.         } catch (Exception ex) {  
    103.             ex.printStackTrace();  
    104.             return null;  
    105.         }  
    106.     }  
    107.   
    108.     // 解码图像和缩放以减少内存的消耗  
    109.     private Bitmap decodeFile(File f) {  
    110.         try {  
    111.             // 解码图像尺寸  
    112.             BitmapFactory.Options o = new BitmapFactory.Options();  
    113.             o.inJustDecodeBounds = true;  
    114.             BitmapFactory.decodeStream(new FileInputStream(f), null, o);  
    115.   
    116.             // 找到正确的缩放值。这应该是2的幂。  
    117.             final int REQUIRED_SIZE = 70;  
    118.             int width_tmp = o.outWidth, height_tmp = o.outHeight;  
    119.             int scale = 1;  
    120.             while (true) {  
    121.                 if (width_tmp / 2 < REQUIRED_SIZE  
    122.                         || height_tmp / 2 < REQUIRED_SIZE)  
    123.                     break;  
    124.                 width_tmp /= 2;  
    125.                 height_tmp /= 2;  
    126.                 scale *= 2;  
    127.             }  
    128.   
    129.             // 设置恰当的inSampleSize可以使BitmapFactory分配更少的空间  
    130.             // 用正确恰当的inSampleSize进行decode  
    131.             BitmapFactory.Options o2 = new BitmapFactory.Options();  
    132.             o2.inSampleSize = scale;  
    133.             return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);  
    134.         } catch (FileNotFoundException e) {  
    135.         }  
    136.         return null;  
    137.     }  
    138.   
    139.     /** 
    140.      * PictureToLoad类(包括图片的地址和ImageView对象) 
    141.      *  
    142.      * @author loonggg 
    143.      *  
    144.      */  
    145.     private class PictureToLoad {  
    146.         public String url;  
    147.         public ImageView imageView;  
    148.   
    149.         public PictureToLoad(String u, ImageView i) {  
    150.             url = u;  
    151.             imageView = i;  
    152.         }  
    153.     }  
    154.   
    155.     public void stopThread() {  
    156.         pictureLoaderThread.interrupt();  
    157.     }  
    158.   
    159.     // 存储下载的照片列表  
    160.     class PicturesQueue {  
    161.         private Stack<PictureToLoad> picturesToLoad = new Stack<PictureToLoad>();  
    162.   
    163.         // 删除这个ImageView的所有实例  
    164.         public void Clean(ImageView image) {  
    165.             for (int j = 0; j < picturesToLoad.size();) {  
    166.                 if (picturesToLoad.get(j).imageView == image)  
    167.                     picturesToLoad.remove(j);  
    168.                 else  
    169.                     ++j;  
    170.             }  
    171.         }  
    172.     }  
    173.   
    174.     // 图片加载线程  
    175.     class PicturesLoader extends Thread {  
    176.         public void run() {  
    177.             try {  
    178.                 while (true) {  
    179.                     // 线程等待直到有图片加载在队列中  
    180.                     if (picturesQueue.picturesToLoad.size() == 0)  
    181.                         synchronized (picturesQueue.picturesToLoad) {  
    182.                             picturesQueue.picturesToLoad.wait();  
    183.                         }  
    184.                     if (picturesQueue.picturesToLoad.size() != 0) {  
    185.                         PictureToLoad photoToLoad;  
    186.                         synchronized (picturesQueue.picturesToLoad) {  
    187.                             photoToLoad = picturesQueue.picturesToLoad.pop();  
    188.                         }  
    189.                         Bitmap bmp = getBitmap(photoToLoad.url);  
    190.                         // 写到手机内存中  
    191.                         memoryCache.put(photoToLoad.url, bmp);  
    192.                         String tag = imageViews.get(photoToLoad.imageView);  
    193.                         if (tag != null && tag.equals(photoToLoad.url)) {  
    194.                             BitmapDisplayer bd = new BitmapDisplayer(bmp,  
    195.                                     photoToLoad.imageView);  
    196.                             Activity activity = (Activity) photoToLoad.imageView  
    197.                                     .getContext();  
    198.                             activity.runOnUiThread(bd);  
    199.                         }  
    200.                     }  
    201.                     if (Thread.interrupted())  
    202.                         break;  
    203.                 }  
    204.             } catch (InterruptedException e) {  
    205.                 // 在这里允许线程退出  
    206.             }  
    207.         }  
    208.     }  
    209.   
    210.     // 在UI线程中显示Bitmap图像  
    211.     class BitmapDisplayer implements Runnable {  
    212.         Bitmap bitmap;  
    213.         ImageView imageView;  
    214.   
    215.         public BitmapDisplayer(Bitmap bitmap, ImageView imageView) {  
    216.             this.bitmap = bitmap;  
    217.             this.imageView = imageView;  
    218.         }  
    219.   
    220.         public void run() {  
    221.             if (bitmap != null)  
    222.                 imageView.setImageBitmap(bitmap);  
    223.             else  
    224.                 imageView.setImageResource(stub_id);  
    225.         }  
    226.     }  
    227.   
    228.     public void clearCache() {  
    229.         memoryCache.clear();  
    230.         fileCache.clear();  
    231.     }  
    232.   
    233. }  

    4、紧接着是几个实体类,一个是缓存到SD卡中的实体类,还有一个是缓存到手机内存中的实体类。代码如下:

    (1)、缓存到sd卡的实体类:

    1. package net.loonggg.util;  
    2.   
    3. import java.io.File;  
    4. import android.content.Context;  
    5.   
    6. public class FileCache {  
    7.   
    8.     private File cacheDir;  
    9.   
    10.     public FileCache(Context context) {  
    11.         // 找到保存缓存的图片目录  
    12.         if (android.os.Environment.getExternalStorageState().equals(  
    13.                 android.os.Environment.MEDIA_MOUNTED))  
    14.             cacheDir = new File(  
    15.                     android.os.Environment.getExternalStorageDirectory(),  
    16.                     "newnews");  
    17.         else  
    18.             cacheDir = context.getCacheDir();  
    19.         if (!cacheDir.exists())  
    20.             cacheDir.mkdirs();  
    21.     }  
    22.   
    23.     public File getFile(String url) {  
    24.         String filename = String.valueOf(url.hashCode());  
    25.         File f = new File(cacheDir, filename);  
    26.         return f;  
    27.   
    28.     }  
    29.   
    30.     public void clear() {  
    31.         File[] files = cacheDir.listFiles();  
    32.         for (File f : files)  
    33.             f.delete();  
    34.     }  
    35.   
    36. }  


    (2)、缓存到手机内存的实体类:

    1. package net.loonggg.util;  
    2.   
    3. import java.lang.ref.SoftReference;  
    4. import java.util.HashMap;  
    5. import android.graphics.Bitmap;  
    6.   
    7. public class MemoryCache {  
    8.     private HashMap<String, SoftReference<Bitmap>> cache=new HashMap<String, SoftReference<Bitmap>>();  
    9.       
    10.     public Bitmap get(String id){  
    11.         if(!cache.containsKey(id))  
    12.             return null;  
    13.         SoftReference<Bitmap> ref=cache.get(id);  
    14.         return ref.get();  
    15.     }  
    16.       
    17.     public void put(String id, Bitmap bitmap){  
    18.         cache.put(id, new SoftReference<Bitmap>(bitmap));  
    19.     }  
    20.   
    21.     public void clear() {  
    22.         cache.clear();  
    23.     }  
    24. }  


    5、这个是输入输出流转换的类,及方法:

    1. package net.loonggg.util;  
    2.   
    3. import java.io.InputStream;  
    4. import java.io.OutputStream;  
    5.   
    6. public class ImageUtil {  
    7.     public static void CopyStream(InputStream is, OutputStream os) {  
    8.         final int buffer_size = 1024;  
    9.         try {  
    10.             byte[] bytes = new byte[buffer_size];  
    11.             for (;;) {  
    12.                 int count = is.read(bytes, 0, buffer_size);  
    13.                 if (count == -1)  
    14.                     break;  
    15.                 os.write(bytes, 0, count);  
    16.             }  
    17.               
    18.         } catch (Exception ex) {  
    19.         }  
    20.     }  
    21. }  


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

    非著名程序员可能是东半球最好的技术分享公众号。每天,每周定时推送一些有关移动开发的原创文章和教程,微信号:smart_android。
  • 相关阅读:
    Spring BeanFactory与FactoryBean的区别及其各自的详细介绍于用法
    解决 vim 报错:the imp module is deprecated in favour of importlib
    SIFT了解,哪些方法可以在现在的AI算法中借鉴?
    CLAHE
    实际算法项目工程上手日志C/C++
    OS X 切换gcc版本
    opencv3.4.2 cmake错误:in-source builds are not allowed
    C++ opencv 计算两张图像的PSNR相似度
    如何在OS X 中使用markdown + latex混合记笔记?
    给anaconda 换源
  • 原文地址:https://www.cnblogs.com/loonggg/p/3222861.html
Copyright © 2011-2022 走看看