Do Android phone contact customization also has more than 2 years, recently suddenly found that they are not familiar with the code they see every day, or a little understanding, no in-depth understanding of the implementation mechanism or principle of it, all corners of the country, thick skin, if there is a bad place, light spray, welcome to correct!

Let’s get down to business!

When it comes to contact list head load, will give class ContactPhotoManager. Java, then look at the Android native English for such features:

Asynchronously loads loads contact photos and Maintains a cache of photos. — 2016 — 2016

The four functions of this class are as follows:

  • preload
  • The asynchronous query
  • Bitmap Level 2 cache
  • Display the Bitmap to the ImageView

Here are some important classes related to the ContactPhotoManager class

(The picture below is my hand animation, if you find it difficult to understand, please see Android source code):

See ContactPhotoManager for example code:

    public static ContactPhotoManager getInstance(Context context) {
        if(sInstance = = null) {/ / get ApplicationContext Context ApplicationContext. = the Context getApplicationContext (); / / new ContactPhotoManagerImpl object sInstance = createContactPhotoManager (applicationContext); / / register callback, help application management more effectively use memory applicationContext. RegisterComponentCallbacks (sInstance); // Check whether you have the contact read permissionif(PermissionsUtil hasContactsPermissions (context)) {/ / open the preload sInstance. PreloadPhotosInBackground (); }}return sInstance;
    }

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

This code function: create ContactPhotoManagerImpl object, enable preloading, improve the interface display speed.

Here’s how asynchronous tasks interact with UI threads:

The first is the mMainThreadHandler defined in ContactPhotoManagerImpl, defined and commented as follows:

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

The second is the mLoaderThreadHandler defined in LoaderThread, which declares and initializes functions as follows:

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

Why mention the above two handlers? This is because these two handlers communicate with each other between the main thread (UI Thread) and the child thread (contact thread), the LoaderThread. The specific implementation is as follows:

From the child thread to the UI thread, we find the following two functions in the LoaderThread:

/** Loads thumbnail photos with ids */ private void loadThumbnails(Boolean preloading) {/** Loads thumbnail photos with ids */ mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED); } /** Loads photos referenced with Uris*/ private voidloadUriBasedPhotos() {/ * * here omit according to Uris, or local contact to load the pictures from the network and cache code * / mMainThreadHandler sendEmptyMessage (MESSAGE_PHOTOS_LOADED); }Copy the code

The two functions in the above code are running in asynchronous tasks. After the child thread loads the avatar, MESSAGE_PHOTOS_LOADED message is sent to the UI thread through mMainThreadHandler, and then the UI thread starts to execute H in ContactPhotoManagerImpl AndleMessage (Message MSG) starts displaying cached avatars into ImageView.

From the UI thread to the child thread, please see ContactPhotoManagerImpl. LoaderThread the following functions:

        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;
        }
Copy the code

The requestPreloading() and requestLoading() functions in this code are running in the UI thread, and then the child thread starts processing the received Message and loads the Photos.

Level 2 cache is defined by two variables: mBitmapHolderCache and mBitmapCache in ContactPhotoManagerImpl.

    /**
     * An LRU cache for bitmap holders. The cache contains bytes forphotos 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 cacheCopy the code
    /**
     * 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 level 2 cacheCopy the code

BitmapHolder is defined as follows:

/** * 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; // Note that the reference int decodedSampleSize is used; public BitmapHolder(byte[] bytes, int originalSmallerExtent) { this.bytes = bytes; this.fresh =true; this.originalSmallerExtent = originalSmallerExtent; }}Copy the code

ContactPhotoManagerImpl defines the upper limit for level 1 and level 2 caches (for large running memory devices):

    /** Cache size for mBitmapHolderCache for devices with "large"RAM. */ private static final int HOLDER_CACHE_SIZE = 2000000; / / 1.9 M / * * Cache sizefor mBitmapCache for devices with "large"RAM. */ private static final int BITMAP_CACHE_SIZE = 36864 * 48; / / 1728 k = 1.6875 MCopy the code

Initialize mBitmapHolderCache and mBitmapCache (paste the code as needed, regardless of the order defined in the source code):

//isLowRamDevice() dynamically determines the RAM size of the device and adjusts the ratio finalfloatcacheSizeAdjustment = (am.isLowRamDevice()) ? 0.5 f: 1.0 f; Final int holderCacheSize = (int) (cacheSizeAdjustment * HOLDER_CACHE_SIZE); MBitmapHolderCache = new LruCache<Object, BitmapHolder>(holderCacheSize) {// Must override this method, Override protected int sizeOf(Object key, BitmapHolder value) {Override protected int sizeOf(Object key, BitmapHolder value) {returnvalue.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); Final int bitmapCacheSize = (int) (cacheSizeAdjustment * BITMAP_CACHE_SIZE); MBitmapCache = new LruCache<Object, Bitmap>(bitmapCacheSize) {// You must override this method, @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(); }};Copy the code

If you do not understand LruCache, please refer to LruCache analysis

A final word on how thumbnails and avatars are cached once loaded:

    /**
     * Stores the supplied bitmap inPrivate void cacheBitmap(Object key, byte[] bytes, Boolean preloading, Int requestedExtent) {/** omit irrelevant code */ BitmapHolder holder = new BitmapHolder(bytes, bytes == null? -1 : BitmapUtil.getSmallerExtentFromBytes(bytes)); // Unless this image is being preloaded, decode it right awaywhileWe are still on the background thread. // iF the thread is not preloaded, the thumbnail is decoded in the thread and saved to the holder object, i.e. bytes are decoded into bitmap.if(! preloading) { inflateBitmap(holder, requestedExtent); } // Note: the holder is added to the mBitmapHolderCache cache, and the thumbnail is in loadCachedPhoto(.....) Function to add mBitmapCacheif(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; @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); // Create a weak reference to the bitmap mbitMapHolderCache.put (request.getkey (), holder); / / note: this will join the mBitmapHolderCache cache mBitmapHolderCacheAllUnfresh = holderfalse; mBitmapCache.put(request.getKey(), bitmap); // Note: add bitmap to mBitmapCache}Copy the code

Thumbnails in loadCachedPhoto(…) Function to mBitmapCache. Specific operations are as follows:

    /**
     * Checks if the photo is present in cache.  If so, sets the photo on the view.
     *
     * @return false ifthe 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; }} /** omit the irrelevant code */ // Put the bitmapin 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); // Blaze the reference holder.bitmap = null; // release direct references in Holderreturn holder.fresh;
    }
Copy the code

To be continued