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学习笔记(二)之异步加载图片的更多相关文章
- Android学习笔记_36_ListView数据异步加载与AsyncTask
一.界面布局文件: 1.加入sdcard写入和网络权限: <!-- 访问internet权限 --> <uses-permission android:name="andr ...
- android Listview 软引用SoftReference异步加载图片
首先说一下,android系统加载大量图片系统内存溢出的3中解决方法: (1)从网络或本地加载图片的时候,只加载缩略图.这个方法的确能够少占用不少内存,可是它的致命的缺点就是,因为加载的是缩略图,所以 ...
- 【Android】ListView、RecyclerView异步加载图片引起错位问题
今天在RecyclerView列表里遇到一个情况,它包含300条数据,每项包含一个图片,发现在首次载入时,由于本地没图,请求网络的时候:快速滑动导致了图片错位.闪烁的问题. 原理的话有一篇已经说的很清 ...
- 学习andriod开发之 异步加载图片(二)--- 使用其他进度条
大家好 我是akira上一节 我们讲到使用AsyncTask 这个类进行异步的下载 主要是涉及到一些图片的更新 这次我们继续上一个demo的改进 . 不知道你是否发现一个问题 上一节我们遗留了两个bu ...
- vue.js学习笔记(二):如何加载本地json文件
在项目开发的过程中,因为无法和后台的数据做交互,所以我们可以自建一个假数据文件(如data.json)到项目文件夹中,这样我们就可以模仿后台的数据进行开发.但是,如何在一个vue.js 项目中引入本地 ...
- IOS学习之路二十三(EGOImageLoading异步加载图片开源框架使用)
EGOImageLoading 是一个用的比较多的异步加载图片的第三方类库,简化开发过程,我们直接传入图片的url,这个类库就会自动帮我们异步加载和缓存工作:当从网上获取图片时,如果网速慢图片短时间内 ...
- Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案
我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位.重复.闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化. 比如L ...
- Android 异步加载图片,使用LruCache和SD卡或手机缓存,效果非常的流畅
Android 高手进阶(21) 版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明出处http://blog.csdn.net/xiaanming/article/details ...
- Android之ListView异步加载图片且仅显示可见子项中的图片
折腾了好多天,遇到 N 多让人崩溃无语的问题,不过今天终于有些收获了,这是实验的第一版,有些混乱,下一步进行改造细分,先把代码记录在这儿吧. 网上查了很多资料,发现都千篇一律,抄来抄去,很多细节和完整 ...
随机推荐
- 补间动画 Interpolator 简介 示例
简介 补间动画的原理: 每次绘制视图时View所在的[ViewGroup]中的drawChild函数获取该View的Animation的值,然后调用canvas.concat (transformTo ...
- (转)\r \r\n \t 的区别
小风吹雪 \r \r\n \t 的区别 http://www.360doc.com/content/12/0530/15/16538_214756101.shtml \n 软回车: 在Wi ...
- Linux samba服务器设置简单匿名共享
linux下面的samba非常的好用,很多人拿它来作共享文件服务器, 缺省配置下,samba必须提供用户名密码来访问,如果是所有人都可以访问的内容,那么是比较麻烦的,其实通过一个设置,即可实现不用输入 ...
- react-redux源码解析
有理解不对的地方,欢迎大家指正!!! react为什么需要redux辅助???react是view层的单向数据流框架,数据需要一层一层往子组件传递(子组件并不会自动继承).子组件需要操作父组件的数据时 ...
- Phpcms V9全站伪静态设置方法
为什么要伪静态?具体在这里就不说了,你懂的!一方面更新修改后不需要生成静态文件,另一方面为了SEO! 访问规则如下 1 2 list-{$catid}-{$page}.html content-{$c ...
- jquery radio取值,checkbox取值,select取值,radio选中,checkbox选中,select选中
jQuery获取Select选择的Text和Value: 语法解释: 1. $("#select_id").change(function(){//code...}); //为Se ...
- 微星b85(b85i b85-gaming) 系列dsdt
从网友得了一个b85-gaming 的dsdt,发现跟我的b85i的dsdt错误都是一样的. 发布上来给需要的人参考. 微星这个系列的dsdt不能用dsdt editor的fix功能,不然文件会越来越 ...
- 超级列表框List Ctrl
LVCFMT_CENTER居中对齐 LONG styles; CListCtrl *str=new CListCtrl; str->Create(LVS_ICON, CRect(,,,), ); ...
- ASP.NET MVC5中的数据注解
ASP.NET MVC5中Model层开发,使用的数据注解有三个作用: 数据映射(把Model层的类用EntityFramework映射成对应的表) 数据验证(在服务器端和客户端验证数据的有效性) 数 ...
- angular2 学习笔记 ( ngModule 模块 )
2016-08-25, 当前版本是 RC 5. 参考 : https://angular.cn/docs/ts/latest/guide/ngmodule.html 提醒 : 这系列笔记的 " ...