`
phenom
  • 浏览: 409852 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

(DOC)Displaying Bitmaps Efficiently 2

 
阅读更多
Processing Bitmaps Off the UI Thread
非ui线程处理位图。
BitmapFactory.decode*方法,在上一篇讨论过的,不应该在ui线程上处理的情况:从硬盘加载或从网络加载。因为加载时间未知,如果时间过久,会导致程序失去响应。
这章节是关于AsyncTask在后台处理图片的。
AsyncTask类提供了一个简易的方法处理后台事务,并通知ui线程。使用它需要创建一个子类,覆盖一些方法这里举一个加载图片到ImageView的例子:
class BitmapWorkerTask extends AsyncTask {
    private final WeakReference imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference(imageView);
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}
WeakReference是为了避免ImageView被回收时由于引用造成无法回收。所以多次判断是否为null值。这种为空的情况如Activity已经到了其它Activity,或配置变化了。
加载图片就简单了:
public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

Handle Concurrency:
ListView,GridView是另一个麻烦的地方,为了有效地使用内存,这些组件会在用户滚动时回收一些子View,如果每一个View都触发一个AsyncTask,不能保证在操作完成时,相关的View还存在。它可能被回收了
http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html 更详细地说明了并发的问题,提供了一个解决办法。存储最近的AsyncTask。

提供专用的Drawable子类来存储task,
static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}
在执行BitmapWorkerTask时,先创建一个AsyncDrawable,绑定到相关的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);
    }
}
cancelPotentialWork方法就是检查是否关联的task已经在运行了。它先调用cancel()结束先前的方法,
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;
}
getBitmapWorkerTask这个方法用于关联特定的ImageView
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;
}
最后一步是在onPostExecute()确认是否任务结束了和当前的关联ImageView匹配:
class BitmapWorkerTask extends AsyncTask {
    ...

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

这个实现可以用于listview,gridview这样回收他们的子元素的组件中,只要简单地调用loadBitmap方法就可以了。

这里介绍的方法可能的问题是导致很多的线程创建,销毁,这也算是一个问题吧。

Caching Bitmaps 缓存图片
加载单张图片到ui中,比较容易,更复杂一些,一次加载一系列图片。这种情况屏幕上的图片可能通过滚动,不再显示。
一些组件通过回收子元素来回收内存,垃圾回收器释放你已经加载的位图,非长期的引用。这是必须的,但无法提供流畅的体验,你需要避免一直处理这些图片,把它们 贴到屏幕上,内存或硬盘的缓存就可以提供一些帮助了。

Use a Memory Cache
内存缓存提供位图的快速访问,LruCache类(在Support Library中也可用的)
适合缓存位图,保持最近的引用对象,是强引用,清除早期的对象。
SoftReference or WeakReference在之前的版本最好使用这样的引用保存位图,便于回收,3.0版本以前位图是存储在本地内存中的,不容易回收。
不管是哪个版本,都应该使用软引用或弱弱引用,(据说软引用更适合,但这里用弱引用)

对于LruCache的大小选择,有几个因素需要参考的:
你的应用剩下部分需要多数内存?
你需要一次加载多少图片到屏幕上?
屏幕的大小与设备的解析度,高解析度的设备xhdpi像Nexus相比Galaxy S hdpi需要大的缓存来保存相同数量的图片。
维度与配置决定了位图的占用资源的多少。
图片的访问频率。一些比较常用到,另一些不常用,需要保持一些常用的在缓存中,或建多个LruCache对象来存储图片。
在数量与质量间平衡, 有时缓存大量的低分辨率的图片,而加载大图是用后台线程来处理。
对所有没有固定统一的规则,需要自己分析处理。缓存的大小需要自己试验,不同的系统不同的手机需要适配,找到一个合适的值。

下面提供一个使用LruCache的例子:
private LruCache mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get memory class of this device, exceeding this amount will throw an
    // OutOfMemory exception.
    final int memClass = ((ActivityManager) context.getSystemService(
            Context.ACTIVITY_SERVICE)).getMemoryClass();

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = 1024 * 1024 * memClass / 8;

    mMemoryCache = new LruCache(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in bytes rather than number of items.
            return bitmap.getByteCount();
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}
这个例子中假设hdpi的设备中最小值是4m(32/8),全屏的图片在800*480分辨率中需要消耗1.5m(800*480*4),所以缓存了2.5页图片。

当加载图片时,LruCache会先检查,然后没有才加载其它的图片
public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}
BitmapWorkerTask的更新版本:
class BitmapWorkerTask extends AsyncTask {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}

Use a Disk Cache 磁盘缓存。
内存缓存更快速,当然不能仅靠内存来维持,listview,gridview会很快地占用了内存中的图片,而且你的Activity可能被销毁,然后再加载,这时就需要另一处缓存了。

磁盘缓存主要在下载图片时用到,缓存后不用再次下载,从磁盘中加载当然比从网络中要快得多了
DiskLruCache已经是一种健壮的实现 了,在4.0中提供了源码libcore/luni/src/main/java/libcore/io/DiskLruCache.java,
看个例子:
private DiskLruCache mDiskCache;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Initialize memory cache
    ...
    File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);
    mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);
    ...
}

class BitmapWorkerTask extends AsyncTask {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);

        // Check disk cache in background thread
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);

        if (bitmap == null) { // Not found in disk cache
            // Process as normal
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }

        // Add final bitmap to caches
        addBitmapToCache(String.valueOf(imageKey, bitmap);

        return bitmap;
    }
    ...
}

public void addBitmapToCache(String key, Bitmap bitmap) {
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }

    // Also add to disk cache
    if (!mDiskCache.containsKey(key)) {
        mDiskCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromDiskCache(String key) {
    return mDiskCache.get(key);
}

// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getCacheDir(Context context, String uniqueName) {
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir
    // otherwise use internal cache dir
    final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
            || !Environment.isExternalStorageRemovable() ?
                    context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);
}
虽然这里没有看到DiskLruCache的源码,但是可以想像得出,前面的章节已经了如何加载图片了。
内存缓存在ui线程中检查,磁盘缓存在后台线程中使用。

未完待续



分享到:
评论

相关推荐

    Displaying Bitmaps Efficiently

    "Displaying Bitmaps Efficiently"这一主题是Android 6.0官方开发平台上的重要内容,旨在帮助开发者避免内存溢出、提高应用响应速度以及优化用户体验。 1. **Loading Large Bitmaps Efficiently** - **Read ...

    DisplayingBitmaps.zip

    Android 开发者网站上 Training 部分,Building Apps with Graphics & Animation -> Displaying Bitmaps Efficiently部分的实例 demo

    DisplayingBitmaps

    "DisplayingBitmaps"这个主题主要关注如何有效地缓存和管理Bitmap以避免这样的问题。Google官方提供了一些最佳实践,包括使用LruCache和DiskCache来优化内存使用。 **LruCache** LruCache(Least Recently Used ...

    android-DisplayingBitmaps:已迁移:

    "android-DisplayingBitmaps"项目是专门为解决这些问题而设计的一个示例库,它已被迁移到一个新的仓库,意味着它可能包含了最新的优化技术和最佳实践。这个库主要关注如何高效地在Android应用中加载、解码和显示位图...

    AndroidMediaDB:此代码取自 Android 中的 DisplayingBitmaps Sample。 它显示Android手机上的所有图像和视频,组织到文件夹中(与图库应用程序相同)

    Android 媒体数据库示例(原始 DisplayingBitmaps 示例 ( ))当前版本 1.0 Beta 示例演示如何从主 UI 线程有效地从 Android sd 卡(图库)加载大型位图、缓存位图(在内存和磁盘中)、管理位图内存并在 UI 元素 - ...

    google官方加载图片处理demo

    这个示例项目名为"DisplayingBitmaps",包含了各种实用技巧和最佳实践。 首先,我们要理解Android系统对内存管理的敏感性,特别是在低内存设备上。Android的Bitmap类用于表示图像,但如果不妥善处理,可能导致内存...

    谷歌RecyclingImageView加载图片示例

    在Android开发中,内存管理是优化应用性能的关键...通过对DisplayingBitmaps示例的学习,开发者可以更好地理解和掌握RecyclingImageView的工作机制,从而在自己的应用中有效地管理图片资源,提高应用的性能和用户体验。

    A_centralized_system_for_displaying_and_stylizing__

    A_centralized_system_for_displaying_and_stylizing__聚焦聚焦_A_centralized_system_for_displaying_and_stylizing__focus-rings.zip_A_centralized_system_for_displaying_and_stylizing__focus-rings

    MFC-Project-Gif-displaying

    本项目"MFC-Project-Gif-displaying"的目标是通过MFC源码来实践显示Gif图像的功能。 首先,我们来看关键的源文件: 1. **PictureEx.cpp**:这个文件可能包含了处理图像显示的核心功能,尤其是针对Gif格式的解析和...

    Simple java library for displaying dates as relative time .zip

    在Java编程领域,有时我们需要将日期显示为相对于当前时间的相对时间,比如“2分钟前”、“1小时前”等。这个“Simple java library for displaying dates as relative time .zip”文件提供了一个简洁的解决方案,...

    Calculating and Displaying Fatigue Results

    ### 计算与显示疲劳结果 #### 引言 疲劳分析是材料工程中一个非常重要的领域,它关注的是材料在循环载荷作用下发生破坏的可能性。ANSYS疲劳模块提供了广泛的功能来执行计算并呈现分析结果。本文将详细介绍该模块...

    ScrollBitmap_demo.zip_bitmap dialog_dialog bitmap_image_停车场_显示位图

    Check out this article for displaying large bitmaps into the desired area of your dialog box in its original size with a scrolling technique used to show the entire bitmap. 滚动显示位图 在VC++环境下...

    A slider widget with a popup bubble displaying the precise value selected..zip

    A slider widget with a popup bubble displaying the precise value selected..zip,A slider widget with a popup bubble displaying the precise value selected. Android library made by @Ramotion - ...

    Displaying Data on a PC

    ##### 2. 串口通信基础 串行通信是一种数据传输方式,其中数据按位顺序传输。在本教程中,我们将使用串口连接传感器节点与PC,以便于传输数据。确保PC上安装了Java和`javax.comm`包,这是进行串口通信所必需的。 #...

    C# Displaying Triangles.docx

    2. 第二个三角形是与第一个相反的下三角形,由星号(*)组成,逐渐减少每一行的星号数量。这里同样使用了两个嵌套的for循环,但内层循环的条件相反,`j`从0递增到`10-i`,使得星号数逐渐减少。 ```csharp for (i = 0;...

    A website for updating and displaying the CBDL tag game data

    标题中的"A website for updating and displaying the CBDL tag game data"表明我们要讨论的是一个专门为更新和展示CBDL(可能是一个特定游戏或联赛的缩写)标签游戏数据而设计的网站。这个网站可能是为了实时追踪...

    Chat User Control with WebBrowser Control as Displaying Engine

    "Chat User Control with WebBrowser Control as Displaying Engine" 是一个技术主题,主要探讨如何利用Windows Forms中的WebBrowser控件作为聊天用户界面的显示引擎。这个标题暗示了一个自定义的用户控件(User ...

Global site tag (gtag.js) - Google Analytics