• 侯西阳的博客

  • ——记录我的生活。
    • post by 侯西阳 / 2012-5-14 0:15 Monday

      做公司的android应用,功能很简单,ViewFlipper中用GestureDetector实现滑动切换图片。重写onFling() 方法,判断手指滑动方向,将Bitmap加载到一个imageView中,在调用viewFlipper的addView()方法,再将imageView加载到viewFlipper中。

       最不好解决的就是Bitmap的Out of Memory问题,加载很多图之后会报内存溢出的错误。

      关于这个问题的,网上搜了很多解决方案,没有很好的解决方法,恰巧碰到群里也有人遇到过这种问题,现将我找到的解决方法总结如下,后面我会给出自己的解决方案。

      1. 设置BitmapFactory的options

      代码如下:

      BitmapFactory.Options options = new BitmapFactory.Options();
      options.inSampleSize = 3;
      Bitmap bitmap = BitmapFactory.decodeStream(is, null, ops);

      设置的inSampleSize就是将图片的长宽压缩,设置为3意味着,生成的图片长宽各为原长度的1/3。

      这个方法只是对性能的一种优化,并没有从根本上解决内存的问题,当加载的Bitmap过多时,依然会有内存溢出的问题。


      2. 使用SoftReference实现图片的缓存

      代码不贴了,这里有详细的实现方式,以及关于软引用的介绍:

      http://www.cnblogs.com/dwinter/archive/2012/01/30/2331556.html

      实现的原理就是建立一个softreference用来存放图片和索引,因为softreference容易被GC回收,从而释放占用的内存。

      但是亲自实现下来并没有很好的解决OOM的问题。个人谨慎猜测是因为viewFlipper占用着包含Bitmap的imageView,所以引用一直存在,GC并不会自动回收。


      3. 调用Bitmap的recycle( )方法回收Bitmap

      判断图片id号为current-2和current+2的Bitmap的isRecycled( ),如果没有被回收的话,调用recycle( )强制回收。

      其实这个方法效果很好,只是当想但会浏览图片时会出现“试图加载已经被回收的Bitmap“的错误。

      我自认为程序写的没什么问题(估计还是有问题),最后就放弃了这个方法。


      4. 建立LruCache使图片的加载更有效率

      这个方法在谷歌的官方文档上是有的,链接如下:

      http://developer.android.com/training/displaying-bitmaps/index.html

      实现的原理就是建立一个LruCache,这样可以对图片有效率的显示和利用。

      第二种方法提到过softReference,这个文章中是不推荐这样做的,具体原因可以详细的阅读以下。

      LruCache是在API Level 11中加入的,那Android 2.2 神马的怎么办?没关系,手动写一个LruCache,或者找一个。我贴一个出来吧。

      /**
       * Copyright (C) 2009 The Android Open Source Project
       *
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *      http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      
      package android.guju.service;
      
      import java.lang.ref.ReferenceQueue;
      import java.lang.ref.WeakReference;
      import java.util.HashMap;
      import java.util.LinkedHashMap;
      import java.util.Map;
      
      /***
       * An LRU cache which stores recently inserted entries and all entries ever
       * inserted which still has a strong reference elsewhere.
       */
      public class LruCache<K, V> {
      
          private final HashMap<K, V> mLruMap;
          private final HashMap<K, Entry<K, V>> mWeakMap =
                  new HashMap<K, Entry<K, V>>();
          private ReferenceQueue<V> mQueue = new ReferenceQueue<V>();
      
          @SuppressWarnings("serial")
          public LruCache(final int capacity) {
              mLruMap = new LinkedHashMap<K, V>(16, 0.75f, true) {
                  @Override
                  protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                      return size() > capacity;
                  }
              };
          }
      
          private static class Entry<K, V> extends WeakReference<V> {
              K mKey;
      
              public Entry(K key, V value, ReferenceQueue<V> queue) {
                  super(value, queue);
                  mKey = key;
              }
          }
      
          @SuppressWarnings("unchecked")
          private void cleanUpWeakMap() {
              Entry<K, V> entry = (Entry<K, V>) mQueue.poll();
              while (entry != null) {
                  mWeakMap.remove(entry.mKey);
                  entry = (Entry<K, V>) mQueue.poll();
              }
          }
      
          public synchronized V put(K key, V value) {
              cleanUpWeakMap();
              mLruMap.put(key, value);
              Entry<K, V> entry = mWeakMap.put(
                      key, new Entry<K, V>(key, value, mQueue));
              return entry == null ? null : entry.get();
          }
      
          public synchronized V get(K key) {
              cleanUpWeakMap();
              V value = mLruMap.get(key);
              if (value != null) return value;
              Entry<K, V> entry = mWeakMap.get(key);
              return entry == null ? null : entry.get();
          }
      
          public synchronized void clear() {
              mLruMap.clear();
              mWeakMap.clear();
              mQueue = new ReferenceQueue<V>();
          }
      }

      可以直接拿来用哦亲~不过记得留着人家的版权声明,这是最起码的。


      其实我试过以上所有的方法,问题还没解决——即使是在建立一个LruCache之后。

      在stackoverflow上提过问题之后,有个人的回答是要调用removeView( ) 释放Bitmap。

      恍然大悟啊!好了,问题解决了。

      imageView.setImageBitmap(bitmap);
      imageView.setScaleType(ImageView.ScaleType.CENTER);
      viewFlipper.removeAllViews();
      viewFlipper.addView(imageView);

      每次addView之前,remove掉之前的view就可以了。

      当然,设置BitmapFactory的options以及建立LruCache也是很必须的,能够更好的提供图片浏览的体验。

      不过产生一个新问题就是添加Animation的问题,如果直接调用removeView的话,那个view的动画效果就不容易设置。


      希望对用到的人有些帮助。