高效显示图片(四、五)

原文连接:http://developer.android.com/training/displaying-bitmaps/manage-memory.html

图片内存管理

除了缓存图片,你还可以做一些其他的事情来方便垃圾回收和再利用图片。推荐的策略取决
于设备的Android版本。
在Android 2.2和之前的Android版本中,当垃圾回收发生时,你的应用程序被停止。这将导致
程序滞后,可能会降低性能。Android 2.3增加了并发的垃圾回收,这意味着当一个图片的不
在被引用了,它所占用的内存会被立即回收。
在Android 2.3.3和较低的版本中,对于一个图片的像素数据被存储在本地内存中。而bitmap
对象本身被存储在Dalvik虚拟机的堆中。在本地内存中的像素数据不会以一种可预见的方式
被释放,可能会使得应用程序超过它的内存限制而导致崩溃。而到了Android 3.0,图片的像
素数据也被存储在Dalvik虚拟机的堆中。

在Android 2.3.3和低版本的系统中管理内存

在Android 2.3.3和低版本的系统中,推荐使用recycle()。如果你在app中显示了大量的bitmap
数据,你有可能会导致内存溢出错误。recycle方法使得应用程序尽快回收内存。

下面的代理使用引用计数器来跟踪,一个图片当前被显示或在缓存中。当满足下面的条件时,
bitmap将会被回收。
1.对于mDisplayRefCount和mCacheRefCount的引用数都为0.
2.bitmap不为null,且没有被回收。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private int mCacheRefCount = 0 ;
private int mDisplayRefCount = 0 ;
...
// Notify the drawable that the displayed state has changed.
// Keep a count to determine when the drawable is no longer displayed.
public void setIsDisplayed( boolean isDisplayed) {
    synchronized ( this ) {
         if (isDisplayed) {
             mDisplayRefCount++;
             mHasBeenDisplayed = true ;
         } else {
             mDisplayRefCount--;
         }
    }
    // Check to see if recycle() can be called.
    checkState();
}
 
// Notify the drawable that the cache state has changed.
// Keep a count to determine when the drawable is no longer being cached.
public void setIsCached( boolean isCached) {
    synchronized ( this ) {
         if (isCached) {
             mCacheRefCount++;
         } else {
             mCacheRefCount--;
         }
    }
    // Check to see if recycle() can be called.
    checkState();
}
 
private synchronized void checkState() {
    // If the drawable cache and display ref counts = 0, and this drawable
    // has been displayed, then recycle.
    if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
             && hasValidBitmap()) {
         getBitmap().recycle();
    }
}
 
private synchronized boolean hasValidBitmap() {
    Bitmap bitmap = getBitmap();
    return bitmap != null && !bitmap.isRecycled();
}


在Android 3.0之后的系统中管理内存

Android 3.0引入了BitmapFactory.Options.inBitmap域。如果这个选项被设置,解码方法在加
载内容时将试图重用一个存在的bitmap。这意味着bitmap的内存被重用,使得改善了性能,
消除了内存分配和回收。下面是使用inBitmap时需注意的事项:

1.重用的bitmap必须与源内容的大小相同,而且是JPEG和PNG的格式。
2.重用的bitmap将覆盖设置的inPreferredConfig。
3.你应该总是使用由decode方法返回的bitmap,因为你不能确定重用的bitmap是否能工作。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
HashSet<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> mMemoryCache;
 
// If you're running on Honeycomb or newer, create
// a HashSet of references to reusable bitmaps.
if (Utils.hasHoneycomb()) {
    mReusableBitmaps = new HashSet<SoftReference<Bitmap>>();
}
 
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
 
    // Notify the removed entry that is no longer being cached.
    @Override
    protected void entryRemoved( boolean evicted, String key,
             BitmapDrawable oldValue, BitmapDrawable newValue) {
         if (RecyclingBitmapDrawable. class .isInstance(oldValue)) {
             // The removed entry is a recycling drawable, so notify it
             // that it has been removed from the memory cache.
             ((RecyclingBitmapDrawable) oldValue).setIsCached( false );
         } else {
             // The removed entry is a standard BitmapDrawable.
             if (Utils.hasHoneycomb()) {
                // We're running on Honeycomb or later, so add the bitmap
                // to a SoftReference set for possible use with inBitmap later.
                mReusableBitmaps.add
                         ( new SoftReference<Bitmap>(oldValue.getBitmap()));
             }
         }
    }
....
}


使用一个存在的bitmap

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public static Bitmap decodeSampledBitmapFromFile(String filename,
         int reqWidth, int reqHeight, ImageCache cache) {
 
    final BitmapFactory.Options options = new BitmapFactory.Options();
    ...
    BitmapFactory.decodeFile(filename, options);
    ...
 
    // If we're running on Honeycomb or newer, try to use inBitmap.
    if (Utils.hasHoneycomb()) {
         addInBitmapOptions(options, cache);
    }
    ...
    return BitmapFactory.decodeFile(filename, options);
}

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private static void addInBitmapOptions(BitmapFactory.Options options,
         ImageCache cache) {
    // inBitmap only works with mutable bitmaps, so force the decoder to
    // return mutable bitmaps.
    options.inMutable = true ;
 
    if (cache != null ) {
         // Try to find a bitmap to use for inBitmap.
         Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
 
         if (inBitmap != null ) {
             // If a suitable bitmap has been found, set it as the value of
             // inBitmap.
             options.inBitmap = inBitmap;
         }
    }
}
 
// This method iterates through the reusable bitmaps, looking for one
// to use for inBitmap:
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
         Bitmap bitmap = null ;
 
    if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
         final Iterator<SoftReference<Bitmap>> iterator
                = mReusableBitmaps.iterator();
         Bitmap item;
 
         while (iterator.hasNext()) {
             item = iterator.next().get();
 
             if ( null != item && item.isMutable()) {
                // Check to see it the item can be used for inBitmap.
                if (canUseForInBitmap(item, options)) {
                     bitmap = item;
 
                     // Remove from reusable set so it can't be used again.
                     iterator.remove();
                     break ;
                }
             } else {
                // Remove from the set if the reference has been cleared.
                iterator.remove();
             }
         }
    }
    return bitmap;
}

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
private static boolean canUseForInBitmap(
         Bitmap candidate, BitmapFactory.Options targetOptions) {
    int width = targetOptions.outWidth / targetOptions.inSampleSize;
    int height = targetOptions.outHeight / targetOptions.inSampleSize;
 
    // Returns true if "candidate" can be used for inBitmap re-use with
    // "targetOptions".
    return candidate.getWidth() == width && candidate.getHeight() == height;
}


在UI中显示Bitmap

将Bitmap加载到ViewPager实现

一个交换的视图模式是浏览一个图片库详细信息视图很好的一个方式。你可以使用由PagerAdapter
和ViewPager来实现这种模式。然而,一个更合适的适配器是FragmentStatePagerAdapter,它
能自动销毁和保存在ViewPager中Fragments的状态。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ImageDetailActivity extends FragmentActivity {
    public static final String EXTRA_IMAGE = "extra_image" ;
 
    private ImagePagerAdapter mAdapter;
    private ViewPager mPager;
 
    // A static dataset to back the ViewPager adapter
    public final static Integer[] imageResIds = new Integer[] {
             R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
             R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
             R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.image_detail_pager); // Contains just a ViewPager
 
         mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length);
         mPager = (ViewPager) findViewById(R.id.pager);
         mPager.setAdapter(mAdapter);
    }
 
    public static class ImagePagerAdapter extends FragmentStatePagerAdapter {
         private final int mSize;
 
         public ImagePagerAdapter(FragmentManager fm, int size) {
             super (fm);
             mSize = size;
         }
 
         @Override
         public int getCount() {
             return mSize;
         }
 
         @Override
         public Fragment getItem( int position) {
             return ImageDetailFragment.newInstance(position);
         }
    }
}

下面是一个持有ImageView的详细Fragment的实现。这似乎是一个完全合理的做法,但你能
看到这种实现的缺点吗?如何进行改善?
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ImageDetailFragment extends Fragment {
    private static final String IMAGE_DATA_EXTRA = "resId" ;
    private int mImageNum;
    private ImageView mImageView;
 
    static ImageDetailFragment newInstance( int imageNum) {
         final ImageDetailFragment f = new ImageDetailFragment();
         final Bundle args = new Bundle();
         args.putInt(IMAGE_DATA_EXTRA, imageNum);
         f.setArguments(args);
         return f;
    }
 
    // Empty constructor, required as per Fragment docs
    public ImageDetailFragment() {}
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : - 1 ;
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         // image_detail_fragment.xml contains just an ImageView
         final View v = inflater.inflate(R.layout.image_detail_fragment, container, false );
         mImageView = (ImageView) v.findViewById(R.id.imageView);
         return v;
    }
 
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
         super .onActivityCreated(savedInstanceState);
         final int resId = ImageDetailActivity.imageResIds[mImageNum];
         mImageView.setImageResource(resId); // Load image into ImageView
    }
}

问题是:图片在UI线程中被读取。使用一个AsyncTask。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ImageDetailActivity extends FragmentActivity {
    ...
 
    public void loadBitmap( int resId, ImageView imageView) {
         mImageView.setImageResource(R.drawable.image_placeholder);
         BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
         task.execute(resId);
    }
 
    ... // include BitmapWorkerTask class
}
 
public class ImageDetailFragment extends Fragment {
    ...
 
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
         super .onActivityCreated(savedInstanceState);
         if (ImageDetailActivity. class .isInstance(getActivity())) {
             final int resId = ImageDetailActivity.imageResIds[mImageNum];
             // Call out to ImageDetailActivity to load the bitmap in a background thread
             ((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView);
         }
    }
}

任何额外的处理都能在BitmapWorkerTask中进行,这不会影响UI的响应。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ImageDetailActivity extends FragmentActivity {
    ...
    private LruCache<String, Bitmap> mMemoryCache;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
         ...
         // initialize LruCache as per Use a Memory Cache section
    }
 
    public void loadBitmap( int resId, ImageView imageView) {
         final String imageKey = String.valueOf(resId);
 
         final Bitmap bitmap = mMemoryCache.get(imageKey);
         if (bitmap != null ) {
             mImageView.setImageBitmap(bitmap);
         } else {
             mImageView.setImageResource(R.drawable.image_placeholder);
             BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
             task.execute(resId);
         }
    }
 
    ... // include updated BitmapWorkerTask from Use a Memory Cache section
}


将Bitmaps加载到GridView中的实现

网格列表是用于显示图像的数据集,可以用一个GridView组件实现,其中很多图片能够同时
显示在屏幕上,当用户向上或向下滑动时,更多的图片需要准备好被显示。当实现这种控制
时,你必须确保UI处于活动,内存的使用仍然处于控制,并发被正确处理。

下面是一个包含ImageView子视图置于Fragment中的一个标准GridView。这看起来是一个很好
的方式,但是否有更好的选择?
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
    private ImageAdapter mAdapter;
  
    // A static dataset to back the GridView adapter
    public final static Integer[] imageResIds = new Integer[] {
             R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
             R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
             R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};
  
    // Empty constructor as per Fragment docs
    public ImageGridFragment() {}
  
    @Override
    public void onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         mAdapter = new ImageAdapter(getActivity());
    }
  
    @Override
    public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         final View v = inflater.inflate(R.layout.image_grid_fragment, container, false );
         final GridView mGridView = (GridView) v.findViewById(R.id.gridView);
         mGridView.setAdapter(mAdapter);
         mGridView.setOnItemClickListener( this );
         return v;
    }
  
    @Override
    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
         final Intent i = new Intent(getActivity(), ImageDetailActivity. class );
         i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position);
         startActivity(i);
    }
  
    private class ImageAdapter extends BaseAdapter {
         private final Context mContext;
  
         public ImageAdapter(Context context) {
             super ();
             mContext = context;
         }
  
         @Override
         public int getCount() {
             return imageResIds.length;
         }
  
         @Override
         public Object getItem( int position) {
             return imageResIds[position];
         }
  
         @Override
         public long getItemId( int position) {
             return position;
         }
  
         @Override
         public View getView( int position, View convertView, ViewGroup container) {
             ImageView imageView;
             if (convertView == null ) { // if it's not recycled, initialize some attributes
                imageView = new ImageView(mContext);
                imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                imageView.setLayoutParams( new GridView.LayoutParams(
                         LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
             } else {
                imageView = (ImageView) convertView;
             }
             imageView.setImageResource(imageResIds[position]); // Load image into ImageView
             return imageView;
         }
    }
}

上面实现方式的问题是图片在UI线程中被设置。虽然这可能对小的、简单的图像是可以接受
的,但如果要做任何额外的处理,你的UI就会使人有停顿的感觉。
在这里可以加入异步处理和告诉缓存的方案。然而,你也需要警惕并发问题,GridView循环
回收子视图。为了解决这个问题,使用上一节中使用的技术:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
    ...
  
    private class ImageAdapter extends BaseAdapter {
         ...
  
         @Override
         public View getView( int position, View convertView, ViewGroup container) {
             ...
             loadBitmap(imageResIds[position], imageView)
             return imageView;
         }
    }
  
    public void loadBitmap( int resId, ImageView imageView) {
         if (cancelPotentialWork(resId, imageView)) {
             final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
             final AsyncDrawable asyncDrawable =
                     new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
             imageView.setImageDrawable(asyncDrawable);
             task.execute(resId);
         }
    }
  
    static class AsyncDrawable extends BitmapDrawable {
         private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
  
         public AsyncDrawable(Resources res, Bitmap bitmap,
                BitmapWorkerTask bitmapWorkerTask) {
             super (res, bitmap);
             bitmapWorkerTaskReference =
                new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
         }
  
         public BitmapWorkerTask getBitmapWorkerTask() {
             return bitmapWorkerTaskReference.get();
         }
    }
  
    public static boolean cancelPotentialWork( int data, ImageView imageView) {
         final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
  
         if (bitmapWorkerTask != null ) {
             final int bitmapData = bitmapWorkerTask.data;
             if (bitmapData != data) {
                // Cancel previous task
                bitmapWorkerTask.cancel( true );
             } else {
                // The same work is already in progress
                return false ;
             }
         }
         // No task associated with the ImageView, or an existing task was cancelled
         return true ;
    }
  
    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
       if (imageView != null ) {
            final Drawable drawable = imageView.getDrawable();
            if (drawable instanceof AsyncDrawable) {
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getBitmapWorkerTask();
            }
         }
         return null ;
    }
  
    ... // include updated BitmapWorkerTask class
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值