内存泄漏:带位图的 ListView
Memory Leaks : ListView with Bitmaps
我有一个明显的内存泄漏问题,我需要一些 help/advices 来解决它。
我的方案有一个显示图像和标题的简单 ListView。此图像是从服务器下载的位图图像。
快速上下滚动后,这个 ListView 使我的应用程序崩溃,如果我检查控制台,我会遇到这样的 OOM 异常:
[art] Clamp target GC heap from 111MB to 96MB
[art] Alloc concurrent mark sweep GC freed 3(96B) AllocSpace objects, 0(0B) LOS objects, 0% free, 95MB/96MB, paused 1.187ms total 38.840ms
为了避免 OOM,我实现了 LRUCache 和 DiskCache 以将下载的位图存储到设备中,并获取此文件而不是再次下载图像。
这是我的 ListView 适配器:
public class LazyLoadAdapter : BaseAdapter
{
Activity _activity;
List _products;
BitmapCache cache;
ImageView _imgView;
Dictionary<string, Task> pendingFetch = new Dictionary<string, Task> ();
Bitmap NoMapPicture;
public LazyLoadAdapter(Activity activity, List<CouponExchange> products)
{
_activity = activity;
_products = products;
NoMapPicture = PrepareNoMapPicture (Resource.Drawable.default_coupon);
this.cache = BitmapCache.CreateCache (activity, "MapCache");
}
public override int Count
{
get { return _products.Count; }
}
public override Java.Lang.Object GetItem(int position)
{
return position;
}
public override long GetItemId(int position)
{
return position;
}
public override Android.Views.View GetView(int position, Android.Views.View convertView, Android.Views.ViewGroup parent)
{
if (convertView == null)
{
convertView = _activity.LayoutInflater.Inflate(Resource.Layout.ShopItemList, parent, false);
}
CouponExchange product = _products[position];
TextView txtProductName = convertView.FindViewById<TextView>(Resource.Id.textView24);
txtProductName.Text = product.CouponTitle;
TextView txtProductCost = convertView.FindViewById<TextView>(Resource.Id.textView24b);
txtProductCost.Text = product.Cost.ToString();
_imgView = convertView.FindViewById<ImageView>(Resource.Id.imgProduct);
GetPersonPicture (product.CouponImageUrl);
return convertView;
}
Bitmap DownloadoCacher (string url)
{
Bitmap map = null;
using(map){
map = cache.TryGet2 (url);
if (map!=null)
return map;
byte[] bytes;
using (var wc = new WebClient ()) {
bytes = wc.DownloadData (url);
};
if (bytes != null && bytes.Length > 0) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
map = DecodeSampledBitmapFromResource (bytes, 400, 200);
} else {
return map;
}
cache.AddOrUpdate (url, map, TimeSpan.FromDays (1));
return map;
};
}
public static Bitmap DecodeSampledBitmapFromResource(byte[] bytes,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
BitmapFactory.DecodeByteArray(bytes, 0, bytes.Length, options);
// Calculate inSampleSize
options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeByteArray(bytes, 0, bytes.Length, options);
}
public static int CalculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Bitmap PrepareNoMapPicture (int baseImage)
{
return BitmapFactory.DecodeResource (_activity.Resources, baseImage);
}
Bitmap GetPersonPicture (string url){
if (_imgView == null)
return null;
Bitmap map = null;
using (map) {
map = cache.TryGet2 (url);
if (map!=null) {
_imgView.SetImageBitmap (map);
} else {
_imgView.SetImageBitmap (NoMapPicture);
Action doMapSetting = () => {
_activity.RunOnUiThread (() => {
if (map == null){
map = cache.TryGet2 (url);
}
_imgView.SetImageBitmap (map);
});
};
if (pendingFetch.ContainsKey (url))
pendingFetch [url].ContinueWith (t => doMapSetting (), TaskContinuationOptions.ExecuteSynchronously);
else
pendingFetch[url] = SerialScheduler.Factory.StartNew (() => {
map = DownloadoCacher (url);
doMapSetting ();
});
}
return map;
};
}
下载图像后,我的缓存会从设备文件中获取图像。
如此快速地上下滚动后,cacheDisk 尝试从文件中获取图像并抛出 OOM 异常:
try {
bmp = Android.Graphics.BitmapFactory.DecodeFile (Path.Combine (basePath, key));
} catch (Exception e) {
var err = e.Message;
return null;
}
所有回复将不胜感激。谢谢
我将这个 Picasso Binding 库用于 Xamarin:
https://github.com/jacksierkstra/Picasso
这个强大的图像下载和缓存库可以让您简化图像管理。
官方文档:
http://square.github.io/picasso/
希望对您有所帮助
我有一个明显的内存泄漏问题,我需要一些 help/advices 来解决它。
我的方案有一个显示图像和标题的简单 ListView。此图像是从服务器下载的位图图像。
快速上下滚动后,这个 ListView 使我的应用程序崩溃,如果我检查控制台,我会遇到这样的 OOM 异常:
[art] Clamp target GC heap from 111MB to 96MB
[art] Alloc concurrent mark sweep GC freed 3(96B) AllocSpace objects, 0(0B) LOS objects, 0% free, 95MB/96MB, paused 1.187ms total 38.840ms
为了避免 OOM,我实现了 LRUCache 和 DiskCache 以将下载的位图存储到设备中,并获取此文件而不是再次下载图像。
这是我的 ListView 适配器:
public class LazyLoadAdapter : BaseAdapter
{
Activity _activity;
List _products;
BitmapCache cache;
ImageView _imgView;
Dictionary<string, Task> pendingFetch = new Dictionary<string, Task> ();
Bitmap NoMapPicture;
public LazyLoadAdapter(Activity activity, List<CouponExchange> products)
{
_activity = activity;
_products = products;
NoMapPicture = PrepareNoMapPicture (Resource.Drawable.default_coupon);
this.cache = BitmapCache.CreateCache (activity, "MapCache");
}
public override int Count
{
get { return _products.Count; }
}
public override Java.Lang.Object GetItem(int position)
{
return position;
}
public override long GetItemId(int position)
{
return position;
}
public override Android.Views.View GetView(int position, Android.Views.View convertView, Android.Views.ViewGroup parent)
{
if (convertView == null)
{
convertView = _activity.LayoutInflater.Inflate(Resource.Layout.ShopItemList, parent, false);
}
CouponExchange product = _products[position];
TextView txtProductName = convertView.FindViewById<TextView>(Resource.Id.textView24);
txtProductName.Text = product.CouponTitle;
TextView txtProductCost = convertView.FindViewById<TextView>(Resource.Id.textView24b);
txtProductCost.Text = product.Cost.ToString();
_imgView = convertView.FindViewById<ImageView>(Resource.Id.imgProduct);
GetPersonPicture (product.CouponImageUrl);
return convertView;
}
Bitmap DownloadoCacher (string url)
{
Bitmap map = null;
using(map){
map = cache.TryGet2 (url);
if (map!=null)
return map;
byte[] bytes;
using (var wc = new WebClient ()) {
bytes = wc.DownloadData (url);
};
if (bytes != null && bytes.Length > 0) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
map = DecodeSampledBitmapFromResource (bytes, 400, 200);
} else {
return map;
}
cache.AddOrUpdate (url, map, TimeSpan.FromDays (1));
return map;
};
}
public static Bitmap DecodeSampledBitmapFromResource(byte[] bytes,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
BitmapFactory.DecodeByteArray(bytes, 0, bytes.Length, options);
// Calculate inSampleSize
options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeByteArray(bytes, 0, bytes.Length, options);
}
public static int CalculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Bitmap PrepareNoMapPicture (int baseImage)
{
return BitmapFactory.DecodeResource (_activity.Resources, baseImage);
}
Bitmap GetPersonPicture (string url){
if (_imgView == null)
return null;
Bitmap map = null;
using (map) {
map = cache.TryGet2 (url);
if (map!=null) {
_imgView.SetImageBitmap (map);
} else {
_imgView.SetImageBitmap (NoMapPicture);
Action doMapSetting = () => {
_activity.RunOnUiThread (() => {
if (map == null){
map = cache.TryGet2 (url);
}
_imgView.SetImageBitmap (map);
});
};
if (pendingFetch.ContainsKey (url))
pendingFetch [url].ContinueWith (t => doMapSetting (), TaskContinuationOptions.ExecuteSynchronously);
else
pendingFetch[url] = SerialScheduler.Factory.StartNew (() => {
map = DownloadoCacher (url);
doMapSetting ();
});
}
return map;
};
}
下载图像后,我的缓存会从设备文件中获取图像。 如此快速地上下滚动后,cacheDisk 尝试从文件中获取图像并抛出 OOM 异常:
try {
bmp = Android.Graphics.BitmapFactory.DecodeFile (Path.Combine (basePath, key));
} catch (Exception e) {
var err = e.Message;
return null;
}
所有回复将不胜感激。谢谢
我将这个 Picasso Binding 库用于 Xamarin: https://github.com/jacksierkstra/Picasso
这个强大的图像下载和缓存库可以让您简化图像管理。
官方文档: http://square.github.io/picasso/
希望对您有所帮助