88最近在Android開發(fā)中碰到比較棘手的問題,就是加載圖片內(nèi)存溢出。我開發(fā)的是一個(gè)新聞應(yīng)用,應(yīng)用中用到大量的圖片,一個(gè)界面中可能會(huì)有上百張圖片。開發(fā)android應(yīng)用的朋友可能或多或少碰到加載圖片內(nèi)存溢出問題,一般情況下,加載一張大圖就會(huì)導(dǎo)致內(nèi)存溢出,同樣,加載多張圖片內(nèi)存溢出的概率也很高。
列一下網(wǎng)絡(luò)上查到的一般做法:
1.使用BitmapFactory.Options對(duì)圖片進(jìn)行壓縮
2.優(yōu)化加載圖片的adapter中的getView方法,使之盡可能少占用內(nèi)存
3.使用異步加載圖片的方式,使圖片在頁面加載后慢慢載入進(jìn)來。
1、2步驟是必須做足的工作,但是對(duì)于大量圖片的列表仍然無法解決內(nèi)存溢出的問題,采用異步加載圖片的方式才能有效解決圖片加載內(nèi)存溢出問題。
測(cè)試的效果圖如下:
在這里我把主要的代碼貼出來,給大家分享一下。
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"; // 查詢返回結(jié)果 return HttpUtil.queryStringForPost(url); }}
復(fù)制代碼
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"; // 查詢返回結(jié)果 return HttpUtil.queryStringForPost(url); }}
復(fù)制代碼
(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>
復(fù)制代碼
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; }}
復(fù)制代碼
3、這是最重要的一部分代碼,這就是異步加載圖片的一個(gè)類,這里我就不解釋了,代碼中附有注釋。代碼如下: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 { // 手機(jī)中的緩存 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) { // 設(shè)置線程的優(yōu)先級(jí) pictureLoaderThread.setPriority(Thread.NORM_PRIORITY - 1); fileCache = new FileCache(context); } // 在找不到圖片時(shí),默認(rèn)的圖片 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 {// 如果手機(jī)內(nèi)存緩存中沒有圖片,則調(diào)用任務(wù)隊(duì)列,并先設(shè)置默認(rèn)圖片 queuePhoto(url, activity, imageView); imageView.setImageResource(stub_id); } } private void queuePhoto(String url, Activity activity, ImageView imageView) { // 這ImageView可能之前被用于其它圖像。所以可能會(huì)有一些舊的任務(wù)隊(duì)列。我們需要清理掉它們。 picturesQueue.Clean(imageView); PictureToLoad p = new PictureToLoad(url, imageView); synchronized (picturesQueue.picturesToLoad) { picturesQueue.picturesToLoad.push(p); picturesQueue.picturesToLoad.notifyAll(); } // 如果這個(gè)線程還沒有啟動(dòng),則啟動(dòng)線程 if (pictureLoaderThread.getState() == Thread.State.NEW) pictureLoaderThread.start(); } /** * 根據(jù)url獲取相應(yīng)的圖片的Bitmap * * @param url * @return */ private Bitmap getBitmap(String url) { File f = fileCache.getFile(url); // 從SD卡緩存中獲取 Bitmap b = decodeFile(f); if (b != null) return b; // 否則從網(wǎng)絡(luò)中獲取 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; } } // 解碼圖像和縮放以減少內(nèi)存的消耗 private Bitmap decodeFile(File f) { try { // 解碼圖像尺寸 BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(f), null, o); // 找到正確的縮放值。這應(yīng)該是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; } // 設(shè)置恰當(dāng)?shù)膇nSampleSize可以使BitmapFactory分配更少的空間 // 用正確恰當(dāng)?shù)膇nSampleSize進(jìn)行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對(duì)象) * * @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(); } // 存儲(chǔ)下載的照片列表 class PicturesQueue { private Stack<PictureToLoad> picturesToLoad = new Stack<PictureToLoad>(); // 刪除這個(gè)ImageView的所有實(shí)例 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) { // 線程等待直到有圖片加載在隊(duì)列中 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); // 寫到手機(jī)內(nèi)存中 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(); }}
復(fù)制代碼
4、緊接著是幾個(gè)實(shí)體類,一個(gè)是緩存到SD卡中的實(shí)體類,還有一個(gè)是緩存到手機(jī)內(nèi)存中的實(shí)體類。代碼如下:
(1)、緩存到sd卡的實(shí)體類: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(); }}
復(fù)制代碼
(2)、緩存到手機(jī)內(nèi)存的實(shí)體類: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(); }}
復(fù)制代碼
5、這個(gè)是輸入輸出流轉(zhuǎn)換的類,及方法: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) { } }}
復(fù)制代碼
到這里基本就完成了 |