Android編程學(xué)習(xí)之異步加載圖片的方法
本文實(shí)例講述了Android編程學(xué)習(xí)之異步加載圖片的方法。分享給大家供大家參考,具體如下:
最近在android開(kāi)發(fā)中碰到比較棘手的問(wèn)題,就是加載圖片內(nèi)存溢出。我開(kāi)發(fā)的是一個(gè)新聞應(yīng)用,應(yīng)用中用到大量的圖片,一個(gè)界面中可能會(huì)有上百?gòu)垐D片。開(kāi)發(fā)android應(yīng)用的朋友可能或多或少碰到加載圖片內(nèi)存溢出問(wèn)題,一般情況下,加載一張大圖就會(huì)導(dǎo)致內(nèi)存溢出,同樣,加載多張圖片內(nèi)存溢出的概率也很高。
列一下網(wǎng)絡(luò)上查到的一般做法:
1.使用BitmapFactory.Options對(duì)圖片進(jìn)行壓縮
2.優(yōu)化加載圖片的adapter中的getView方法,使之盡可能少占用內(nèi)存
3.使用異步加載圖片的方式,使圖片在頁(yè)面加載后慢慢載入進(jìn)來(lái)。
1、2步驟是必須做足的工作,但是對(duì)于大量圖片的列表仍然無(wú)法解決內(nèi)存溢出的問(wèn)題,采用異步加載圖片的方式才能有效解決圖片加載內(nèi)存溢出問(wèn)題。
測(cè)試的效果圖如下:

在這里我把主要的代碼貼出來(lái),給大家分享一下。
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);
}
}
(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、這是最重要的一部分代碼,這就是異步加載圖片的一個(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)存緩存中沒(mé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è)線程還沒(méi)有啟動(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);
// 將圖片寫(xiě)到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);
// 寫(xiě)到手機(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();
}
}
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();
}
}
(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();
}
}
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) {
}
}
}
到這里基本就完成了。
希望本文所述對(duì)大家Android程序設(shè)計(jì)有所幫助。
- Android異步加載數(shù)據(jù)和圖片的保存思路詳解
- 深入理解Android中的Handler異步通信機(jī)制
- Android 中使用 AsyncTask 異步讀取網(wǎng)絡(luò)圖片
- Android ListView異步加載圖片方法詳解
- Android實(shí)現(xiàn)圖片異步加載并緩存到本地
- Android實(shí)現(xiàn)圖片異步加載及本地緩存
- Android實(shí)現(xiàn)圖片緩存與異步加載
- Android中異步類AsyncTask用法總結(jié)
- android異步加載圖片并緩存到本地實(shí)現(xiàn)方法
- 全面總結(jié)Android中線程的異步處理方式
相關(guān)文章
Android開(kāi)發(fā)Retrofit源碼分析
這篇文章主要為大家介紹了Android開(kāi)發(fā)Retrofit源碼分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Flutter+Metal實(shí)現(xiàn)圖像處理詳細(xì)流程
Flutter使用CVPixelBuffer和iOS交互,我們可以直接使用CVPixelBuffer創(chuàng)建MTLTexture,然后將MTLTexture設(shè)置為渲染目標(biāo),這篇文章主要介紹了Flutter+Metal實(shí)現(xiàn)圖像處理,需要的朋友可以參考下2022-06-06
Android開(kāi)發(fā)之RadioGroup的簡(jiǎn)單使用與監(jiān)聽(tīng)示例
這篇文章主要介紹了Android開(kāi)發(fā)之RadioGroup的簡(jiǎn)單使用與監(jiān)聽(tīng),結(jié)合實(shí)例形式分析了Android針對(duì)RadioGroup單選按鈕簡(jiǎn)單實(shí)用技巧,需要的朋友可以參考下2017-07-07
Android?Flutter制作一個(gè)修改組件屬性的動(dòng)畫(huà)
flutter為我們提供了一個(gè)AnimationController來(lái)對(duì)動(dòng)畫(huà)進(jìn)行詳盡的控制,不過(guò)直接是用AnimationController是比較復(fù)雜的,如果只是對(duì)一個(gè)widget的屬性進(jìn)行修改,可以做成動(dòng)畫(huà)嗎,本文就來(lái)探討一下2023-05-05
Jetpack?Compose實(shí)現(xiàn)對(duì)角線滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了如何利用Jetpack?Compose實(shí)現(xiàn)一個(gè)簡(jiǎn)單的對(duì)角線滾動(dòng)效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-02-02
Android中PopupWindow彈出式窗口使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Android中PopupWindow彈出式窗口的使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09
Android快速實(shí)現(xiàn)發(fā)送郵件實(shí)例
本篇文章主要介紹了Android快速實(shí)現(xiàn)發(fā)送郵件實(shí)例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04

