对于各种Android应用程序,我需要大ListView
个,即包含100-300个条目的此类视图。
启动应用程序时必须批量加载所有条目,因为某些排序和处理是必要的,应用程序无法知道首先显示哪些项目,否则。
到目前为止,我一直在批量加载所有项目的图像,然后将这些图像与每个条目的其余数据一起保存在ArrayList<CustomType>
中。
但当然,这不是一个好习惯,因为你很可能会有OutOfMemoryException
:ArrayList
中对所有图像的引用会阻止垃圾收集器工作。< / p>
因此,最好的解决方案显然只是批量加载文本数据,然后根据需要加载图像,对吧? Google Play应用程序执行此操作,例如:您可以看到图像在滚动到它们时加载,即它们可能已加载到适配器的getView()
方法中。但是对于谷歌播放,这是一个不同的问题,因为图像必须从互联网加载,这对我来说并非如此。我的问题不是加载图像需要太长时间,但存储它们需要太多内存。
那么我该如何处理这些图片呢?在真正需要时加载getView()
?会使滚动迟缓。那么调用AsyncTask
呢?或者只是正常Thread
?参数化吗?
我可以保存已加载到HashMap<String,Bitmap>
的图片,这样就不需要在getView()
中再次加载它们。但是如果这样做,你又会出现内存问题:HashMap
存储对所有图像的引用,所以最后你可以再次拥有OutOfMemoryException
。
我知道这里已经有很多问题讨论过图像的“延迟加载”。但它们主要涵盖了加载缓慢的问题,而不是太多的内存消耗。
编辑:我现在决定start AsyncTasks in getView() which load the image into the ListView in the background。但这会导致我的应用程序遇到RejectedExecutionException。我现在该怎么办?
答案 0 :(得分:9)
我采用了使用AsyncTask加载图像的方法,并将任务附加到适配器的getView函数中的视图,以跟踪在哪个视图中加载哪个任务。我在我的应用程序中使用它,没有滚动滞后,所有图像都加载到正确的位置,没有异常被抛出。此外,因为如果任务被取消,任务就无效,你可以在列表上执行任务,它应该会落后。
任务:
public class DecodeTask extends AsyncTask<String, Void, Bitmap> {
private static int MaxTextureSize = 2048; /* True for most devices. */
public ImageView v;
public DecodeTask(ImageView iv) {
v = iv;
}
protected Bitmap doInBackground(String... params) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPurgeable = true;
opt.inPreferQualityOverSpeed = false;
opt.inSampleSize = 0;
Bitmap bitmap = null;
if(isCancelled()) {
return bitmap;
}
opt.inJustDecodeBounds = true;
do {
opt.inSampleSize++;
BitmapFactory.decodeFile(params[0], opt);
} while(opt.outHeight > MaxTextureSize || opt.outWidth > MaxTextureSize)
opt.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeFile(params[0], opt);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
if(v != null) {
v.setImageBitmap(result);
}
}
}
适配器存储一个ArrayList,其中包含所有需要加载的图像的文件路径。 getView函数如下所示:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView iv = null;
if(convertView == null) {
convertView = getLayoutInflater().inflate(R.id.your_view, null); /* Inflate your view here */
iv = convertView.findViewById(R.id.your_image_view);
} else {
iv = convertView.findViewById(R.id.your_image_view);
DecodeTask task = (DecodeTask)iv.getTag(R.id.your_image_view);
if(task != null) {
task.cancel(true);
}
}
iv.setImageBitmap(null);
DecodeTask task = new DecodeTask(iv);
task.execute(getItem(position) /* File path to image */);
iv.setTag(R.id.your_image_view, task);
return convertView;
}
注意:这里只是一个警告,这可能仍会在版本1.5 - 2.3上给你内存问题,因为它们使用AsyncTask的线程池。 3.0+默认情况下返回串行模型执行AsyncTasks,使其一次运行一个任务,因此在任何给定时间使用更少的内存。只要你的图像不是太大,你应该没问题。
答案 1 :(得分:2)
1)使用HashMap<String, Bitmap>
解决您的内存问题:您是否可以使用WeakHashMap
以便在需要时回收图片?如果您的ArrayList
对图片的引用较弱,则同样适用于您在开头提到的CustomType
。
2)如果用户在列表中滚动时不加载图像,而是在用户停止滚动时加载图像,此时加载图像。是的,如果没有图像,列表看起来不会很花哨,但在滚动期间它会非常高效,而在用户滚动时,他无论如何都看不到细节。从技术上来说它应该是这样的:
listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
// Don't care.
}
@Override
public void onScrollStateChanged(final AbsListView view, int scrollState) {
// Load images here. ListView can tell you what rows are visible, you load images for these rows and update corresponding View-s.
}
})
3)我有一个应用程序,在应用程序启动期间加载大约300张图像,并保持应用程序的生命周期。所以这对我来说也是一个问题,但到目前为止我看到很少有OOM报告,我怀疑它们是因为泄漏不同而发生的。我尝试使用ListView图像的RGB_565
配置文件来保存一些内存(为此目的,质量没有显着差异),我使用96x96最大图像大小,这对于标准列表项高度应该足够了。
答案 2 :(得分:1)
不要将所有图像存储在列表中,因为它太重了。您应该在getView中启动AsyncTask,在InBackground中获取,解码您的图像,并在PostExecute中的imageView上绘制图像。为了保持列表的性能,您还可以使用getView方法中的convertView参数,但是AsyncTask开始变得复杂,因为您的视图可以在AsyncTask完成之前被回收,并且您应该处理这个额外的...
您可以使用LruCache,但这仅在从互联网下载图像时才有意义。当它们被存储在localy时,没有必要使用它。
答案 3 :(得分:1)
检查一下:https://github.com/DHuckaby/Prime
我会将此用于ListView中的图像与尝试自己解决它...我实际上将它用于我的应用程序中的所有远程图像..或至少阅读源代码..图像管理是一种痛苦..精益在一个成熟的图书馆,让你去。
答案 4 :(得分:0)
你提供的link有助于理解什么是convertView,asyncTask等。我不认为做View v = super.getView(position, convertView, parent);
会有效。如果您想要回收视图,请执行if(convertView != null){ myView = convertView} else{ inflate(myView)};
关于AsyncTask,它在不同的APIS中是不同的,但是当你对旧API使用execute()和在新的时候使用executeOnExecutor时 - 我认为一切都很好。您应该将URI和ImageView传递给AsyncTask。在这里你可能遇到convertView的问题,它出现在一个新的行中,AsyncTask处理它的图像。你可以保存ImageView-AsyncTask的示例hashmap,并取消那些无效的。
PS.Sorry用于创建新评论,但内联答案太长了:)
答案 5 :(得分:-1)
private class CallService extends AsyncTask<String, Integer, String>
{
protected String doInBackground(String... u)
{
fetchReasons();
return null;
}
protected void onPreExecute()
{
//Define the loader here.
}
public void onProgressUpdate(Integer... args)
{
}
protected void onPostExecute(String result)
{
//remove loader
//Add data to your view.
}
}
public void fetchReasons()
{
//Call Your Web Service and save the records in the arrayList
}
调用新的CallService()。execute();在onCreate()方法
答案 6 :(得分:-3)
一小段建议是在发生投掷时禁用图像加载。
脏HACK让一切更快:
在适配器的getItemViewType(int position)
中,返回位置:
@Override
public long getItemViewType(int position) {
return position;
}
@Override
public long getViewTypeCount(int position) {
return getCount();
}
@Override
public View getView(int position, View convertView, ViewGroup arg2) {
if (convertView == null) {
//inflate your convertView and set everything
}
//do not do anything just return the convertView
return convertView;
}
ListView
将决定要缓存的图片数量。