首页 > 代码库 > 一套完善的Android异步任务类
一套完善的Android异步任务类
欢迎各位加入我的Android开发群[257053751]
今天向大家介绍一个很有用的异步任务类处理类,分别包含了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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | /** * 用于替换掉原生的mThreadPoolExecutor,可以大大改善Android自带异步任务框架的处理能力和速度。 * 默认使用LIFO(后进先出)策略来调度线程,可将最新的任务快速执行,当然你自己可以换为FIFO调度策略。 * 这有助于用户当前任务优先完成(比如加载图片时,很容易做到当前屏幕上的图片优先加载)。 */ private static class SmartSerialExecutor implements Executor { /** * 这里使用{@link ArrayDequeCompat}作为栈比{@link Stack}性能高 */ private ArrayDequeCompat<Runnable> mQueue = new ArrayDequeCompat<Runnable>( serialMaxCount); private ScheduleStrategy mStrategy = ScheduleStrategy.LIFO; private enum ScheduleStrategy { LIFO, FIFO; } /** * 一次同时并发的数量,根据处理器数量调节 <br> * cpu count : 1 2 3 4 8 16 32 <br> * once(base*2): 1 2 3 4 8 16 32 <br> * 一个时间段内最多并发线程个数: 双核手机:2 四核手机:4 ... 计算公式如下: */ private static int serialOneTime; /** * 并发最大数量,当投入的任务过多大于此值时,根据Lru规则,将最老的任务移除(将得不到执行) <br> * cpu count : 1 2 3 4 8 16 32 <br> * base(cpu+3) : 4 5 6 7 11 19 35 <br> * max(base*16): 64 80 96 112 176 304 560 <br> */ private static int serialMaxCount; private void reSettings( int cpuCount) { serialOneTime = cpuCount; serialMaxCount = (cpuCount + 3 ) * 16 ; } public SmartSerialExecutor() { reSettings(CPU_COUNT); } @Override public synchronized void execute( final Runnable command) { Runnable r = new Runnable() { @Override public void run() { command.run(); next(); } }; if ((mThreadPoolExecutor).getActiveCount() < serialOneTime) { // 小于单次并发量直接运行 mThreadPoolExecutor.execute(r); } else { // 如果大于并发上限,那么移除最老的任务 if (mQueue.size() >= serialMaxCount) { mQueue.pollFirst(); } // 新任务放在队尾 mQueue.offerLast(r); } } public synchronized void next() { Runnable mActive; switch (mStrategy) { case LIFO: mActive = mQueue.pollLast(); break ; case FIFO: mActive = mQueue.pollFirst(); break ; default : mActive = mQueue.pollLast(); break ; } if (mActive != null ) { mThreadPoolExecutor.execute(mActive); } } } |
以上便是对AsyncTask的并发执行优化,接下来我们看对异常捕获的改进。
真正说起来,这并不算是什么功能上的改进,仅仅是一种开发上的技巧。代码过长,我删去了一些,仅留下重要部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | /** * 安全异步任务,可以捕获任意异常,并反馈给给开发者。<br> * 从执行前,执行中,执行后,乃至更新时的异常都捕获。<br> */ public abstract class SafeTask<Params, Progress, Result> extends KJTaskExecutor<Params, Progress, Result> { private Exception cause; @Override protected final void onPreExecute() { try { onPreExecuteSafely(); } catch (Exception e) { exceptionLog(e); } } @Override protected final Result doInBackground(Params... params) { try { return doInBackgroundSafely(params); } catch (Exception e) { exceptionLog(e); cause = e; } return null ; } @Override protected final void onProgressUpdate(Progress... values) { try { onProgressUpdateSafely(values); } catch (Exception e) { exceptionLog(e); } } @Override protected final void onPostExecute(Result result) { try { onPostExecuteSafely(result, cause); } catch (Exception e) { exceptionLog(e); } } @Override protected final void onCancelled(Result result) { onCancelled(result); } } |
其实从代码就可以看出,仅仅是对原AsyncTask类中各个阶段的代码做了一次try..catch... 但就是这一个小优化,不仅可以使代码整齐(我觉得try...catch太多真的很影响代码美观),而且在最终都可以由一个onPostExecuteSafely(xxx)来整合处理,使得结构更加紧凑。
让AsyncTask附带数据缓存功能
我们在做APP开发的时候,网络访问都会加上缓存处理,其中的原因我想就不必讲了。那么如果让AsyncTask自身就附带网络JSON缓存,岂不是更好?其实实现原理很简单,就是将平时我们写在外面的缓存方法放到AsyncTask内部去实现,注释已经讲解的很清楚了,这里就不再讲了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | /** * 本类主要用于获取网络数据,并将结果缓存至文件,文件名为key,缓存有效时间为value <br> * <b>注:</b>{@link #CachedTask#Result}需要序列化,否则不能或者不能完整的读取缓存。<br> */ public abstract class CachedTask<Params, Progress, Result extends Serializable> extends SafeTask<Params, Progress, Result> { private String cachePath = "folderName" ; // 缓存路径 private String cacheName = "MD5_effectiveTime" ; // 缓存文件名格式 private long expiredTime = 0 ; // 缓存时间 private String key; // 缓存以键值对形式存在 private ConcurrentHashMap<String, Long> cacheMap; /** * 构造方法 * @param cachePath 缓存路径 * @param key 存储的key值,若重复将覆盖 * @param cacheTime 缓存有效期,单位:分 */ public CachedTask(String cachePath, String key, long cacheTime) { if (StringUtils.isEmpty(cachePath) || StringUtils.isEmpty(key)) { throw new RuntimeException( "cachePath or key is empty" ); } else { this .cachePath = cachePath; // 对外url,对内url的md5值(不仅可以防止由于url过长造成文件名错误,还能防止恶意修改缓存内容) this .key = CipherUtils.md5(key); // 对外单位:分,对内单位:毫秒 this .expiredTime = TimeUnit.MILLISECONDS.convert( cacheTime, TimeUnit.MINUTES); this .cacheName = this .key + "_" + cacheTime; initCacheMap(); } } private void initCacheMap() { cacheMap = new ConcurrentHashMap<String, Long>(); File folder = FileUtils.getSaveFolder(cachePath); for (String name : folder.list()) { if (!StringUtils.isEmpty(name)) { String[] nameFormat = name.split( "_" ); // 若满足命名格式则认为是一个合格的cache if (nameFormat.length == 2 && (nameFormat[ 0 ].length() == 32 || nameFormat[ 0 ].length() == 64 || nameFormat[ 0 ].length() == 128 )) { cacheMap.put(nameFormat[ 0 ], TimeUnit.MILLISECONDS.convert(StringUtils.toLong(nameFormat[ 1 ]), TimeUnit.MINUTES)); } } } } /** * 做联网操作,本方法运行在线程中 */ protected abstract Result doConnectNetwork(Params... params) throws Exception; /** * 做耗时操作 */ @Override protected final Result doInBackgroundSafely(Params... params) throws Exception { Result res = null ; Long time = cacheMap.get(key); long lastTime = (time == null ) ? 0 : time; // 获取缓存有效时间 long currentTime = System.currentTimeMillis(); // 获取当前时间 if (currentTime >= lastTime + expiredTime) { // 若缓存无效,联网下载 res = doConnectNetwork(params); if (res == null ) res = getResultFromCache(); else saveCache(res); } else { // 缓存有效,使用缓存 res = getResultFromCache(); if (res == null ) { // 若缓存数据意外丢失,重新下载 res = doConnectNetwork(params); saveCache(res); } } return res; } private Result getResultFromCache() { Result res = null ; ObjectInputStream ois = null ; try { ois = new ObjectInputStream( new FileInputStream( FileUtils.getSaveFile(cachePath, key))); res = (Result) ois.readObject(); } catch (Exception e) { e.printStackTrace(); } finally { FileUtils.closeIO(ois); } return res; } /** * 保存数据,并返回是否成功 */ private boolean saveResultToCache(Result res) { boolean saveSuccess = false ; ObjectOutputStream oos = null ; try { oos = new ObjectOutputStream( new FileOutputStream( FileUtils.getSaveFile(cachePath, key))); oos.writeObject(res); saveSuccess = true ; } catch (Exception e) { e.printStackTrace(); } finally { FileUtils.closeIO(oos); } return saveSuccess; } /** * 清空缓存文件(异步) */ public void cleanCacheFiles() { cacheMap.clear(); File file = FileUtils.getSaveFolder(cachePath); final File[] fileList = file.listFiles(); if (fileList != null ) { // 异步删除全部文件 TaskExecutor.start( new Runnable() { @Override public void run() { for (File f : fileList) { if (f.isFile()) { f.delete(); } } } // end run() }); } // end if } /** * 移除一个缓存 */ public void remove(String key) { // 对内是url的MD5 String realKey = CipherUtils.md5(key); for (Map.Entry<String, Long> entry : cacheMap.entrySet()) { if (entry.getKey().startsWith(realKey)) { cacheMap.remove(realKey); return ; } } } /** * 如果缓存是有效的,就保存 * @param res 将要缓存的数据 */ private void saveCache(Result res) { if (res != null ) { saveResultToCache(res); cacheMap.put(cacheName, System.currentTimeMillis()); } } } |
一套完善的Android异步任务类