Android OOM的那些事


Context部分

View的构造函数

View.java

  1. /** 
  2.  
  3.      * Simple constructor to use when creating a view from code. 
  4.  
  5.      * 
  6.  
  7.      * @param context The Context the view is running in, through which it can 
  8.  
  9.      *        access the current theme, resources, etc. 
  10.  
  11.      */ 
  12.  
  13.     public View(Context context) { 
  14.  
  15.         mContext = context; 
  16.  
  17.         mResources = context != null ? context.getResources() : null
  18.  
  19.         mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; 
  20.  
  21.         // Used for debug only 
  22.  
  23.         //++sInstanceCount; 
  24.  
  25.         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 
  26.  

View 保存了对context的引用,mContext = context;

1.我们知道结构其实和DOM差不多,都会保存其父容器、子容器的引用,因而context的使用需要注意,不要使用静态的子View

2.来看View中的setBackgroundDrawable方法

View.java

 

  1. /** 
  2.  
  3.      * Set the background to a given Drawable, or remove the background. If the 
  4.  
  5.      * background has padding, this View's padding is set to the background's 
  6.  
  7.      * padding. However, when a background is removed, this View's padding isn't 
  8.  
  9.      * touched. If setting the padding is desired, please use 
  10.  
  11.      * {@link #setPadding(int, int, int, int)}. 
  12.  
  13.      * 
  14.  
  15.      * @param d The Drawable to use as the background, or null to remove the 
  16.  
  17.      *        background 
  18.  
  19.      */ 
  20.  
  21.     public void setBackgroundDrawable(Drawable d) { 
  22.  
  23.         boolean requestLayout = false
  24.  
  25.   
  26.  
  27.         mBackgroundResource = 0
  28.  
  29.   
  30.  
  31.         /* 
  32.  
  33.          * Regardless of whether we're setting a new background or not, we want 
  34.  
  35.          * to clear the previous drawable. 
  36.  
  37.          */ 
  38.  
  39.         if (mBGDrawable != null) { 
  40.  
  41.             mBGDrawable.setCallback(null); 
  42.  
  43.             unscheduleDrawable(mBGDrawable); 
  44.  
  45.         } 
  46.  
  47.   
  48.  
  49.         if (d != null) { 
  50.  
  51.             Rect padding = sThreadLocal.get(); 
  52.  
  53.             if (padding == null) { 
  54.  
  55.                 padding = new Rect(); 
  56.  
  57.                 sThreadLocal.set(padding); 
  58.  
  59.             } 
  60.  
  61.             if (d.getPadding(padding)) { 
  62.  
  63.                 setPadding(padding.left, padding.top, padding.right, padding.bottom); 
  64.  
  65.             } 
  66.  
  67.   
  68.  
  69.             // Compare the minimum sizes of the old Drawable and the new.  If there isn't an old or 
  70.  
  71.             // if it has a different minimum size, we should layout again 
  72.  
  73.             if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() || 
  74.  
  75.                     mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) { 
  76.  
  77.                 requestLayout = true
  78.  
  79.             } 
  80.  
  81.   
  82.  
  83.             d.setCallback(this); 
  84.  
  85.             if (d.isStateful()) { 
  86.  
  87.                 d.setState(getDrawableState()); 
  88.  
  89.             } 
  90.  
  91.             d.setVisible(getVisibility() == VISIBLE, false); 
  92.  
  93.             mBGDrawable = d; 
  94.  
  95.   
  96.  
  97.             if ((mPrivateFlags & SKIP_DRAW) != 0) { 
  98.  
  99.                 mPrivateFlags &= ~SKIP_DRAW; 
  100.  
  101.                 mPrivateFlags |= ONLY_DRAWS_BACKGROUND; 
  102.  
  103.                 requestLayout = true
  104.  
  105.             } 
  106.  
  107.         } else { 
  108.  
  109.             /* Remove the background */ 
  110.  
  111.             mBGDrawable = null
  112.  
  113.   
  114.  
  115.             if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) { 
  116.  
  117.                 /* 
  118.  
  119.                  * This view ONLY drew the background before and we're removing 
  120.  
  121.                  * the background, so now it won't draw anything 
  122.  
  123.                  * (hence we SKIP_DRAW) 
  124.  
  125.                  */ 
  126.  
  127.                 mPrivateFlags &= ~ONLY_DRAWS_BACKGROUND; 
  128.  
  129.                 mPrivateFlags |= SKIP_DRAW; 
  130.  
  131.             } 
  132.  
  133.   
  134.  
  135.             /* 
  136.  
  137.              * When the background is set, we try to apply its padding to this 
  138.  
  139.              * View. When the background is removed, we don't touch this View's 
  140.  
  141.              * padding. This is noted in the Javadocs. Hence, we don't need to 
  142.  
  143.              * requestLayout(), the invalidate() below is sufficient. 
  144.  
  145.              */ 
  146.  
  147.   
  148.  
  149.             // The old background's minimum size could have affected this 
  150.  
  151.             // View's layout, so let's requestLayout 
  152.  
  153.             requestLayout = true
  154.  
  155.         } 
  156.  
  157.   
  158.  
  159.         computeOpaqueFlags(); 
  160.  
  161.   
  162.  
  163.         if (requestLayout) { 
  164.  
  165.             requestLayout(); 
  166.  
  167.         } 
  168.  
  169.   
  170.  
  171.         mBackgroundSizeChanged = true
  172.  
  173.         invalidate(); 
  174.  
  175.     } 

我们注意到d.setCallback(this);

Drawable保存了View的引用,处理了一些回调。因此,Drawable对象使用时需要注意了。不要作为静态对象使用。

 

Resource.getDrewable()方法是比较好的,Resource内部使用了一个Drawable.ConstantState的弱引用缓存,提高了效率。

来看代码:

Resource.java

 

  1. /*package*/ Drawable loadDrawable(TypedValue value, int id) 
  2.  
  3.             throws NotFoundException { 
  4.  
  5.   
  6.  
  7.         if (TRACE_FOR_PRELOAD) { 
  8.  
  9.             // Log only framework resources 
  10.  
  11.             if ((id >>> 24) == 0x1) { 
  12.  
  13.                 final String name = getResourceName(id); 
  14.  
  15.                 if (name != null) Android.util.Log.d("PreloadDrawable", name); 
  16.  
  17.             } 
  18.  
  19.         } 
  20.  
  21.   
  22.  
  23.         final long key = (((long) value.assetCookie) << 32) | value.data; 
  24.  
  25.         Drawable dr = getCachedDrawable(key); 
  26.  
  27.   
  28.  
  29.         if (dr != null) { 
  30.  
  31.             return dr; 
  32.  
  33.         } 
  34.  
  35.   
  36.  
  37.         Drawable.ConstantState cs = sPreloadedDrawables.get(key); 
  38.  
  39.         if (cs != null) { 
  40.  
  41.             dr = cs.newDrawable(this); 
  42.  
  43.         } else { 
  44.  
  45.             if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && 
  46.  
  47.                     value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 
  48.  
  49.                 dr = new ColorDrawable(value.data); 
  50.  
  51.             } 
  52.  
  53.   
  54.  
  55.             if (dr == null) { 
  56.  
  57.                 if (value.string == null) { 
  58.  
  59.                     throw new NotFoundException( 
  60.  
  61.                             "Resource is not a Drawable (color or path): " + value); 
  62.  
  63.                 } 
  64.  
  65.   
  66.  
  67.                 String file = value.string.toString(); 
  68.  
  69.   
  70.  
  71.                 if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie " 
  72.  
  73.                         + value.assetCookie + ": " + file); 
  74.  
  75.   
  76.  
  77.                 if (file.endsWith(".xml")) { 
  78.  
  79.                     try { 
  80.  
  81.                         XmlResourceParser rp = loadXmlResourceParser( 
  82.  
  83.                                 file, id, value.assetCookie, "drawable"); 
  84.  
  85.                         dr = Drawable.createFromXml(this, rp); 
  86.  
  87.                         rp.close(); 
  88.  
  89.                     } catch (Exception e) { 
  90.  
  91.                         NotFoundException rnf = new NotFoundException( 
  92.  
  93.                             "File " + file + " from drawable resource ID #0x" 
  94.  
  95.                             + Integer.toHexString(id)); 
  96.  
  97.                         rnf.initCause(e); 
  98.  
  99.                         throw rnf; 
  100.  
  101.                     } 
  102.  
  103.   
  104.  
  105.                 } else { 
  106.  
  107.                     try { 
  108.  
  109.                         InputStream is = mAssets.openNonAsset( 
  110.  
  111.                                 value.assetCookie, file, AssetManager.ACCESS_STREAMING); 
  112.  
  113.         //                System.out.println("Opened file " + file + ": " + is); 
  114.  
  115.                         dr = Drawable.createFromResourceStream(this, value, is, 
  116.  
  117.                                 file, null); 
  118.  
  119.                         is.close(); 
  120.  
  121.         //                System.out.println("Created stream: " + dr); 
  122.  
  123.                     } catch (Exception e) { 
  124.  
  125.                         NotFoundException rnf = new NotFoundException( 
  126.  
  127.                             "File " + file + " from drawable resource ID #0x" 
  128.  
  129.                             + Integer.toHexString(id)); 
  130.  
  131.                         rnf.initCause(e); 
  132.  
  133.                         throw rnf; 
  134.  
  135.                     } 
  136.  
  137.                 } 
  138.  
  139.             } 
  140.  
  141.         } 
  142.  
  143.   
  144.  
  145.         if (dr != null) { 
  146.  
  147.             dr.setChangingConfigurations(value.changingConfigurations); 
  148.  
  149.             cs = dr.getConstantState(); 
  150.  
  151.             if (cs != null) { 
  152.  
  153.                 if (mPreloading) { 
  154.  
  155.                     sPreloadedDrawables.put(key, cs); 
  156.  
  157.                 } else { 
  158.  
  159.                     synchronized (mTmpValue) { 
  160.  
  161.                         //Log.i(TAG, "Saving cached drawable @ #" + 
  162.  
  163.                         //        Integer.toHexString(key.intValue()) 
  164.  
  165.                         //        + " in " + this + ": " + cs); 
  166.  
  167.                         mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs)); 
  168.  
  169.                     } 
  170.  
  171.                 } 
  172.  
  173.             } 
  174.  
  175.         } 
  176.  
  177.   
  178.  
  179.         return dr; 
  180.  
  181.     } 

使用Drawable一般情况下效率较高,且不易发生内存泄露。

 

接下来我们来看下BitMap的使用

 

BitMapFactory.java

 

  1. /** 
  2.  
  3.      * Decode an input stream into a bitmap. If the input stream is null, or 
  4.  
  5.      * cannot be used to decode a bitmap, the function returns null. 
  6.  
  7.      * The stream's position will be where ever it was after the encoded data 
  8.  
  9.      * was read. 
  10.  
  11.      * 
  12.  
  13.      * @param is The input stream that holds the raw data to be decoded into a 
  14.  
  15.      *           bitmap. 
  16.  
  17.      * @param outPadding If not null, return the padding rect for the bitmap if 
  18.  
  19.      *                   it exists, otherwise set padding to [-1,-1,-1,-1]. If 
  20.  
  21.      *                   no bitmap is returned (null) then padding is 
  22.  
  23.      *                   unchanged. 
  24.  
  25.      * @param opts null-ok; Options that control downsampling and whether the 
  26.  
  27.      *             image should be completely decoded, or just is size returned. 
  28.  
  29.      * @return The decoded bitmap, or null if the image data could not be 
  30.  
  31.      *         decoded, or, if opts is non-null, if opts requested only the 
  32.  
  33.      *         size be returned (in opts.outWidth and opts.outHeight) 
  34.  
  35.      */ 
  36.  
  37.     public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { 
  38.  
  39.         // we don't throw in this case, thus allowing the caller to only check 
  40.  
  41.         // the cache, and not force the image to be decoded. 
  42.  
  43.         if (is == null) { 
  44.  
  45.             return null
  46.  
  47.         } 
  48.  
  49.   
  50.  
  51.         // we need mark/reset to work properly 
  52.  
  53.   
  54.  
  55.         if (!is.markSupported()) { 
  56.  
  57.             is = new BufferedInputStream(is, 16 * 1024); 
  58.  
  59.         } 
  60.  
  61.   
  62.  
  63.         // so we can call reset() if a given codec gives up after reading up to 
  64.  
  65.         // this many bytes. FIXME: need to find out from the codecs what this 
  66.  
  67.         // value should be. 
  68.  
  69.         is.mark(1024); 
  70.  
  71.   
  72.  
  73.         Bitmap  bm; 
  74.  
  75.   
  76.  
  77.         if (is instanceof AssetManager.AssetInputStream) { 
  78.  
  79.             bm = nativeDecodeAsset(((AssetManager.AssetInputStream) is).getAssetInt(), 
  80.  
  81.                     outPadding, opts); 
  82.  
  83.         } else { 
  84.  
  85.             // pass some temp storage down to the native code. 1024 is made up, 
  86.  
  87.             // but should be large enough to avoid too many small calls back 
  88.  
  89.             // into is.read(...) This number is not related to the value passed 
  90.  
  91.             // to mark(...) above. 
  92.  
  93.             byte [] tempStorage = null
  94.  
  95.             if (opts != null
  96.  
  97.                 tempStorage = opts.inTempStorage; 
  98.  
  99.             if (tempStorage == null
  100.  
  101.                 tempStorage = new byte[16 * 1024]; 
  102.  
  103.             bm = nativeDecodeStream(is, tempStorage, outPadding, opts); 
  104.  
  105.         } 
  106.  
  107.   
  108.  
  109.         return finishDecode(bm, outPadding, opts); 
  110.  
  111.     } 

我们每次调用BitMapFactory中的生产bitmap的方法的时候都会new一个bitmap出来,为了避免内存溢出,我们有两种主要的思路:

1.       使用缓存,避免重复new同一个图片

2.       销毁bitmap,使用完之后立刻销毁bitmap

缓存需要自己实现,如果是销毁的话,可以使用Bitmap中的recycle()方法。

更多Android相关信息见Android 专题页面 http://www.bkjia.com/topicnews.aspx?tid=11

相关内容