今天给大家介绍一个非常好用的异步任务处理类,包括AsyncTask各个环节的异常处理,大量并发执行无异常,字符串数据缓存等功能。也感谢@马天宇(http://litesuits.com/)给我的想法和指点。研究过Android系统源码的同学会发现:AsyncTask在android2.3时,线程池核心数为5个线程,队列可容纳10个线程。138并发的时候,就算手机不被你压垮,超过这个指标的应用肯定会崩溃。后来升级到3.0,为了避免并发引起的一些系列问题,AsyncTask变成了顺序执行器,即即使你同时执行N个AsyncTasks,也是排队一个一个执行。这点同学们一定要注意。AsyncTask3.0以后是异步的,但是不是并发的。关于这一点的改进方法,之前写过一篇文章《Thread并发请求封装——深入理解AsyncTask类》。没有看过的同学可以点此阅读。本文就是在此基础上进一步优化AsyncTask。根据Android4.0的源码我们可以看到AsyncTask中默认有两个executor,ThreadPoolExecutor和SerialExecutor,分别代表并行执行器和串行执行器。但是默认的并行执行器不能处理超过128个任务,所以我们这里定义一个基于lru调度策略的并行执行器。可以在此处查看源代码。/***用来替代原来的mThreadPoolExecutor,可以大大提高Android自带的异步任务框架的处理能力和速度。*默认采用LIFO(后进先出)策略调度线程,可以快速执行最新的任务。当然你也可以自己改成FIFO调度策略。*这有助于用户先完成当前任务(例如加载图片时,很容易让当前屏幕上的图片先加载)。*/privatestaticclassSmartSerialExecutorimplementsExecutor{/***这里使用{@linkArrayDequeCompat}作为栈比{@linkStack}性能更高*/privateArrayDequeCompatmQueue=newArrayDequeCompat(serialMaxCount);privateScheduleStrategymStrategy{ScheduleStrategy.LIFO;privateenumSched;}/***同时并发线程数根据处理器数调整
*cpucount:123481632
*once(base*2):123481632
*a中最大并发线程数时间段:双核手机:2四核手机:4...计算公式如下:*/privatestaticintserialOneTime;/***并发***数,当任务投入过多时这个值,根据Lru规则,最早的任务会被Remove(不会被执行)
*cpucount:123481632
*base(cpu+3):4567111935
*max(base*16):648096112176304560
*/privatestaticintserialMaxCount;privatevoidreSettings(intcpuCount){serialOneTime=cpuCount;serialMaxCount=(cpuCount+3)*16;}publicSmartSerialExecutor(){reSettings(CPU_COUNT);}@Overridepublicsynchronizedvoidexecute(finalRunnablecommand){Runnabler=newRunnable(){@Overridepublicvoidrun(){command.run();next();}};if((mThreadPoolExecutor).getActiveCount()=serialMaxCount){mQueue.pollFirst();}//新任务放在队尾mQueue.offerLast(r);}}publicsynchronizedvoidnext(){RunnablemActive;switch(mStrategy){caseLIFO:mActive=mQueue.pollLast();break;caseFIFO:mActive=mQueue.pollFirst();break;default:mActive=mQueue.pollLast();break;}if(mActive!=null){mThreadPoolExecutor.execute(mActive);}}}以上是AsyncTask并发执行优化,接下来我们看异常捕获的改进。其实这不是功能上的改进,只是一种开发技术。代码太长,我删掉了一些,只留下重要的部分。/***安全的异步任务,可以捕获任何异常并反馈给开发者。
*执行前、执行中、执行后,甚至更新时,所有异常都被捕获。
*/publicabstractclassSafeTaskextendsKJTaskExecutor{privateExceptioncause;@OverrideprotectedfinalvoidonPreExecute(){try{onPreExecuteSafely();}catch(Exceptione){exceptionLog(efin)后台;}}@Overrideprot(Params...params){try{returndoInBackgroundSafely(params);}catch(Exceptione){exceptionLog(e);cause=e;}returnnull;}@OverrideprotectedfinalvoidonProgressUpdate(Progress...values){try{onProgressUpdateSafely(值);}catch(Exceptione){exceptionLog(e);}}@OverrideprotectedfinalvoidonPostExecute(Resultresult){try{onPostExecuteSafely(结果,原因);}catch(Exceptione){exceptionLog(e);}}@OverrideprotectedfinalvoidonCancelled(Resultresult){onCancelled(result);}}其实从代码可以看出,它只是对原来AsyncTask类中各个阶段的代码进行了一次try..catch...但是这个小小的优化不仅可以让codetidy(我觉得try...太多的catch确实影响代码的美观),最后,他们可以all被一个onPostExecuteSafely(xxx)整合处理,结构更加紧凑。让AsyncTask有一个数据缓存的功能。我们在做APP开发的时候,网络访问都会加上缓存。我认为我不需要谈论它的原因。那么如果AsyncTask本身自带网络JSON缓存不是更好吗?其实实现原理很简单,就是把我们平时写在外面的缓存方法放到AsyncTask中去实现。评论里已经解释的很清楚了,这里就不说了/***这个类主要是用来获取网络数据的。并将结果缓存到一个文件中,文件名为key,缓存的有效时间为value
*注意:{@link#CachedTask#Result}需要序列化,否则它不能或不能完全读取缓存。
*/publicabstractclassCachedTaskextendsSafeTask{privateStringcachePath="folderName";//缓存路径privateStringcacheName="MD5_effectiveTime";//缓存文件名格式privatelongexpiredTime=0;//缓存时privateStringkey;//缓存以键值对的形式存在privateConcurrentHashMapcacheMap;/***构造方法*@paramcachePath缓存路径*@paramkey存储的key值,如果重复会覆盖*@paramcacheTime缓存有效期,单位:分*/publicCachedTask(StringcachePath,Stringkey,longcacheTime){if(StringUtils.isEmpty(cachePath)||StringUtils.isEmpty(key)){thrownewRuntimeException("cachePathorkeyisempty");}else{this.cachePath=cachePath;//外部url和内部url的md5值(不仅可以防止url过长导致的文件名错误,还可以防止恶意修改缓存内容)this.key=CipherUtils.md5(key);//外部单位:分,内部单位:milliseco找到this.expiredTime=TimeUnit.MILLISECONDS.convert(cacheTime,TimeUnit.MINUTES);this.cacheName=this.key+"_"+cacheTime;initCacheMap();}}privatevoidinitCacheMap(){cacheMap=newConcurrentHashMap();Filefolder=FileUtils.getSaveFolder(cachePath);for(Stringname:folder.list()){if(!StringUtils.isEmpty(name)){String[]nameFormat=name.split("_");//如果满足命名格式,则认为是合格的缓存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));}}}}/***对于网络操作,该方法在线程中运行*/protectedabstractResultdoConnectNetwork(Params...params)throwsException;/***做耗时操作*/@OverrideprotectedfinalResultdoInBackgroundSafely(Params...params)throwsException{Resultres=null;longtime=cacheMap.get(key);longlastTime=(time==null)?0:time;//获取缓存的有效时间longcurrentTime=System.currentTimeMillis();//获取当前时间if(currentTime>=lastTime+expiredTime){//如果缓存无效,则在线下载res=doConnectNetwork(params);if(res==null)res=getResultFromCache();elsesaveCache(res);}else{//缓存有效,usethecacheres=getResultFromCache();if(res==null){//如果缓存的数据是不小心丢了,重新下载res=doConnectNetwork(params);saveCache(res);}}returnres;}privateResultgetResultFromCache(){Resultres=null;ObjectInputStreamois=null;try{ois=newObjectInputStream(newFileInputStream(FileUtils.getSaveFile(cachePath,key)));res=(结果)ois.readObject();}catch(Exceptione){e.printStackTrace();}finally{FileUtils.closeIO(ois);}returnres;}/***保存数据并返回是否成功*/privatebooleansaveResultToCache(结果){booleansaveSuccess=false;ObjectOutputStreamoos=null;try{oos=newObjectOutputStream(newFileOutputStream(FileUtils.getSaveFile(cachePath,key)));oos.writeObject(res);saveSuccess=true;}catch(Exceptione){e.printStackTrace();}finally{FileUtils.closeIO(oos);}returnsaveSuccess;}/***清空缓存文件(异步)*/publicvoidcleanCacheFiles(){cacheMap.clear();Filefile=FileUtils.getSaveFolder(cachePath);finalFile[]fileList=file.listFiles();if(fileList!=null){//异步删除所有文件TaskExecutor.start(newRunnable(){@Overridepublicvoidrun(){for(Filef:fileList){if(f.isFile()){f.delete();}}}//endrun()});}//endif}/***删除一个Cache*/publicvoidremove(Stringkey){//里面是url=CipherUtils.md5(key)的MD5StringrealKey;for(Map.Entryentry:cacheMap.entrySet()){if(entry.getKey().startsWith(realKey)){cacheMap.remove(realKey);return;}}}/***如果缓存有效,通过*@paramres*/privatevoidsaveCache(Resultres){if(res!=null){saveResultToCache(res);cacheMap.put(cacheName,System.currentTimeMillis());}}}