Android:BitmapFactory.nativeDecodeAsset 处的 OutOfMemoryError(Honeycomb 3.0 及更高版本)

Posted

技术标签:

【中文标题】Android:BitmapFactory.nativeDecodeAsset 处的 OutOfMemoryError(Honeycomb 3.0 及更高版本)【英文标题】:Android: OutOfMemoryError at BitmapFactory.nativeDecodeAsset (Honeycomb 3.0 and above) 【发布时间】:2013-03-12 08:41:11 【问题描述】:

由于 android 在 Android 3.0 中引入了新的位图内存管理(现在分配位图数据inside the Dalvik Heap),我收到了这个 OutOfMemoryError 错误,导致我的应用程序崩溃。

基本上我有一个 ListView 大约。 1000 个 ImageViews 包含我的资产文件夹中的位图(分辨率:300 x 200 像素)。

我在运行 Jelly Bean 的 HTC Desire 上检测到此问题。如果我滚动到快,我的应用程序的 Pro 版本会崩溃。 (免费版列表中只有 150 个项目) 我试图在模拟器中重现错误,发现 Android 3.0 及更高版本中 DDMS 的垃圾收集消息不像我的旧 2.3.6 智能手机(运行应用程序没有任何问题)上的 GC_EXTERNAL_ALLOC。因此,大量数据的内存大小是不够的。我不认为 ListView 真的将所有的 Bitmaps 都保存在内存中,但是 150 个 Items 和 1000 个 Items 之间肯定是有区别的。

我尝试在我的列表适配器中对 GetView() 执行 AsyncTask,但随后 OutOfMemory 发生在 AsyncTask 线程中。所以我对此无能为力......

有人有解决这个问题的想法吗?

Google's development guide 没有帮助我

编辑:

好的,我对我的 APP 进行了 MAT 分析,似乎不是 ListView 导致了问题,而是我的 ViewFlipper。

我的 MainMenu、我的 SearchScreen、我的 SearchResults(ListView 和 GridView - 可切换)和我的DetailedView 都是我的 ViewFlipper 的单个“页面”。

MainMenu、SearchScreen 和DetailedView 占用我大约20 MB 的内存。 我从代码中提取了 List- 和 Gridview,看起来它们占用了 8MB 到 13MB 的空间(取决于执行滚动的速度)。

Android SDK 模拟器(模拟 4.2)的 VM 堆大小为 32MB。所以最初它可以工作,如果我滚动到快速,我会遇到内存不足,并显示错误消息:

skia decoder->decode returned false

随后出现各种 Bitmap-decode-OutOfMemory 问题。

所以我认为我必须卸载/缓存我的 ViewFlipper 的页面 - 对吧? 怎么做???

这里是 ListView/GridView 实现的提取源代码。

public class GridTestActivity extends Activity 

    GridView gv;
    ListView lv;
    int lvScreenWidth;
    int lvScreenHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_grid_test);

        Display display = getWindowManager().getDefaultDisplay(); 
        this.lvScreenWidth  =  display.getWidth();
        this.lvScreenHeight  =  display.getHeight();

        MyAdapter adp = new MyAdapter(this);

        gv = (GridView)findViewById(R.id.gridView1);
        gv.setAdapter(adp);
        gv.setFastScrollEnabled(true);

        lv = (ListView)findViewById(R.id.listView1);
        lv.setAdapter(adp);
        lv.setFastScrollEnabled(true);


        ActivityManager am = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
        Toast.makeText(this, "DALVIK HEAP SIZE: " + am.getMemoryClass() + "MB", 5).show();


    

    static class ViewHolder
         TextView text;
         ImageView icon;
    


    public class MyAdapter extends BaseAdapter

        private LayoutInflater mInflater;

        public MyAdapter(Context context) 
            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        


        @Override
        public int getCount() 
            return 1000;
        

        @Override
        public Object getItem(int arg0) 
            return null;
        

        @Override
        public long getItemId(int position) 
            return 0;
        

        @Override
        public boolean hasStableIds() 
            return true;
        


        @Override
        public View getView(int position, View convertView, ViewGroup parent) 
            ViewHolder holder;

            if (convertView == null) 
                convertView = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
                holder = new ViewHolder();
                holder.text = (TextView) convertView.findViewById(R.id.text);
                holder.icon = (ImageView) convertView.findViewById(R.id.icon);

                convertView.setTag(holder);
             else 
                holder = (ViewHolder) convertView.getTag();
            

            holder.text.setText("pos:" + position);

            // best way to load an asset-image???
            try 
                InputStream ims = getAssets().open("assetpic_" + position + ".jpg");
                BitmapDrawable d = (BitmapDrawable) Drawable.createFromStream(ims, null);
                ims.close();
                holder.icon.setImageDrawable(d);


                if (parent.equals(lv))   
                    int oldWidth = d.getIntrinsicWidth();
                    int oldHeight= d.getIntrinsicHeight();
                    holder.icon.getLayoutParams().height = lvScreenWidth * oldHeight/oldWidth;
                


            
            catch(IOException ex) 
                Log.i("MyAdapter" , "Could not load 'assetpic_" + position + ".jpg' from Assets-folder");
            

            return convertView;
        

    


我不确定,但也许我错过了我的代码的一些细节?

full source (25MB)

【问题讨论】:

也许这可以为您指明正确的方向:***.com/questions/477572/… 【参考方案1】:

您应该在不使用时回收位图。

bitmap.recycle();

您可以使用通用图像加载器。 https://github.com/nostra13/Android-Universal-Image-Loader.

您可以使用延迟加载图像。 https://github.com/thest1/LazyList.

两者都使用缓存。

http://www.youtube.com/watch?v=_CruQY55HOk。演讲内容是关于内存管理、回收位图以及如何使用 MAT Analyzer 检测内存泄漏。

http://developer.android.com/training/improving-layouts/smooth-scrolling.html。使用 View Holder 进行平滑滚动。

http://www.youtube.com/watch?v=wDBM6wVEO70。演讲是关于视图持有者和列表视图的性能。

正如你所提到的,你已经有效地展示了位图。

【讨论】:

感谢您的回复。我已经实现了您的一些提示(请参阅已编辑的原始帖子添加的源代码)。也许你知道一些技巧,如何摆脱 viewflipper-problem?提前谢谢你 您是否使用了通用图像加载器,但仍然遇到内存泄漏??请在此处粘贴代码。 只粘贴相关代码。没有人会看源代码下载它。 (25MB) 我没试过 UIL jet。我认为这个库/项目仅用于远程加载图片而不用于本地图像加载。但似乎 UIL 的多线程和缓存可能对我的项目有用。 您也可以在本地加载图像,也可以使用 UIL。【参考方案2】:

这可能不是一个合适的解决方案,但这就是我现在必须提供的全部内容。 添加你的清单(在应用程序标签中)

android:largeHeap="true"

这可能会在一定程度上解决您的内存不足问题。如果您显示您的适配器和列表视图对,我可能会告诉一些更合法的解决方案。

【讨论】:

@Erican youtube.com/watch?v=wDBM6wVEO70。这家伙使用大堆给出了一个很大的警告。堆越大,垃圾收集越频繁,导致性能下降。 真的。但如果没有看到代码,我无法提供更好的东西。【参考方案3】:

问题在于 AsyncTask 只适用于少数图像,对于数百(或数千)张图像,它们处理得不是很好。

缓存肯定是必不可少的,在您发布的 Google 链接上,有关于 RAM 和磁盘缓存的说明。

对于这样的大型数据集,您确实有两种选择:

    使用库(例如 Universal Image Loader 或 Lazy List),它们并不完美,但它们在大多数情况下都能正常工作。

    创建您自己的实现。我之前已经实现了这些,我希望它们不是公司 IP 的一部分,因为我想开源它。但是您必须为从一个缓存或另一个缓存或在线加载的线程实现几组线程(使用Executors),并返回结果,如果该 ImageView 被回收,请记住取消返回.这是一个漫长而复杂的过程,但如果你做得对,你会得到一些非常流畅的滚动。

【讨论】:

以上是关于Android:BitmapFactory.nativeDecodeAsset 处的 OutOfMemoryError(Honeycomb 3.0 及更高版本)的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向Android 权限 ( Android 逆向中使用的 android.permission 权限 | Android 系统中的 Linux 用户权限 )

android 21 是啥版本

Android逆向-Android基础逆向(2-2)

【Android笔记】android Toast

图解Android - Android核心机制

Android游戏开发大全的目录