Android手机联系人(Contact)列表缩略图(大头贴)的加载过程-基于7.1版本

1,215 阅读5分钟

做Android手机联系人定制也有2年多了,最近忽然发现原来自己每天看的代码自己并不熟悉,或者说一知半解,没有深入明白其中的实现机制或者说原理吧, 混迹江湖, 脸皮得厚, 如果有不好的地方, 轻喷, 欢迎指正!

下面进入正题!

说到联系人列表头像加载, 必提类ContactPhotoManager.java, 接下来请看Android原生对于此类功能的英文说明:

Asynchronously loads contact photos and maintains a cache of photos. (翻译: 异步加载联系人头像和维护照片缓存)

该类的四大功能如下:

  • 预加载
  • 异步查询
  • Bitmap二级缓存
  • 将Bitmap显示到ImageView

下面来说说与ContactPhotoManager类相关的几个重要类

(下图是自己手动画的, 如果觉得难以理解, 请看Android源码):

接下来请看ContactPhotoManager中获取实例代码:

    public static ContactPhotoManager getInstance(Context context) {
        if (sInstance == null) {
            //获取ApplicationContext
            Context applicationContext = context.getApplicationContext();
            //新建ContactPhotoManagerImpl对象
            sInstance = createContactPhotoManager(applicationContext);
            //注册回调, 帮助应用程序更有效地管理使用内存
            applicationContext.registerComponentCallbacks(sInstance);
            //判断是否有读联系人权限
            if (PermissionsUtil.hasContactsPermissions(context)) {
                //开启预加载
                sInstance.preloadPhotosInBackground();
            }
        }
        return sInstance;
    }

    public static synchronized ContactPhotoManager createContactPhotoManager(Context context) {
        return new ContactPhotoManagerImpl(context);
    }

这段代码功能:创建ContactPhotoManagerImpl对象, 开启预加载, 提高界面显示速度.

下面说说异步任务和UI线程的交互:

第一个是ContactPhotoManagerImpl中定义的 mMainThreadHandler, 定义及注释如下:

/**
 * Handler for messages sent to the UI thread.
 */
private final Handler mMainThreadHandler = new Handler(this);

第二个是LoaderThread中定义的mLoaderThreadHandler, 声明及初始化函数如下:

private Handler mLoaderThreadHandler;
public void ensureHandler() {
    if (mLoaderThreadHandler == null) {
        mLoaderThreadHandler = new Handler(getLooper(), this);
    }
}

为什么要提到上面两个Handler呢? 因为这两个Handler 实现了主线程(UI thread) 和 子线程(联系人头像加载线程LoaderThread) 之间的互相通信. 具体实施如下:

从子线程到UI线程, 在LoaderThread 中我们找到下面2个函数:

/** Loads thumbnail photos with ids */
private void loadThumbnails(boolean preloading) {
    /**此处省略掉从数据库中加载联系人缩略图并缓存的代码*/
    mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
}

/** Loads photos referenced with Uris*/
private void loadUriBasedPhotos() {
    /**此处省略掉根据Uris从网络或者本地加载联系人头像并缓存的代码*/
    mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
}

上面这段代码中两个函数是运行在异步任务中的,在子线程加载完头像之后通过mMainThreadHandler向UI线程发送MESSAGE_PHOTOS_LOADED消息,接着UI线程开始执行ContactPhotoManagerImpl中的handleMessage(Message msg)开始将已经加载缓存的头像显示到ImageView.

从UI线程到子线程, 请看ContactPhotoManagerImpl.LoaderThread中的如下函数:

        public void requestPreloading() {
            if (mPreloadStatus == PRELOAD_STATUS_DONE) {
                return;
            }

            ensureHandler();
            if (mLoaderThreadHandler.hasMessages(MESSAGE_LOAD_PHOTOS)) {
                return;
            }

            mLoaderThreadHandler.sendEmptyMessageDelayed(
                    MESSAGE_PRELOAD_PHOTOS, PHOTO_PRELOAD_DELAY);
        }

        public void requestLoading() {
            ensureHandler();
            mLoaderThreadHandler.removeMessages(MESSAGE_PRELOAD_PHOTOS);
            mLoaderThreadHandler.sendEmptyMessage(MESSAGE_LOAD_PHOTOS);
        }

        /**
         * Receives the above message, loads photos and then sends a message to the main thread to process them.
         */
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_PRELOAD_PHOTOS:
                    preloadPhotosInBackground();
                    break;
                case MESSAGE_LOAD_PHOTOS:
                    loadPhotosInBackground();
                    break;
            }
            return true;
        }

上面这段代码中requestPreloading()和requestLoading()函数是运行在UI线程中的, 接着子线程开始处理接收到的Message, 加载Photos.

下面来说说Bitmap二级缓存: 二级缓存主要通过ContactPhotoManagerImpl中的mBitmapHolderCache和mBitmapCache两个变量定义(重点请看注释):

    /**
     * An LRU cache for bitmap holders. The cache contains bytes for photos just
     * as they come from the database. Each holder has a soft reference to the
     * actual bitmap.
     */
    private final LruCache<Object, BitmapHolder> mBitmapHolderCache;//Holder一级缓存
    /**
     * Level 2 LRU cache for bitmaps. This is a smaller cache that holds
     * the most recently used bitmaps to save time on decoding
     * them from bytes (the bytes are stored in {@link #mBitmapHolderCache}.
     */
    private final LruCache<Object, Bitmap> mBitmapCache;//Bitmap二级缓存

BitmapHolder定义如下:

    /**
     * Maintains the state of a particular photo.
     */
    private static class BitmapHolder {
        final byte[] bytes;
        final int originalSmallerExtent;

        volatile boolean fresh;
        Bitmap bitmap;
        Reference<Bitmap> bitmapRef;//注意此处用到了引用
        int decodedSampleSize;

        public BitmapHolder(byte[] bytes, int originalSmallerExtent) {
            this.bytes = bytes;
            this.fresh = true;
            this.originalSmallerExtent = originalSmallerExtent;
        }
    }

ContactPhotoManagerImpl中对一级缓存和二级缓存定义的上限(针对大运行内存设备的上限):

    /** Cache size for mBitmapHolderCache for devices with "large" RAM. */
    private static final int HOLDER_CACHE_SIZE = 2000000; //1.9M

    /** Cache size for mBitmapCache for devices with "large" RAM. */
    private static final int BITMAP_CACHE_SIZE = 36864 * 48; // 1728K=1.6875M

对mBitmapHolderCache和mBitmapCache初始化(此处按需要粘贴代码, 不考虑在源码中的定义顺序):

    //isLowRamDevice()动态判断设备运行内存RAM大小, 调整比例
    final float cacheSizeAdjustment = (am.isLowRamDevice()) ? 0.5f : 1.0f;
    
    //一级缓存定义
    final int holderCacheSize = (int) (cacheSizeAdjustment * HOLDER_CACHE_SIZE);
    mBitmapHolderCache = new LruCache<Object, BitmapHolder>(holderCacheSize) {
        //必须重写此方法,来测量BitmapHolder的大小
        @Override protected int sizeOf(Object key, BitmapHolder value) {
            return value.bytes != null ? value.bytes.length : 0;
        }

        @Override protected void entryRemoved(
                boolean evicted, Object key, BitmapHolder oldValue, BitmapHolder newValue) {
            if (DEBUG) dumpStats();
        }
    };
    mBitmapHolderCacheRedZoneBytes = (int) (holderCacheSize * 0.75);//当Holder缓存超过此值将不会预加载bitmaps
    
    //二级缓存定义
    final int bitmapCacheSize = (int) (cacheSizeAdjustment * BITMAP_CACHE_SIZE);
    mBitmapCache = new LruCache<Object, Bitmap>(bitmapCacheSize) {
        //必须重写此方法,来测量Bitmap的大小
        @Override protected int sizeOf(Object key, Bitmap value) {
            return value.getByteCount();
        }

        @Override protected void entryRemoved(
                boolean evicted, Object key, Bitmap oldValue, Bitmap newValue) {
            if (DEBUG) dumpStats();
        }
    };

对这段代码说明:如不明白LruCache的, 请参考LruCache之LruCache分析

最后说说缩略图和头像加载出来之后是怎么缓存的:

    /**
     * Stores the supplied bitmap in cache.
     */
    //缓存加载到内存的缩略图
    private void cacheBitmap(Object key, byte[] bytes, boolean preloading, int requestedExtent) {
        /**此处省略不相关代码*/
        BitmapHolder holder = new BitmapHolder(bytes,
                bytes == null ? -1 : BitmapUtil.getSmallerExtentFromBytes(bytes));

        // Unless this image is being preloaded, decode it right away while we are still on the background thread.
        // 翻译:如果不是预加载, 在线程中就解码缩略图, 保存到holder对象, 即inflateBitmap函数中就将bytes解码成bitmap,
        if (!preloading) {
            inflateBitmap(holder, requestedExtent);
        }
        //注意:此处将holder加入mBitmapHolderCache缓存, 缩略图是在loadCachedPhoto(.....)函数里加入mBitmapCache缓存的
        if (bytes != null) {
            mBitmapHolderCache.put(key, holder);
            if (mBitmapHolderCache.get(key) != holder) {
                Log.w(TAG, "Bitmap too big to fit in cache.");
                mBitmapHolderCache.put(key, BITMAP_UNAVAILABLE);
            }
        } else {
            mBitmapHolderCache.put(key, BITMAP_UNAVAILABLE);
        }

        mBitmapHolderCacheAllUnfresh = false;
    }

    //缓存根据Uri加载到内存的头像
    @Override
    public void cacheBitmap(Uri photoUri, Bitmap bitmap, byte[] photoBytes) {
        /**此处省略不相关代码*/
        Request request = Request.createFromUri(photoUri, smallerExtent, false /* darkTheme */,
                false /* isCircular */ , DEFAULT_AVATAR);
        
        BitmapHolder holder = new BitmapHolder(photoBytes, smallerExtent);
        holder.bitmapRef = new SoftReference<Bitmap>(bitmap);//新建指向bitmap的弱引用
        mBitmapHolderCache.put(request.getKey(), holder);//注意:此处将holder加入mBitmapHolderCache缓存
        mBitmapHolderCacheAllUnfresh = false;
        mBitmapCache.put(request.getKey(), bitmap);//注意:此处将bitmap加入mBitmapCache缓存
    }

缩略图在loadCachedPhoto(...)函数里面转存到mBitmapCache里面.具体操作如下:

    /**
     * Checks if the photo is present in cache.  If so, sets the photo on the view.
     *
     * @return false if the photo needs to be (re)loaded from the provider.
     */
    private boolean loadCachedPhoto(ImageView view, Request request, boolean fadeIn) {
        BitmapHolder holder = mBitmapHolderCache.get(request.getKey());
        /**此处省略不相关代码*/

        Bitmap cachedBitmap = holder.bitmapRef == null ? null : holder.bitmapRef.get();
        if (cachedBitmap == null) {
            if (holder.bytes.length < 8 * 1024) {
                // Small thumbnails are usually quick to inflate. Let's do that on the UI thread
                inflateBitmap(holder, request.getRequestedExtent());
                cachedBitmap = holder.bitmap;
                if (cachedBitmap == null) return false;
            } else {
                // This is bigger data. Let's send that back to the Loader so that we can
                // inflate this in the background
                request.applyDefaultImage(view, request.mIsCircular);
                return false;
            }
        }
        /**此处省略不相关代码*/
        // Put the bitmap in the LRU cache. But only do this for images that are small enough (we require that at least six of those can be cached at the same time)
        if (cachedBitmap.getByteCount() < mBitmapCache.maxSize() / 6) {
            mBitmapCache.put(request.getKey(), cachedBitmap);//缓存到mBitmapCache
        }

        // Soften the reference
        holder.bitmap = null;//Holder中释放直接引用

        return holder.fresh;
    }

未完待续