【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());
         }
     }
}


转自:http://blog.csdn.net/kymjs/article/details/41649859



你可能感兴趣的:(android)