[Java & Android open-source library code analysis] unzip Android-Smart-image-View

Source: Internet
Author: User

Android Application Development has entered a relatively mature stage, especially in foreign countries. A variety of mature and stable open-source libraries have emerged for common developers. Although this situation greatly accelerates the process of app development, the problem is that most common developers only stop using these open source libraries and know how to use them, however, the underlying implementation principles of open-source libraries are not clear, or they are not very thorough, which leads to many problems: 1) when the open-source library encounters a bug, it cannot quickly locate the problem; 2) your daily code writing is only limited to implementing the app's business logic, which is too high-layer, and is not very good for improving the technical level; 3) for those who are pursuing perfection, only when you have a clear understanding of the principles of all code implementations in your project can you feel at ease. 4) when you need to write the basic library code for your project, if you are familiar with the implementation of various open-source libraries, you can better design the architecture and write the code.

The above solutions are to learn more about the source code of the open-source database, understand its operating mechanism, and thus improve its technical accumulation. This is the original intention of this series. This series will select a variety of common or uncommon open-source libraries. Most of them will be based on the Java language at the beginning as long as they have the value of profiling, in the future, we will gradually cover objective C and C, C ++, PHP and other languages. At the same time, you are welcome to recommend the open-source library you want to know. I will go into this series of schedules after screening.

James Smith, net name loopj, on the Android platform, because Android-async-HTTP (https://github.com/loopj/android-async-http) This open source library and well-known, this series we will carefully analyze this library, but not now, at the beginning, we came up with a little simpler, also from loopj, named Android-Smart-image-view (https://github.com/loopj/android-smart-image-view)

Check out the code from GitHub. We can see that the code of the entire project contains only seven Java source files. This library is an extension of the imageview control in the android SDK, it facilitates asynchronous loading of images with specified URLs on the network, as well as system contact portraits. It also provides a simple and scalable framework for users to expand based on the actual image source. The usage of smartimageview is similar to that of imageview. For more information, see http://loopj.com/android-smart-image-view.

Android-Smart-image-view is extended from imageview to easily display image resources of different sources. Therefore, you must first define an interface to indicate that the image obtains such a public behavior. In Android, when an image is finally drawn to the canvas, it is represented by bitmap. Therefore, the interface is defined as follows:

public interface SmartImage {    public Bitmap getBitmap(Context context);}

Implement the smartimage interface based on different image sources, and process the image retrieval logic in the getbitmap function. The class image structure is as follows:

First, let's look at the implementation structure in the upper part. We find that three classes implement the smartimage interface, bitmapimage, contactimage, and webimage.

1) bitmapimage class, the simplest implementation (which can be considered as the dummy class), because it is only passed into the bitmap instance in the constructor and then returned when getbitmap is called.

2) contactimage class, which can be used to obtain the system contact avatar, input the specified contact ID in the constructor, and then find the avatar of the contact with the specified ID in the getbitmap function, if no Avatar is set, null is returned.

3) The webimage class allows you to obtain image resources from a specified URL. Of course, instead of loading images from the network every time, you can implement a simple level-2 cache, that is, memory cache and disk cache, each time the image is loaded, the system first determines whether the image exists in the memory or disk cache. When the cache does not hit the image, the image is downloaded to the specified URL.

 

[Retrieve system Contact Profile]

To obtain the contact profile, that is, to access the data of the system address book app, you must add the permission statement to the androidmanifest. xml file:

<uses-permission android:name="android.permission.READ_CONTACTS"/> 

When accessing data of other apps in the Android system, it is generally implemented through contentprovider. A contentprovider class implements a set of standard method interfaces, this allows other apps to save or read the various data types it provides. Other apps can access the data provided by contentprovider through the contentresolver interface. In the getbitmap function implementation of the contactimage class, you first obtain the contentresolver instance, generate a search URI Based on the contact ID, and then call the opencontactphotoinputstream function of the system contact class to obtain the data stream of the Avatar, use bitmapfactory. the decodestream function generates bitmap instances for data streams. (It should be noted that the mobile phone's contact profile is obtained here, rather than the contact profile in the SIM card, because the SIM card has no contact profile data due to capacity restrictions and other reasons ).

public class ContactImage implements SmartImage {        private long contactId;    public ContactImage(long contactId) {        this.contactId = contactId;    }    public Bitmap getBitmap(Context context) {        Bitmap bitmap = null;        ContentResolver contentResolver = context.getContentResolver();        try {            Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);            InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, uri);            if(input != null) {                bitmap = BitmapFactory.decodeStream(input);            }        } catch(Exception e) {            e.printStackTrace();        }        return bitmap;    }}

At this point, some people may wonder how the contactimage contact ID variable contactid came from? Contactid is also obtained through contentresolver query. The sample code is as follows:

Private Static final int display_name_index = 0; Private Static final int phone_number_index = 1; Private Static final int photo_id_index = 2; Private Static final int contact_id_index = 3; private Static final string [] phones_projection = new string [] {Phone. display_name, phone. number, phone. photo_id, phone. contact_id}; private void getphonecontact (context) {contentresolver contentresolv ER = context. getcontentresolver (); cursor = contentresolver. Query (phone. content_uri, phones_projection, null, null); If (cursor! = NULL) {While (cursor. movetonext () {string displayname = cursor. getstring (display_name_index); // contact name string phonenum = cursor. getstring (phone_number_index); // contact number long contactid = cursor. getlong (contact_id_index); // contact Id long photoid = cursor. getlong (photo_id_index); // contact Avatar ID (when photoid is greater than 0, it indicates that the contact has an avatar)} cursor. close ();}}

[Load images from a specified URL]

The specified URL usually refers to the external link of the image. The format is similar

Http://farm6.staticflickr.com/5489/9272288811_286d003d9e_o.png

Therefore, you can simply use the getcontent method of urlconnection to obtain the image data, and then use bitmapfactory to convert it to bitmap. The code is implemented as follows:

    private Bitmap getBitmapFromUrl(String url) {        Bitmap bitmap = null;        try {            URLConnection conn = new URL(url).openConnection();            conn.setConnectTimeout(CONNECT_TIMEOUT);            conn.setReadTimeout(READ_TIMEOUT);            bitmap = BitmapFactory.decodeStream((InputStream) conn.getContent());        } catch(Exception e) {            e.printStackTrace();        }        return bitmap;    }

[Second-level cache implementation]

To speed up image loading, the smart-Image Library introduces a simple second-level cache. We know that the data acquisition speed depends on the physical media, generally memory> disk> network. Therefore, when loading an image of a URL, the system first checks whether the image hits the memory cache. If the image does not hit the cache, the system searches for the disk cache to load the image from the network and updates the memory cache and disk cache records.

Considering the speed of cache lookup, data structures with low time complexity such as hash tables are generally used for memory caching. Because multiple threads can be searched in the hash table at the same time, it is reasonable to use concurrenthashmap to implement the memory cache when considering concurrent multi-thread access. The app memory on the Android platform is limited. When the memory exceeds this limit, OOM (outofmemory) may occur. To avoid this problem, in the memory cache, we do not directly hold the reference of the bitmap instance, but use softreference to hold the soft reference of the bitmap object. If an object has soft reference and the memory space is sufficient, the garbage collector does not recycle it. The memory occupied by these objects will be reclaimed only when the memory space is insufficient. Therefore, soft references are usually used to implement memory-sensitive high-speed cache.

On the Android system, the disk cache can be stored in internal storage space or external storage space (that is, SD card ). The cache for small images can be stored in the internal storage space. However, when there are large images and a large number of images, the images should be cached on the SD card, after all, the internal storage space is much smaller than the SD card space. The disk cache of the smart-Image Library is stored in the internal storage space, that is, in the cache directory of the app, which uses context. getcachedir () function. The format is similar to/data/APP package name/cache. The cache directory is mainly used to store cached files. When the system's internal storage space is insufficient, the files under this directory will be deleted. Of course, we cannot rely on the system to clear these cached files, instead, you should set the maximum storage space for these cached files. When the actual occupied space exceeds this maximum, you need to clear the cached files using certain algorithms. This is not considered in the implementation of the smart-Image Library.

The two-level cache space is created in the webimagecache constructor. The Code is as follows:

    public WebImageCache(Context context) {        // Set up in-memory cache store        memoryCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>();        // Set up disk cache store        Context appContext = context.getApplicationContext();        diskCachePath = appContext.getCacheDir().getAbsolutePath() + DISK_CACHE_PATH;        File outFile = new File(diskCachePath);        outFile.mkdirs();        diskCacheEnabled = outFile.exists();        // Set up threadpool for image fetching tasks        writeThread = Executors.newSingleThreadExecutor();    }

The code to determine whether bitmap hits the memory cache is as follows: it first extracts the soft reference of Bitmap and determines whether it has been recycled by the system. If not, it extracts the bitmap instance from the soft reference:

    private Bitmap getBitmapFromMemory(String url) {        Bitmap bitmap = null;        SoftReference<Bitmap> softRef = memoryCache.get(getCacheKey(url));        if(softRef != null){            bitmap = softRef.get();        }        return bitmap;    }

The code used to determine whether bitmap hits the disk cache is as follows. The basic principle is to find the corresponding file based on the URL on the disk. If yes, convert it to a bitmap instance and return it. Because the URL may contain some special characters that cannot appear in the file name, preprocessing is required when converting the URL into a file name to filter out these characters.

    private Bitmap getBitmapFromDisk(String url) {        Bitmap bitmap = null;        if(diskCacheEnabled){            String filePath = getFilePath(url);            File file = new File(filePath);            if(file.exists()) {                bitmap = BitmapFactory.decodeFile(filePath);            }        }        return bitmap;    }    private String getFilePath(String url) {        return diskCachePath + getCacheKey(url);    }    private String getCacheKey(String url) {        if(url == null){            throw new RuntimeException("Null url passed in");        } else {            return url.replaceAll("[.:/,%?&=]", "+").replaceAll("[+]+", "+");        }    }

The process of saving bitmap to the memory cache is very simple, that is, adding a data to the hashmap, but note that the soft reference of bitmap is stored. The Code is as follows.

    private void cacheBitmapToMemory(final String url, final Bitmap bitmap) {        memoryCache.put(getCacheKey(url), new SoftReference<Bitmap>(bitmap));    }

Adding bitmap to the disk cache is implemented through the executorservice thread pool. On the one hand, it restricts the number of concurrent threads and solves the synchronization problem. The smart-image library uses a thread pool with only one thread, which can be seen in the webimagecache constructor. Therefore, the disk cache is added sequentially. The cache generation process is to first generate the corresponding file in the cache directory based on the URL, and then call the bitmap. Compress function to write bitmap to the disk file output stream according to the specified compression format and compression quality.

    private void cacheBitmapToDisk(final String url, final Bitmap bitmap) {        writeThread.execute(new Runnable() {            @Override            public void run() {                if(diskCacheEnabled) {                    BufferedOutputStream ostream = null;                    try {                        ostream = new BufferedOutputStream(new FileOutputStream(                                new File(diskCachePath, getCacheKey(url))), 2*1024);                        bitmap.compress(CompressFormat.PNG, 100, ostream);                    } catch (FileNotFoundException e) {                        e.printStackTrace();                    } finally {                        try {                            if(ostream != null) {                                ostream.flush();                                ostream.close();                            }                        } catch (IOException e) {}                    }                }            }        });    }

At this point, the related classes in the above class diagram are finally introduced. Next, let's look at another class diagram:

In this class diagram, there are two classes: smartimagetask and smartimageview, And the oncompletelistener and oncompletehandler interfaces. The smartimage class has already been described above. It can be easily seen that the relationship between smartimagetask and smartimageview is an aggregation relationship. The task provides operations such as loading background images for the view, and the view focuses on the presentation of the UI.

Generally, this background task class implements the runnable interface, especially when used with the thread pool, smartimagetask is no exception, because there is a thread pool in smartimageview.

Since smartimagetask implements the runnable interface, its main logical implementation is in the run method. From the class graph structure, we can see that smartimagetask aggregates smartimage and uses the getbitmap function of smartimage to obtain the bitmap instance of the specified URL. The Code is as follows:

    @Override    public void run() {        if(image != null) {            complete(image.getBitmap(context));            context = null;        }    }

In addition, the callback mechanism is also implemented in the task class for the View class. It includes a static handler class (which defines handler as static to avoid Memory leakage). The callback interface oncompletelistener for image loading is defined as follows:

    public static class OnCompleteHandler extends Handler {        @Override        public void handleMessage(Message msg) {            Bitmap bitmap = (Bitmap)msg.obj;            onComplete(bitmap);        }        public void onComplete(Bitmap bitmap){};    }    public abstract static class OnCompleteListener {        public abstract void onComplete();    }

When the image is not loaded yet, if you need to cancel the loading, you can set the flag canceled to false. Even if the image is loaded successfully, no message is sent to the upper View class.

Smartimageview is a subclass of imageview. It defines a thread pool containing four threads for executing the smartimagetask task. When setting image resources for imageview, you can choose whether to set the default image, whether to set the failed image loading, and whether to set the callback interface after loading. Before enabling a new task, you must first determine whether a task that has already set an image for the current imageview is running. If yes, cancel the task and create a new task and add it to the thread pool, always ensure that one imageview has only one latest task running.

    public void setImage(final SmartImage image, final Integer fallbackResource, final Integer loadingResource, final SmartImageTask.OnCompleteListener completeListener) {        // Set a loading resource        if(loadingResource != null){            setImageResource(loadingResource);        }        // Cancel any existing tasks for this image view        if(currentTask != null) {            currentTask.cancel();            currentTask = null;        }        // Set up the new task        currentTask = new SmartImageTask(getContext(), image);        currentTask.setOnCompleteHandler(new SmartImageTask.OnCompleteHandler() {            @Override            public void onComplete(Bitmap bitmap) {                if(bitmap != null) {                    setImageBitmap(bitmap);                } else {                    // Set fallback resource                    if(fallbackResource != null) {                        setImageResource(fallbackResource);                    }                }                if(completeListener != null){                    completeListener.onComplete();                }            }        });        // Run the task in a threadpool        threadPool.execute(currentTask);    }

Finally, when you want to cancel all tasks waiting and running in the thread pool, you can call the shutdownnow function of executorservice. The code below shows how to create and destroy the thread pool:

    private static final int LOADING_THREADS = 4;    private static ExecutorService threadPool = Executors.newFixedThreadPool(LOADING_THREADS);        public static void cancelAllTasks() {        threadPool.shutdownNow();        threadPool = Executors.newFixedThreadPool(LOADING_THREADS);    }

[Expansion and optimization]

As mentioned above, if images have other sources except URLs and contact portraits, developers need to implement the smartimage interface for expansion. Another overseas developer, commonsguy (will introduce his open-source project later)

Post a smartimage implementation class videoimage to obtain thumbnails of videos in the system.

Class videoimage implements smartimage {private int videoid; // video ID private int thumbnailkind; // micro_kind-micro scaling mode; mini_kind-mini scaling mode; the former has lower resolution than public videoimage (INT videoid, int thumbnailkind) {This. videoid = videoid; this. thumbnailkind = thumbnailkind;} @ override public bitmap getbitmap (context) {return (mediastore. video. thumbnails. getthumbnail (context. getcontentresolver (), videoid, thumbnailkind, null ));}}

In Android development, if the system memory is insufficient, creating a bitmap instance will cause an outofmemoryerror, resulting in APP crash. Therefore, do you need to determine the available memory size of the system before creating a bitmap? Whether Oome should be captured is not considered in the smart-Image Library, because after all, this library is only applicable to loading small images. If optimization is required, you can add low memory to the place where the bitmap object is created in the webimage class. If the memory is too low, you can reduce the insample value of the image to reduce the image quality, reduce the memory space it occupies. The improved getbitmapfromurl function is as follows:

    private Bitmap getBitmapFromUrl(String url) {        Bitmap bitmap = null;                try {            URLConnection conn = new URL(url).openConnection();            conn.setConnectTimeout(CONNECT_TIMEOUT);            conn.setReadTimeout(READ_TIMEOUT);            ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();            int inSample = 1;            if (memInfo.lowMemory) {                inSample = 12;            }            BitmapFactory.Options options = new BitmapFactory.Options();            options.inSampleSize = inSample;            bitmap = BitmapFactory.decodeStream((InputStream) conn.getContent(), null, options);        } catch(Exception e) {            e.printStackTrace();        }        return bitmap;    }

Of course, outofmemoryerror still occurs when the image size after the quality reduction exceeds the allocable memory size. Can we catch this exception? The answer is yes, but not recommended. Java documentation clearly states that Java. lang. the error class is Java. lang. the subclass of throwable, Java. lang. exception is also a subclass of throwable. Exception indicates exceptions that can and should be caught, and error indicates fatal errors of the program crash, which should not be captured. However, in some cases, when an outofmemoryerror error occurs in our program, we may need to perform some log operations or make some remedial measures, such as releasing the memory or reducing the applied memory space, you can still catch the outofmemoryerror exception.

 

-- Welcome to reprint, please indicate the source of http://blog.csdn.net/asce1885
Please do not use it for commercial purposes without your consent. Thank you --

 

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.