zoukankan      html  css  js  c++  java
  • Android转换位图BUG,知其然不知其所以然

      最近,在开发某App的时候,发现了一个很奇怪的bug,前面我也发了关于bitmap的总结,但是这个问题恰恰出在BitmapFactory.decodeFile(pathName)这个函数上,使用这个函数在我的应用中如果设置在activity的onCreate方法内部,会导致activity无法加载,返回上级activity。

      网上描述的大多数原因是OutOfMemoryError,但我catch不到这个error,所以可以肯定不是内存溢出引起的错误。为什么解码图像会出现这样的问题呢?关于这个问题,我纠结了一段时间。

    由于调用decodeFile与decodeStream基本相似,中间过程中会引用一个设置bitmap比例的函数外最终都会调用BitmapFactory.decodeStream(is, outPadding, opts),先看一下他们的转码流程,下面这段解码分析参考的是别人一篇oom文章的:

      上图是整个decodeStream实现bitmap转码的流程,最终的决定权其实是在Init.c中,因为Android在启动系统的时候会去优先执行这个里面的函数,通过调用dvmStartup()方法来初始化虚拟机,最终调用到会调用到HeapSource.c中的dvmHeapSourceStartup()方法,而在Init.c中有这么两句代码:

    gDvm.heapSizeStart = 2 * 1024 * 1024;   // Spec says 16MB; too big for us.
    
    gDvm.heapSizeMax = 16 * 1024 * 1024;    // Spec says 75% physical mem

    在另外一个地方也有类似的代码,那就是AndroidRuntime.cpp中的startVM()方法中:

    strcpy(heapsizeOptsBuf, "-Xmx");
    
    property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");
    
    //LOGI("Heap size: %s", heapsizeOptsBuf);
    
    opt.optionString = heapsizeOptsBuf;

    同样也是默认值为16M,虽然目前我看到了两个可以启动VM的方法,具体Android何时会调用这两个初始化VM的方法,还不是很清楚。不过可以肯定的一点就是,如果启动DVM时未指定参数,那么其初始化堆最大大小应该就是16M,那么我们在网上查到了诸多关于解码图像超过8M就会出错的论断是如何得出来的呢?

    我们来看看HeapSource.c中的这个方法的注释:

    /*
    
    * External allocation tracking
    
    *
    
    * In some situations, memory outside of the heap is tied to the
    
    * lifetime of objects in the heap.  Since that memory is kept alive
    
    * by heap objects, it should provide memory pressure that can influence
    
    * GCs.
    
    */
    
    static bool
    
    externalAllocPossible(const HeapSource *hs, size_t n)
    
    {
    
        const Heap *heap;
    
        size_t currentHeapSize;
    
       /* Make sure that this allocation is even possible.
    
         * Don’t let the external size plus the actual heap size
    
         * go over the absolute max.  This essentially treats
    
         * external allocations as part of the active heap.
    
         *
    
         * Note that this will fail "mysteriously" if there’s
    
         * a small softLimit but a large heap footprint.
    
         */
    
        heap = hs2heap(hs);
    
        currentHeapSize = mspace_max_allowed_footprint(heap->msp);
    
        if (currentHeapSize + hs->externalBytesAllocated + n <=
    
                heap->absoluteMaxSize)
    
        {
    
            return true;
    
        }
    
        HSTRACE("externalAllocPossible(): "
    
                "footprint %zu + extAlloc %zu + n %zu >= max %zu (space for %zu)\n",
    
                currentHeapSize, hs->externalBytesAllocated, n,
    
                heap->absoluteMaxSize,
    
                heap->absoluteMaxSize -
    
                        (currentHeapSize + hs->externalBytesAllocated));
    
        return false;
    
    }

    标为红色的注释的意思应该是说,为了确保我们外部分配内存成功,我们应该保证当前已分配的内存加上当前需要分配的内存值,大小不能超过当前堆的最大内存值,而且内存管理上将外部内存完全当成了当前堆的一部分。也许我们可以这样理解,Bitmap对象通过栈上的引用来指向堆上的Bitmap对象,而Bitmap对象又对应了一个使用了外部存储的native图像,实际上使用的是byte[]来存储的内存空间,如下图:

    当然我们承认不好的程序总是程序员自己错误的写法导致的 ,不过我们倒是非常想知道如何来规避这个问题,那么接下来就是解答这个问题的关键。

    今天无意中看到stackoverflow上有人也曾经遇到过这个问题,而这个给了一个很好的解决方案,但他也不知道这个BUG该怎么解释:

    I had this same issue and solved it by avoiding the BitmapFactory.decodeStream or decodeFile functions and instead used BitmapFactory.decodeFileDescriptor

    decodeFileDescriptor looks like it calls different native methods than the decodeStream/decodeFile.

    Anyway what worked was this (note that I added some options as some had above, but that's not what made the difference. What is critical is the call to Bitmap.decodeFileDescriptor instead of decodeStream or decodeFile):

    private void showImage(String path)   {
        Log.i("showImage","loading:"+path);
        BitmapFactory.Options bfOptions=new BitmapFactory.Options();
        bfOptions.inDither=false;                     
        bfOptions.inPurgeable=true;                 
        bfOptions.inInputShareable=true;             
        bfOptions.inTempStorage=new byte[32 * 1024]; 
    
    
        File file=new File(path);
        FileInputStream fs=null;
        try {
            fs = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    
        try {
            if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
        } catch (IOException e) {
            e.printStackTrace();
        } finally{ 
            if(fs!=null) {
                try {
                    fs.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        im.setImageBitmap(bm);
        bm=null;
    }

    I think there is a problem with the native function used in decodeStream/decodeFile. I have confirmed that a different native method is called when using decodeFileDescriptor. Also what I've read is "that Images (Bitmaps) are not allocated in a standard Java way but via native calls; the allocations are done outside of the virtual heap, but are counted against it!"

    我拙劣的翻译了一下:

    我遇到了同样的问题,通过规避BitmapFactory.decodeStream或者decodeFile函数,使用BitmapFactory.decodeFileDescriptor解决的decodeFileDescriptor相比decodeStream/decodeFile来说,看起来它调用了不同的本地方法。无论如何,它是这样工作的(注意,像上边的一样,我增加了一些设置,但那不是使这个不同的地方。)关键的就是它调用Bitmap.decodeFileDescriptor而不是decodeStream or decodeFile)。

    我想这可能是decodeStream/decodeFile中本地函数的问题。我很确定当使用decodeFileDescriptor时一个不同的本地方法被调用。我读到的也是“图片(Bitmaps)并不是指派给一个标准的java路径,但是是通过本地调用的;这个分配是在虚拟的堆外完成的,但是是被认为针对它的!”。

    还是点到为止吧,大家都应该明白我的题目的意思了,最主要的是这个错误没办法去验证究竟什么地方发生了错误,但是可以规避这种错误,希望大家都自己去测试一下,验证一下,毕竟自己做过验证的才能算是放心的。

    参考资料:http://www.cnblogs.com/-OYK/archive/2012/12/03/2798903.html

  • 相关阅读:
    SQL Server 阻止了对组件 'Ole Automation Procedures' 的 过程'sys.sp_OACreate' 的访问
    谷歌浏览器扩展程序manifest.json参数详解
    获取天气api
    UVA 10385 Duathlon
    UVA 10668 Expanding Rods
    UVALIVE 3891 The Teacher's Side of Math
    UVA 11149 Power of Matrix
    UVA 10655 Contemplation! Algebra
    UVA 11210 Chinese Mahjong
    UVA 11384 Help is needed for Dexter
  • 原文地址:https://www.cnblogs.com/akira90/p/2960666.html
Copyright © 2011-2022 走看看