一套完善的Android异步任务类

移动开发 Android
今天向大家介绍一个很有用的异步任务类处理类,分别包含了AsyncTask各个环节中的异常处理、大量并发执行而不发生异常、字符串数据缓存等功能。并且感谢@马天宇(http://litesuits.com/)给我的思路与指点。

[[123542]]

今天向大家介绍一个很有用的异步任务类处理类,分别包含了AsyncTask各个环节中的异常处理、大量并发执行而不发生异常、字符串数据缓存等功能。并且感谢@马天宇(http://litesuits.com/)给我的思路与指点。

研究过Android系统源码的同学会发现:AsyncTask在android2.3的时候线程池是一个核心数为5线程,队列可容纳10线程,***执行128个任务,这存在一个问题,当你真的有138个并发时,即使手机没被你撑爆,那么超出这个指标应用绝对crash掉。 后来升级到3.0,为了避免并发带来的一些列问题,AsyncTask竟然成为序列执行器了,也就是你即使你同时execute N个AsyncTask,它也是挨个排队执行的。 这一点请同学们一定注意,AsyncTask在3.0以后,是异步的没错,但不是并发的。关于这一点的改进办法,我之前写过一篇《Thread并发请求封装——深入理解AsyncTask类》没有看过的同学可以看这里,本文是在这个基础上对AsyncTask做进一步的优化。

根据Android4.0源码我们可以看到,在AsyncTask中默认有两个执行器,ThreadPoolExecutor和SerialExecutor,分别表示并行执行器和串行执行器。但是默认的并行执行器并不能执行大于128个任务的处理,所以我们在此定义一个根据lru调度策略的并行执行器。源码可以看这里。

  1. /** 
  2.     * 用于替换掉原生的mThreadPoolExecutor,可以大大改善Android自带异步任务框架的处理能力和速度。 
  3.     * 默认使用LIFO(后进先出)策略来调度线程,可将***的任务快速执行,当然你自己可以换为FIFO调度策略。 
  4.     * 这有助于用户当前任务优先完成(比如加载图片时,很容易做到当前屏幕上的图片优先加载)。 
  5.     */ 
  6.    private static class SmartSerialExecutor implements Executor { 
  7.        /** 
  8.         * 这里使用{@link ArrayDequeCompat}作为栈比{@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.         * 一次同时并发的数量,根据处理器数量调节 <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.         * 一个时间段内最多并发线程个数: 双核手机:2 四核手机:4 ... 计算公式如下: 
  23.         */ 
  24.        private static int serialOneTime; 
  25.        /** 
  26.         * 并发***数量,当投入的任务过多大于此值时,根据Lru规则,将最老的任务移除(将得不到执行) <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.                // 小于单次并发量直接运行 
  51.                mThreadPoolExecutor.execute(r); 
  52.            } else { 
  53.                // 如果大于并发上限,那么移除最老的任务 
  54.                if (mQueue.size() >= serialMaxCount) { 
  55.                    mQueue.pollFirst(); 
  56.                } 
  57.                // 新任务放在队尾 
  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.    } 

以上便是对AsyncTask的并发执行优化,接下来我们看对异常捕获的改进。

真正说起来,这并不算是什么功能上的改进,仅仅是一种开发上的技巧。代码过长,我删去了一些,仅留下重要部分。

  1. /** 
  2.  * 安全异步任务,可以捕获任意异常,并反馈给给开发者。<br> 
  3.  * 从执行前,执行中,执行后,乃至更新时的异常都捕获。<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.     } 

其实从代码就可以看出,仅仅是对原AsyncTask类中各个阶段的代码做了一次try..catch... 但就是这一个小优化,不仅可以使代码整齐(我觉得try...catch太多真的很影响代码美观),而且在最终都可以由一个onPostExecuteSafely(xxx)来整合处理,使得结构更加紧凑。

让AsyncTask附带数据缓存功能

我们在做APP开发的时候,网络访问都会加上缓存处理,其中的原因我想就不必讲了。那么如果让AsyncTask自身就附带网络JSON缓存,岂不是更好?其实实现原理很简单,就是将平时我们写在外面的缓存方法放到AsyncTask内部去实现,注释已经讲解的很清楚了,这里就不再讲了

  1. /** 
  2.  * 本类主要用于获取网络数据,并将结果缓存至文件,文件名为key,缓存有效时间为value <br> 
  3.  * <b>注:</b>{@link #CachedTask#Result}需要序列化,否则不能或者不能完整的读取缓存。<br> 
  4.  */ 
  5. public abstract class CachedTask<Params, Progress, Result extends Serializable> 
  6.         extends SafeTask<Params, Progress, Result> { 
  7.     private String cachePath = "folderName"// 缓存路径 
  8.     private String cacheName = "MD5_effectiveTime"// 缓存文件名格式 
  9.     private long expiredTime = 0// 缓存时间 
  10.     private String key; // 缓存以键值对形式存在 
  11.     private ConcurrentHashMap<String, Long> cacheMap; 
  12.   
  13.     /** 
  14.      * 构造方法 
  15.      * @param cachePath  缓存路径 
  16.      * @param key  存储的key值,若重复将覆盖 
  17.      * @param cacheTime  缓存有效期,单位:分 
  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.             // 对外url,对内url的md5值(不仅可以防止由于url过长造成文件名错误,还能防止恶意修改缓存内容) 
  26.             this.key = CipherUtils.md5(key); 
  27.             // 对外单位:分,对内单位:毫秒 
  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.                 // 若满足命名格式则认为是一个合格的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.      * 做联网操作,本方法运行在线程中 
  51.      */ 
  52.     protected abstract Result doConnectNetwork(Params... params) 
  53.             throws Exception; 
  54.   
  55.     /** 
  56.      * 做耗时操作 
  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; // 获取缓存有效时间 
  64.         long currentTime = System.currentTimeMillis(); // 获取当前时间 
  65.   
  66.         if (currentTime >= lastTime + expiredTime) { // 若缓存无效,联网下载 
  67.             res = doConnectNetwork(params); 
  68.             if (res == null)  
  69.                 res = getResultFromCache(); 
  70.             else  
  71.                 saveCache(res); 
  72.         } else { // 缓存有效,使用缓存 
  73.             res = getResultFromCache(); 
  74.             if (res == null) { // 若缓存数据意外丢失,重新下载 
  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.      * 保存数据,并返回是否成功 
  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.      * 清空缓存文件(异步) 
  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.             // 异步删除全部文件 
  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.      * 移除一个缓存 
  140.      */ 
  141.     public void remove(String key) { 
  142.         // 对内是url的MD5 
  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.      * 如果缓存是有效的,就保存 
  154.      * @param res 将要缓存的数据 
  155.      */ 
  156.     private void saveCache(Result res) { 
  157.         if (res != null) { 
  158.             saveResultToCache(res); 
  159.             cacheMap.put(cacheName, System.currentTimeMillis()); 
  160.         } 
  161.     } 

 

责任编辑:chenqingxiang 来源: oschina
相关推荐

2024-09-23 04:00:00

java架构分布式系统

2018-08-31 08:42:48

LinuxUnix实用程序

2009-06-23 18:01:45

Ajax框架源代码

2023-03-03 17:00:00

部署Linux内核

2019-10-11 15:58:25

戴尔

2021-05-27 07:12:19

单点登录系统

2021-03-29 11:20:39

前端代码工作流

2010-06-09 17:00:43

UML试题

2022-02-25 09:00:00

数据科学工具架构

2020-12-08 08:12:14

SQL脚本行转列

2018-03-19 15:22:43

Android图标包图标

2010-12-24 11:27:13

华为HCNE认证

2009-09-22 17:12:22

计算机原理教材

2021-05-06 11:06:52

人工智能语音识别声闻检索

2020-06-30 15:33:05

Linux 系统 数据

2024-03-06 08:13:33

FutureJDKCallable

2009-03-03 13:00:00

虚拟化技术vmwarexen

2021-11-07 20:43:14

React

2020-07-02 08:11:06

Linux技巧语法

2016-10-12 17:42:04

云服务云计算云迁移
点赞
收藏

51CTO技术栈公众号