A complete set of Android asynchronous task classes

A complete set of Android asynchronous task classes

[[123542]]

Today I would like to introduce a very useful asynchronous task processing class, which includes exception handling in each link of AsyncTask, large number of concurrent executions without exceptions, string data caching and other functions. And I would like to thank @马天宇(http://litesuits.com/) for giving me ideas and advice.

Students who have studied the source code of the Android system will find that: in Android 2.3, the thread pool of AsyncTask is a core with 5 threads, the queue can accommodate 10 threads, and the maximum execution of 128 tasks. This has a problem. When you really have 138 concurrent tasks, even if the phone is not blown up by you, the application will definitely crash if it exceeds this indicator. Later, when it was upgraded to 3.0, in order to avoid some problems caused by concurrency, AsyncTask became a sequence executor, that is, even if you execute N AsyncTasks at the same time, it will be executed one by one in a queue. Students must pay attention to this point. After 3.0, AsyncTask is asynchronous, but not concurrent. Regarding the improvement method of this point, I have written an article before, "Thread Concurrent Request Encapsulation - In-depth Understanding of AsyncTask Class". Students who have not read it can read it here. This article is to further optimize AsyncTask on this basis.

According to the Android 4.0 source code, we can see that there are two default executors in AsyncTask, ThreadPoolExecutor and SerialExecutor, which represent parallel executors and serial executors respectively. However, the default parallel executor cannot process more than 128 tasks, so we define a parallel executor based on the lru scheduling strategy. The source code can be seen here.

  1. /**
  2. * Used to replace the native mThreadPoolExecutor, which can greatly improve the processing power and speed of Android's own asynchronous task framework.
  3. * By default, the LIFO (last in, first out) strategy is used to schedule threads, which can quickly execute the most urgent tasks. Of course, you can change it to the FIFO scheduling strategy.
  4. * This helps the user prioritize the current task (for example, when loading images, it is easy to prioritize the images on the current screen).
  5. */  
  6. private   static   class SmartSerialExecutor implements Executor {
  7. /**
  8. * Here, {@link ArrayDequeCompat} is used as a stack, which has higher performance than {@link Stack}
  9. */  
  10. private ArrayDequeCompat<Runnable> mQueue = new ArrayDequeCompat<Runnable>(
  11. serialMaxCount);
  12. private ScheduleStrategy mStrategy = ScheduleStrategy.LIFO;
  13.  
  14. private   enum ScheduleStrategy {
  15. LIFO, FIFO;
  16. }
  17.  
  18. /**
  19. * The number of concurrent connections at one time is adjusted according to the number of processors <br>
  20. * cpu count: 1 2 3 4 8 16 32 <br>
  21. * once(base*2): 1 2 3 4 8 16 32 <br>
  22. * Maximum number of concurrent threads in a time period: Dual-core phone: 2 Quad-core phone: 4 ... The calculation formula is as follows:
  23. */  
  24. private   static   int serialOneTime;
  25. /**
  26. * Maximum number of concurrent tasks. When the number of tasks input is greater than this value, the oldest task will be removed according to the LRU rule (it will not be executed) <br>
  27. * cpu count: 1 2 3 4 8 16 32 <br>
  28. * base(cpu+3): 4 5 6 7 11 19 35 <br>
  29. * max(base*16): 64 80 96 112 176 304 560 <br>
  30. */  
  31. private   static   int serialMaxCount;
  32.  
  33. private   void reSettings( int cpuCount) {
  34. serialOneTime = cpuCount;
  35. serialMaxCount = (cpuCount + 3 ) * 16 ;
  36. }
  37. public SmartSerialExecutor() {
  38. reSettings(CPU_COUNT);
  39. }
  40. @Override  
  41. public   synchronized   void execute( final Runnable command) {
  42. Runnable r = new Runnable() {
  43. @Override  
  44. public   void run() {
  45. command.run();
  46. next();
  47. }
  48. };
  49. if ((mThreadPoolExecutor).getActiveCount() < serialOneTime) {
  50. // Run directly if the number of concurrent connections is less than that of a single connection  
  51. mThreadPoolExecutor.execute(r);
  52. } else {
  53. // If it is greater than the concurrency limit, remove the oldest task  
  54. if (mQueue.size() >= serialMaxCount) {
  55. mQueue.pollFirst();
  56. }
  57. // Put the new task at the end of the queue  
  58. mQueue.offerLast(r);
  59. }
  60. }
  61. public   synchronized   void next() {
  62. Runnable mActive;
  63. switch (mStrategy) {
  64. case LIFO:
  65. mActive = mQueue.pollLast();
  66. break ;
  67. case FIFO:
  68. mActive = mQueue.pollFirst();
  69. break ;
  70. default :
  71. mActive = mQueue.pollLast();
  72. break ;
  73. }
  74. if (mActive != null ) {
  75. mThreadPoolExecutor.execute(mActive);
  76. }
  77. }
  78. }

The above is the optimization of concurrent execution of AsyncTask. Next, let’s look at the improvement of exception capture.

To be honest, this is not a functional improvement, but just a development technique. The code is too long, so I deleted some of it and left only the important parts.

  1. /**
  2. * Safe asynchronous tasks can capture any exceptions and provide feedback to developers.<br>
  3. * Capture exceptions before, during, after, and even when updating.<br>
  4. */  
  5. public   abstract   class SafeTask<Params, Progress, Result> extends  
  6. KJTaskExecutor<Params, Progress, Result> {
  7. private Exception cause;
  8.   
  9. @Override  
  10. protected   final   void onPreExecute() {
  11. try {
  12. onPreExecuteSafely();
  13. } catch (Exception e) {
  14. exceptionLog(e);
  15. }
  16. }
  17. @Override  
  18. protected   final Result doInBackground(Params... params) {
  19. try {
  20. return doInBackgroundSafely(params);
  21. } catch (Exception e) {
  22. exceptionLog(e);
  23. cause = e;
  24. }
  25. return   null ;
  26. }
  27. @Override  
  28. protected   final   void onProgressUpdate(Progress... values) {
  29. try {
  30. onProgressUpdateSafely(values);
  31. } catch (Exception e) {
  32. exceptionLog(e);
  33. }
  34. }
  35. @Override  
  36. protected   final   void onPostExecute(Result result) {
  37. try {
  38. onPostExecuteSafely(result, cause);
  39. } catch (Exception e) {
  40. exceptionLog(e);
  41. }
  42. }
  43. @Override  
  44. protected   final   void onCancelled(Result result) {
  45. onCancelled(result);
  46. }
  47. }

In fact, it can be seen from the code that only a try...catch is done on the code of each stage in the original AsyncTask class... But this small optimization can not only make the code tidy (I think too much try...catch really affects the beauty of the code), but also can be integrated and processed by an onPostExecuteSafely(xxx) in the end, making the structure more compact.

Let AsyncTask come with data caching function

When we develop apps, we always add cache processing to network access. I don't think I need to explain the reasons. So wouldn't it be better if AsyncTask itself has network JSON cache? In fact, the implementation principle is very simple, that is, put the cache method we usually write outside into AsyncTask to implement it. The comments have explained it very clearly, so I won't explain it here.

  1. /**
  2. * This class is mainly used to obtain network data and cache the results to a file. The file name is key and the cache validity period is value. <br>
  3. * <b>Note:</b> {@link #CachedTask#Result} needs to be serialized, otherwise the cache cannot be read or cannot be read completely. <br>
  4. */  
  5. public   abstract   class CachedTask<Params, Progress, Result extends Serializable>
  6. extends SafeTask<Params, Progress, Result> {
  7. private String cachePath = "folderName" ; // cache path  
  8. private String cacheName = "MD5_effectiveTime" ; // Cache file name format  
  9. private   long expiredTime = 0 ; // Cache time  
  10. private String key; // The cache exists in the form of key-value pairs  
  11. private ConcurrentHashMap<String, Long> cacheMap;
  12.   
  13. /**
  14. * Construction method
  15. * @param cachePath cache path
  16. * @param key The key value to be stored. If repeated, it will be overwritten.
  17. * @param cacheTime cache validity period, unit: minutes
  18. */  
  19. public CachedTask(String cachePath, String key, long cacheTime) {
  20. if (StringUtils.isEmpty(cachePath)
  21. || StringUtils.isEmpty(key)) {
  22. throw   new RuntimeException( "cachePath or key is empty" );
  23. } else {
  24. this .cachePath = cachePath;
  25. // md5 value of external url and internal url (not only can it prevent file name errors caused by too long url, but also can prevent malicious modification of cache content)  
  26. this .key = CipherUtils.md5(key);
  27. // External unit: minutes, internal unit: milliseconds  
  28. this .expiredTime = TimeUnit.MILLISECONDS.convert(
  29. cacheTime, TimeUnit.MINUTES);
  30. this .cacheName = this .key + "_" + cacheTime;
  31. initCacheMap();
  32. }
  33. }
  34.   
  35. private   void initCacheMap() {
  36. cacheMap = new ConcurrentHashMap<String, Long>();
  37. File folder = FileUtils.getSaveFolder(cachePath);
  38. for (String name : folder.list()) {
  39. if (!StringUtils.isEmpty(name)) {
  40. String[] nameFormat = name.split( "_" );
  41. // If the naming format is met, it is considered a qualified cache  
  42. if (nameFormat.length == 2 && (nameFormat[ 0 ].length() == 32 || nameFormat[ 0 ].length() == 64 || nameFormat[ 0 ].length() == 128 )) {
  43. cacheMap.put(nameFormat[ 0 ], TimeUnit.MILLISECONDS.convert(StringUtils.toLong(nameFormat[ 1 ]), TimeUnit.MINUTES));
  44. }
  45. }
  46. }
  47. }
  48.   
  49. /**
  50. * Perform networking operations. This method runs in the thread.
  51. */  
  52. protected   abstract Result doConnectNetwork(Params... params)
  53. throws Exception;
  54.   
  55. /**
  56. * Perform time-consuming operations
  57. */  
  58. @Override  
  59. protected   final Result doInBackgroundSafely(Params... params)
  60. throws Exception {
  61. Result res = null ;
  62. Long time = cacheMap.get(key);
  63. long lastTime = (time == null ) ? 0 : time; // Get the cache validity time  
  64. long currentTime = System.currentTimeMillis(); // Get the current time  
  65.   
  66. if (currentTime >= lastTime + expiredTime) { // If the cache is invalid, download online  
  67. res = doConnectNetwork(params);
  68. if (res == null )
  69. res = getResultFromCache();
  70. else   
  71. saveCache(res);
  72. } else { // Cache is valid, use cache  
  73. res = getResultFromCache();
  74. if (res == null ) { // If the cached data is accidentally lost, re-download  
  75. res = doConnectNetwork(params);
  76. saveCache(res);
  77. }
  78. }
  79. return res;
  80. }
  81.   
  82. private Result getResultFromCache() {
  83. Result res = null ;
  84. ObjectInputStream ois = null ;
  85. try {
  86. ois = new ObjectInputStream( new FileInputStream(
  87. FileUtils.getSaveFile(cachePath, key)));
  88. res = (Result) ois.readObject();
  89. } catch (Exception e) {
  90. e.printStackTrace();
  91. finally
  92. FileUtils.closeIO(ois);
  93. }
  94. return res;
  95. }
  96.   
  97. /**
  98. * Save the data and return whether it is successful
  99. */  
  100. private   boolean saveResultToCache(Result res) {
  101. boolean saveSuccess = false ;
  102. ObjectOutputStream oos = null ;
  103. try {
  104. oos = new ObjectOutputStream( new FileOutputStream(
  105. FileUtils.getSaveFile(cachePath, key)));
  106. oos.writeObject(res);
  107. saveSuccess = true ;
  108. } catch (Exception e) {
  109. e.printStackTrace();
  110. finally
  111. FileUtils.closeIO(oos);
  112. }
  113. return saveSuccess;
  114. }
  115.   
  116. /**
  117. * Clear cache files (asynchronous)
  118. */  
  119. public   void cleanCacheFiles() {
  120. cacheMap.clear();
  121. File file = FileUtils.getSaveFolder(cachePath);
  122. final File[] fileList = file.listFiles();
  123. if (fileList != null ) {
  124. //Asynchronously delete all files  
  125. TaskExecutor.start( new Runnable() {
  126. @Override  
  127. public   void run() {
  128. for (File f : fileList) {
  129. if (f.isFile()) {
  130. f.delete();
  131. }
  132. }
  133. } // end run()  
  134. });
  135. } // end if  
  136. }
  137.   
  138. /**
  139. * Remove a cache
  140. */  
  141. public   void remove(String key) {
  142. // Internally it is the MD5 of the URL  
  143. String realKey = CipherUtils.md5(key);
  144. for (Map.Entry<String, Long> entry : cacheMap.entrySet()) {
  145. if (entry.getKey().startsWith(realKey)) {
  146. cacheMap.remove(realKey);
  147. return ;
  148. }
  149. }
  150. }
  151.   
  152. /**
  153. * If the cache is valid, save it
  154. * @param res The data to be cached
  155. */  
  156. private   void saveCache(Result res) {
  157. if (res != null ) {
  158. saveResultToCache(res);
  159. cacheMap.put(cacheName, System.currentTimeMillis());
  160. }
  161. }
  162. }

<<:  Is iOS jailbreaking really necessary?

>>:  A chart to understand the gender ratio of Silicon Valley technology companies

Recommend

Hisense TV's crazy expansion of content pool may lead to crisis

On March 18, Hisense signed contracts with 11 vid...

How to invest in Tik Tok’s huge Qianchuan?

Douyin's Juliang Qianchuan is an e-commerce a...

What exactly is the cholera that broke out at Wuhan University?

Mixed Knowledge Specially designed to cure confus...

How to create a Douyin account? Introduction to Douyin operation methods

Nowadays, short videos are in a very fierce compe...

How to use channels to achieve user growth?

There are many entrepreneurs around me. Most of t...

stateof.ai: 2020 Artificial Intelligence Report

Stateof.ai has released its latest "Artifici...

Setting up and placing advertising accounts in Moments

We have shared content on the characteristics of ...