"Cache strategy series in Android" Disk cache Disklrucache for the Android cache policy

Source: Internet
Author: User

Wirelessly's cache consists of two scenarios, memory cache and disk cache, where the memory cache is primarily using the LRUCache class, where the memory cache I have already explained in detail in the memory cache LRUCache of the "Cache Strategy series" in Android cache strategy, such as crossing has not seen this blog, Suggest crossing first to look.

We know that LRUCache allows us to quickly get the user's most recently used bitmap from memory, but we can't guarantee that the most recently accessed bitmap can be saved in the cache, and that a large number of data-filled controls like the GridView can easily run out of memory caches. In addition, our app may be suspended due to behavior such as phone calls, because the background app may be killed, then the memory cache will be destroyed and the cached bitmap will not exist. Once the user resumes the app's status, the app needs to re-process those images, and in some cases the cached image of the app will be visible even if the user exits the entire app, and it's clearly not possible to use the memory cache in this case.

The disk cache can be used to hold bitmap that have already been processed, and it can also reduce the number of bitmap that are not in the memory cache. Disk caching mainly involves the Disklrucache class. The following from the source point of view in detail Disklrucache This class, and then on this basis to explain how to use the Disklrucache, let the reader know its why.


One Disklrucache class:

First, let's take a look at its constructor

Private Disklrucache (File directory, int appversion, int valuecount, long maxSize) {        this.directory = Directory;        This.appversion = appversion;        This.journalfile = new File (directory, journal_file);        this.journalfiletmp = new File (directory, journal_file_tmp);        This.valuecount = Valuecount;        This.maxsize = maxSize;    }
It can be seen that its constructor is modified by private, which means it is invisible to the outside, that is, we cannot create a Disklrucache object by its constructor, and if we want to create an Disklrucache instance we need to use the Open function, the code is as follows:

 public static Disklrucache open (File directory, int appversion, int valuecount, long maxSize) throws Ioexcepti        on {if (maxSize <= 0) {throw new IllegalArgumentException ("maxSize <= 0");        } if (Valuecount <= 0) {throw new IllegalArgumentException ("Valuecount <= 0"); }//prefer to pick up where we left off Disklrucache cache = new Disklrucache (directory, appversion, VALUEC        Ount, maxSize);                if (cache.journalFile.exists ()) {try {cache.readjournal ();                Cache.processjournal ();                Cache.journalwriter = new BufferedWriter (new FileWriter (Cache.journalfile, True), io_buffer_size);            return cache;                        } catch (IOException Journaliscorrupt) {//SYSTEM.LOGW ("Disklrucache" + directory + "is corrupt:"//                + journaliscorrupt.getmessage () + ", removing"); Cache.Delete ();        }}//Create a new empty cache directory.mkdirs ();        cache = new Disklrucache (directory, appversion, Valuecount, maxSize);        Cache.rebuildjournal ();    return cache; }
You can see that the arguments to the open function are exactly the same as the parameters of the Disklrucache, where the first parameter directory represents the storage path of the disk cache in the file system, generally select the cache path on the SD card, the default location is/sdcard/android/data/ <application Package_name>/cache directory, where <application package_name> represents the package name of the app, and the directory is deleted when the app is uninstalled. The second parameter, by definition, is the app version number, usually set to 1, when the version number is changed to empty all the previous cache files, the third parameter is used to specify the number of cache files that a single cache node can correspond to, usually 1, and the fourth parameter maxsize, as the name implies, indicates the maximum capacity of the disk cache.

The first parameter, maxsize, can also specify the directory under which data is currently applied (the cache path is/data/data/<application Package>/cache at this time), so we usually first determine if there is an SD card. If present, use the SD card cache, otherwise select the current app's directory cache under data. The specific code is as follows:

Public File Getdiskcachedir (context context, string uniqueName) {string cachepath;if (Environment.MEDIA_MOUNTED.equals (Environment.getexternalstoragestate ()) | | ! Environment.isexternalstorageremovable ()) {CachePath = Context.getexternalcachedir (). GetPath ();} else {CachePath = Context.getcachedir (). GetPath ();} return new File (CachePath + file.separator + uniqueName);}

In the open function, you can see that the constructor that first calls Disklrucache, creates the journalfile,journalfiletmp two files in the constructor, and then determines whether journalfile exists, and if so,
Call Cache.readjournal (), read the journal log file, and then call Cache.processjournal (), processing the log file, which is the function of calculating the size of the initialization and collecting the garbage files in the cache file (computes The initial size and collects garbage as a part of opening the cache), delete Dirty record (Dirty entries is assumed to be inconsiste NT and Will is deleted), which is the junk file. This concept is similar to reading dirty data in a database, where you have to explain to the reader the format of the Disklrucache log file. The format is as follows (note: This figure is from the network, thanks to the person who contributed the diagram)


The first five elements are essentially fixed, representing the header data of the Disklrucache log file, and a fixed string "Libcore.io.DiskLruCache" in the line, which means that we are using Disklrucache,

The second line is the Disklrucache version number, which is a constant of 1. The third line is the version number of the application, which is the same as the version number we passed in the open () method. Row four is Valuecount, and this value is also passed in the open () method, typically 1. Line Five is a blank line. The contents of the log file after the empty line:

Next is a line beginning with dirty, followed by a series of numbers is the key to the data stored, if the reader understand the database, know that the general dirty represents dirty data, This is because every time we write a single piece of data to the disk cache, we write a dirty record to the journal file, indicating that we are preparing to write a cache of data, but we do not know what the result will be. When the commit () method is called to indicate a successful write cache, a clean record is written to journal, which means that the dirty data is "cleaned", it is no longer dirty, and when the Abort () method is called to indicate a write cache failure, A remove record is written to the journal. In other words, each line of dirty key, should have a row corresponding to the clean or remove record, otherwise this data is "dirty", will be automatically deleted. Another line that starts with read indicates that we read a piece of data from the cache, and a read record is added to the log file.

This way we can understand the cache.processjournal () function described above to process the log file, that is, the function clears only the dirty but does not appear clean or remove records, that is, the clean and not removed records will be saved, Then through cache.journalwriter = new BufferedWriter (new FileWriter (Cache.journalfile, True), io_buffer_size); Save the clean record to the log file, and then return to the cache.


The second case is if the cache.journalfile does not exist, which is equivalent to the initial creation of the Cahce file, an empty cache is created with the following code:

        Directory.mkdirs ();        cache = new Disklrucache (directory, appversion, Valuecount, maxSize);        Cache.rebuildjournal ();
Cache.rebuildjournal () is called when creating an empty Cahce, which is used to remove extraneous information from the log file and replace the current log file if the log file already exists. In this process, the header data is written to the log file, which is why the Disklrucache log file that we see above contains the preceding 5 rows of data in the following code:

Private synchronized void Rebuildjournal () throws IOException {if (journalwriter! = null) {Journalwrit        Er.close ();        Writer writer = new BufferedWriter (new FileWriter (journalfiletmp), io_buffer_size);        Writer.write (MAGIC);        Writer.write ("\ n");        Writer.write (version_1);        Writer.write ("\ n");        Writer.write (integer.tostring (appversion));        Writer.write ("\ n");        Writer.write (integer.tostring (Valuecount));        Writer.write ("\ n");        Writer.write ("\ n"); For (Entry entry:lruEntries.values ()) {if (entry.currenteditor! = null) {Writer.write (DIRTY            + ' + entry.key + ' \ n ');            } else {writer.write (clean + ' + Entry.key + entry.getlengths () + ' \ n ');        }} writer.close ();        Journalfiletmp.renameto (Journalfile);    Journalwriter = new BufferedWriter (new FileWriter (Journalfile, True), io_buffer_size); }

Next we look at the important methods in Disklrucache:

First look at the edit method for adding a cache.

 Public Editor edit (String key) throws IOException {return edit (key, Any_sequence_number); } Private synchronized Editor edit (String key, Long Expectedsequencenumber) throws IOException {checknotclosed (        );        Validatekey (key);        Entry Entry = Lruentries.get (key); if (expectedsequencenumber! = Any_sequence_number && (entry = = NULL | | Entry.sequencenumber! = EXPE Ctedsequencenumber) {return null;//snapshot is stale} if (entry = = null) {entry            = new Entry (key);        Lruentries.put (key, entry); } else if (entry.currenteditor! = null) {return null;//Another edit is in progress} Editor Ed        Itor = new Editor (entry);        Entry.currenteditor = editor;        Flush the journal before creating files to prevent file leaks journalwriter.write (DIRTY + ' + key + ' \ n ');        Journalwriter.flush ();    return editor; }
You can see that the edit method is synchronous. The Edit method first calls Validatekey (key) to detect if the incoming key is legal, cannot contain spaces, and when we cache a picture we usually get the URL of the picture, and the URL may contain the above illegal characters, So usually we will convert the URL of the image to key, then pass it as a parameter to edit (), and then call Linkedhashmap's Get method to get the cache entry via key, if the entry is empty (indicating that we first deposited the cache), The Entry is created by key to assign it to Entry and put it to lruentries, which is Entry = new Entry (key); Lruentries.put (key, Entry);

If the obtained entry is not empty, then the cache is not the first to be stored in the key. Determines whether Entry.currenteditor is empty, and if not NULL, indicates that the current cache entry is being edit, which returns null directly, that is, Disklrucache does not allow simultaneous edit of a cached object. Note that entry.currenteditor is not NULL if the entry is not empty.

If the obtained entry is not empty while entry.currenteditor is empty, the Editor Object Editor is constructed according to Entyr, then the value of the editor is assigned to Entry.currenteditor and then called Journalwriter.writ E (DIRTY + ' + key + ' \ n '); writes a DIRTY line to the log file indicating that the record is being manipulated. Finally, return to the editor. The public outputstream newoutputstream (int index) method of the editor allows you to get the cached file output stream. The file output stream allows the cache to be written to disk to be saved, and finally the commit () of editor must be called to commit the write operation, so that the record is actually written to the disk cache.


Let's take a look. Gets the cached get method:

 public synchronized Snapshot get (String key) throws IOException {        checknotclosed (); &nbsp ;       Validatekey (key);        Entry Entry = Lruentries.get (key);      & nbsp if (entry = = null) {            return null;       }    &NBSP ;   if (!entry.readable) {            return null;       }  &NB Sp    /*         * Open all streams eagerly to guarantee so we see a single PUBLISHED&N Bsp        * snapshot. If we opened streams lazily then the streams could come         * from different edits.  &nb Sp      */        inputstream[] ins = new inputstream[valuecount];      &NB Sp Try {            for (int i = 0; i < Valuecount; i++) {                ins[i] = new FileInputStream (Entry.getcleanfile (i));           }       } catch (FileNotFoundException e) {      &N Bsp    /A file must have been deleted manually!            return null;  &nbsp ;    }        redundantopcount++;        Journalwriter.append (READ + " + key + ' \ n ');        if (journalrebuildrequired ()) {            Executo Rservice.submit (cleanupcallable);       }        return new Snapshot (key, entry. SequenceNumber, INS);   }

You can also see that the Get method is also synchronous, its role is to return a snapshot object based on key, you can see in the method is also called Validatekey (key), the legality of the detection, if it is valid through key to obtain the cache entry, If entry is empty or is currently unreadable, NULL is returned, otherwise a valuecount file input stream is created based on the value of Valuecountd, and the source of these file input streams is the cache of clean records in entry, which is ins[i] = new FileInputStream (Entry.getcleanfile (i)); then call Journalwriter.append (Read + "+ key + ' \ n '); write a read record line to the cache log file, Finally, a snapshot object is constructed using the key and file input stream array to return it. When the value is returned, it is moved to the head of the cache queue (If a value is returned, it is moved to the head of the LRU queue)


After the snapshot object is obtained, the public inputstream getinputstream (int index) method of the object can get to the cached file input stream, through which the cached record is converted to the bitmap object.



The use of two Disklrucache

The same use of Disklrucache mainly includes the previous module, which is to create a disk cache, add records to the disk cache, and fetch records from the cache. The following is a brief introduction to the use of these three modules, and then combined with LRUCache and Disklrucache to give the full code of the Android cache policy.

Create cache: The cache is created primarily using the Open function: public static Disklrucache Open (File directory, int appversion, int valuecount, long maxSize)

public file Getdiskcachedir (context context, string uniqueName) {string cachepath;if (Environment.MEDIA_MOUNTED.equals ( Environment.getexternalstoragestate ()) | | ! Environment.isexternalstorageremovable ()) {CachePath = Context.getexternalcachedir (). GetPath ();} else {CachePath = Context.getcachedir (). GetPath ();} return new File (CachePath + file.separator + uniqueName);} public int Getappversion (context context) {try {packageinfo info = Context.getpackagemanager (). Getpackageinfo ( Context.getpackagename (), 0); return info.versioncode;} catch (Namenotfoundexception e) {e.printstacktrace ();}     return 1;}     Disklrucache mdisklrucache = null; try {File Cachedir = Getdiskcachedir (Context, "bitmap"), if (!cachedir.exists ()) {cachedir.mkdirs ();}       Mdisklrucache = Disklrucache.open (Cachedir, getappversion (context), 1, 10 * 1024 * 1024);      } catch (IOException e) {e.printstacktrace (); }
Where the Getdiskcachedir function is used to get the cached path, passing its return value as an argument to the first argument of the Open function, getappversion to get the app's version number, and pass its return value as a parameter to the second argument of the open function. The third parameter is generally set to 1, and the fourth parameter is generally specified as 10M.

Write cache: The write cache is primarily done through the Disklrucache.editor class, which is obtained through the Disklrucache edit () method. Usually the write disk cache is fetched from the network and then written to the cache, so we have to define a thread to get the picture from the network.

public string Hashkeyfromurl (string key) {string Cachekey;try {final MessageDigest mdigest = Messagedigest.getinstance (" MD5 "); Mdigest.update (Key.getbytes ()); CacheKey = Bytestohexstring (Mdigest.digest ());} catch (NoSuchAlgorithmException e) {CacheKey = String.valueof (Key.hashcode ());} return CacheKey;}  Private String bytestohexstring (byte[] bytes) {StringBuilder sb = new StringBuilder (); for (int i = 0; i < bytes.length; i++) {String hex = integer.tohexstring (0xFF & Bytes[i]), if (hex.length () = = 1) {sb.append (' 0 ');} Sb.append (hex);} return sb.tostring ();} New Thread () {@Overridepublic void run () {try {String imageUrl = "Http://www.baidu.com/logo.jpg"; String key = Hashkeyfromurl (IMAGEURL);D isklrucache.editor Editor = Mdisklrucache.edit (key); if (Editor! = null) { OutputStream outputstream = Editor.newoutputstream (Disk_cache_index); if (Downloadurltostream (IMAGEURL, OutputStream ) {Editor.commit ();} else {editor.abort ();}} Mdisklrucache.flush ();} catch (IOException e) {e.printstacktrace ();}}}. Start ();p rivate boolean downloadurltostream (String urlstring, OutputStream outputstream) {httpurlconnection URLConnection = null; Bufferedoutputstream out = null; Bufferedinputstream in = null;try {final URL url = new URL (urlstring); urlconnection = (httpurlconnection) url.openconnecti On (); in = new Bufferedinputstream (Urlconnection.getinputstream (), 8 * 1024x768); out = new Bufferedoutputstream (outputstream , 8 * 1024x768); int B;while ((b = In.read ())! =-1) {out.write (b);} return true;} catch (Final IOException e) {e.printstacktrace ();} finally {if (urlconnection! = null) {Urlconnection.disconnect ();} try {if (out! = null) {Out.close ();} if (in = null) {In.close ();}} catch (Final IOException e) {e.printstacktrace ();}} return false;}
Where hashkeyfromurl This function is used to load the URL of the network slice to key, because the URL on the network may contain illegal characters, this in the previous source code analysis has been explained.

Then through Mdisklrucache.edit (key), construct an editor object through key, and then Editor.newoutputstream (Disk_cache_index) to get the file output stream (disk_cache_ Index is typically specified as 0, and then the URL of the output stream and the network slice is passed as a parameter to the Downloadurltostream (String urlstring, OutputStream outputstream) function. The function is to write the image on the network through the URL of the image and OutputStream to the local file via OutputStream, where the output stream is Disklrucache, so it is written to the disk cache. Note that the operation is done in a sub-thread, and after the download is complete, the commit method of the editor is called to make it really write to the cache. If there is an error in the download process, the entire operation is rolled back through the abort () function in editor.

Get cache: Getting the cache is done primarily through the public synchronized Snapshot get (String key) function. The code is as follows:

try {String imageUrl = "Http://www.baidu.com/logo.jpg"; String key = Hashkeyfromurl (IMAGEURL);D isklrucache.snapshot Snapshot = Mdisklrucache.get (key);       if (snapShot! = null) {FileInputStream FIS = (fileinputstream) snapshot.getinputstream (Disk_cache_index); Bitmap Bitmap = Bitmapfactory.decodestream (FIS);//Note that this method does not compress images obtained on the network Mimage.setimagebitmap (BITMAP);}} catch (IOException e) {e.printstacktrace ();}
That is, call Mdisklrucache.get (key), get the Disklrucache.snapshot object, pass the object's Snapshot.getinputstream (Disk_cache_index), get the input stream, Getting to the input stream is basically similar to a local file operation, and it is easy to convert it to a bitmap object, noting that the captured picture is not compressed in the above code and is not directly displayed on the ImageView control. For picture compression, see my blog: Android image compression technology


Well, the above is I understand about disklrucache related knowledge points, crossing if feel good, please remember to click on the "Top" or "like" button to give me a little encouragement oh, crossing can also see my other blog articles Oh!






"Cache strategy series in Android" Disk cache Disklrucache for the Android cache policy

Related Article

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.