Android Endless list 内存管理

Posted

技术标签:

【中文标题】Android Endless list 内存管理【英文标题】:Android Endless list memory management 【发布时间】:2014-04-13 07:34:52 【问题描述】:

我通过在 onScrollStateChanged(...) 方法中将更多项目加载到 arraylist 来实现无限列表视图。如果我正在实施这个方案来获取超过 100 万个条目,我将在 arraylist 中添加一百万个对象,这是内存密集型的。我可以使用哪些方案来进行高效的内存管理?

PS:问题是关于可以放入适配器的项目数量。 编辑:

更多细节:

数据来源是互联网。我必须从 Internet 获取数据并将其放入 listview 适配器。

【问题讨论】:

ListView本身而言,你应该利用回收机制:***.com/a/14108676/3071356。 @super-qua - 我了解视图中的回收。但问题是关于listview的来源,它是一个arraylist。 是的,我知道,只是想添加这个以防你不知道 如果您仍然延迟加载列表视图(并且由于 ListView 本身回收视图),为什么要将所有 100 万个项目加载到 ArrayList 中?我建议只保留屏幕上显示的项目并动态加载新项目。当然我不知道你的数据的来源,这可能不是那么容易。如果您详细说明实际数据,我可以给您一些建议。 我会考虑将数据存储到 SQLite DB 中,并用Cursor 填充ListView(使用CursorAdapter)。更具体地说,好处是SQLiteCursor 扩展自AbstractWindowedCursor,它利用了通过CursorWindow(基本上是缓冲区)公开数据的优势。因此,您不必太担心在内存中存储大量项目,以及对所有这些不同的 android 设备进行适当的管理。话虽如此,您真的想向用户显示一个包含超过一百万个项目的列表吗? 【参考方案1】:

我认为您应该只保留当前条目以及它们之前或之后的条目(可能是 100 个),将此数据放入缓存中。

当您滚动列表视图时,获取更多条目并像以前一样更新缓存(不要一次获得 100 万个)。

【讨论】:

如果我有一个包含 100 个元素的适配器,当我加载第 101 个元素时,我应该用第 101 个元素替换第一个条目是什么意思? 使用缓冲思维。我的意思是你把第 101 到第 200 放在你的缓存中(当前是 100),如果你滚动 130,不需要向下,当当前是 160 时,也许你可以在后台加载第 201 到第 300。只需更新缓存一些空闲时间。 我想这是对理论上无限数据集有意义的方法,例如社交网络流。 您能否提供此建议的真实代码示例?【参考方案2】:

在 Android 中,ListView 是虚拟化的。实际上这意味着 里面的元素数量没有实际限制。你可以把 列表中的数百万行,它只会为 当前可见的(或更多的顶部)。

Source

也可以查看这篇文章Performance Tips for Android’s ListView

【讨论】:

我了解 ListView 中所做的优化。问题是可以将多少对象放入 ListView 的适配器中。 您有一个特定于设备的内存限制。只要您不耗尽内存限制,您就可以存储任意数量的项目。【参考方案3】:

这个问题与Adapter“容量”无关。相反,它与您的应用程序分配的内存量有关。

它有一个保留的heap 用于分配对象,如果你超过了这个限制,你会得到一个Out of Memory Exception

这里有一个小测试,它可以让您了解可以分配的数据量。但请注意,在此示例中,对象仅包含一个 String,如果它是一个华丽的 Bitmap,那么要分配的对象数量会少得多。

//内存活动

public class MemoryActivity extends Activity 

    private List<TestObject> _testObjects = new ArrayList<TestObject>();

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_memory);
        coolGuysDoNotLookBackAtExplosion();
        starCountdown();
    

    private void starCountdown() 
        new CountDownTimer(300000, 500) 

            public void onTick(long millisUntilFinished) 
                TextView tv_watcher = (TextView) findViewById(R.id.tv_watcher);
                tv_watcher.setText(getMemoryUsage());
            

            public void onFinish() 
                starCountdown();
            

        .start();
    

    private String getMemoryUsage() 
        String heapSize = String.format("%.3f", (float) (Runtime.getRuntime().totalMemory() / 1024.00 / 1024.00));
        String freeMemory = String.format("%.3f", (float) (Runtime.getRuntime().freeMemory() / 1024.00 / 1024.00));

        String allocatedMemory = String
                .format("%.3f", (float) ((Runtime.getRuntime()
                        .totalMemory() - Runtime.getRuntime()
                        .freeMemory()) / 1024.00 / 1024.00));
        String heapSizeLimit = String.format("%.3f", (float) (Runtime.getRuntime().maxMemory() / 1024.00 / 1024.00));

        String nObjects = "Objects Allocated: " + _testObjects.size();

        return "Current Heap Size: "    + heapSize
                + "\n Free memory: "
                + freeMemory
                + "\n Allocated Memory: "
                + allocatedMemory
                + "\n Heap Size Limit:  "
                + heapSizeLimit
                + "\n" + nObjects;
    

    private void coolGuysDoNotLookBackAtExplosion()
        new Thread(new Runnable() 
            @Override
            public void run() 
                _testObjects = new ArrayList<TestObject>();
                while (true) 
                    _testObjects.add(new TestObject());
                
            
        ).start();
    

//测试对象

public class TestObject 
    private String sampleText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry";

【讨论】:

【参考方案4】:

如果您的 ListView 仅包含文本项,则您无需做太多事情。但是,如果您正在加载更多内存密集型的东西,例如可绘制对象(例如,您的视图右侧有一张图片),那么您应该进行一些回收,以获得最佳效果。在较弱的设备上,您可能会很快收到OutOfMemoryException。我什至可以在 Nexus 4 上进行 OOM。只需尝试非常快速地上下滚动,然后重复直到强制关闭。

看看RecyclerListener,很容易实现。

【讨论】:

【参考方案5】:

您应该使用pagingLoad More 按钮作为ListView 的页脚。例如:

url = http://your_full_url.php?page=1

假设您在每个页面中有 100 条记录,然后第一次获取所有这 100 条 page 1 的记录,将它们显示在 ListViewcache 上。现在向下滚动您的ListView 并单击加载更多按钮(加载更多按钮应设置为ListView 的页脚)。

当您点击加载更多时,您将通过调用获得下 100 条记录

url = http://your_full_url.php?page=2 等等

url = http://your_full_url.php?page=3,

url = http://your_full_url.php?page=4 等等...

每次您都会缓存这些记录,以便在连接丢失的情况下,您可以显示缓存中可用的记录。

【讨论】:

【参考方案6】:

我猜是 sqlite 数据库和流解析器(GSON)。

【讨论】:

您能进一步解释一下吗?答案很模糊,我无处可去 根据您的问题,您不想使用数组列表,因为它会占用更多内存,所以我建议不要使用数组列表,而是使用 sqllite 数据库并在您想使用时使用内容提供程序该信息。 Streaming Parser 逐一解析数据流,因此无需一次将百万个条目下载到设备内存中。一件一件地做并保存到数据库中。这样你就可以节省很多内存。 ListView 本身经过高度优化,它使用所需的大量内存。因此,使用流解析器一一下载数据并将其一一保存到数据库并将该数据库连接到列表视图。无需在内存缓存中存储任何东西。 让我试试。我猜大小的问题将是 sqllite db 的大小。【参考方案7】:

我找不到文档中提到的确切数字。但是,所有Adapter#getCount()(查看子类)的返回类型都是ints

因此,我们强烈怀疑您最多可以将 Integer.MAX_VALUE 项添加到适配器中,即 231-1(超过 20 亿)。适配器使用ListsMaps在内部存储数据,have the same limit。

所以你不应该担心适配器的限制而不是使用过多的内存。我建议您将 10-100 个元素加载到适配器中,并在用户到达列表视图底部后立即添加更多项目。

【讨论】:

【参考方案8】:

天威的方法是要走的路。

如果 ListView 是延迟加载的,并且由于 ListView 本身正在回收视图,最好只将可见的列表条目保留在内存中。您基本上在 ListView 为视图所做的适配器中执行相同的操作。

如果您将所有数据保存在内存中,那么延迟加载 ListView 的意义何在?只需加载所有数据并跳过延迟加载部分...... 当然,对于只加载可见数据(可能还有更多)的延迟加载方法,您必须在列表的底部和顶部实现延迟加载才能使其工作。

现在,由于没有关于数据(文本、图像)或来源(Internet、SQLite Db、文本文件...)性质的信息,我无法为您提供如何实现此功能的代码(示例)。如果您详细说明数据,我可以更准确地回答问题。

【讨论】:

【参考方案9】:

如果你需要在内存中保存 1M 个对象,并且假设对象数据很小,那么这只是几 MB 的内存,只保存在内存中应该没问题。从问题中我了解到,当用户向前滚动时,您会阅读更多项目,因此实际上您不会有 1M 行 - 用户需要滚动很长时间才能达到 1M。 只要你正确使用 ListView,你可以让适配器数据在内存中增长到 1M+ 行,没有任何问题

【讨论】:

以上是关于Android Endless list 内存管理的主要内容,如果未能解决你的问题,请参考以下文章

Android:Endless Scrolling listview 内容错误和 NullpointerException

android中带有Endless Listview Scroll的AsynTask

无尽跑酷3D (Endless Run 3D) 苹果安卓版免费下载|iOS|Android|iPhone版本免费下载 - 休闲 3D游戏 跑酷游戏 竞速游戏

JEasyUI: Create endless progressbar

死循环(endless loop)

Endless Sky源码学习笔记-3