Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android編程學習之異步加載圖片的方法

Android編程學習之異步加載圖片的方法

編輯:關於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程序設計有所幫助。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved