今天向大家介绍一个很有用的异步任务类处理类,分别包含了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调度策略的并行执行器。源码可以看这里。
/**
* 用于替换掉原生的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);
}
}
}
- 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.
以上便是对AsyncTask的并发执行优化,接下来我们看对异常捕获的改进。
真正说起来,这并不算是什么功能上的改进,仅仅是一种开发上的技巧。代码过长,我删去了一些,仅留下重要部分。
/**
* 安全异步任务,可以捕获任意异常,并反馈给给开发者。<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);
}
}
- 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.
其实从代码就可以看出,仅仅是对原AsyncTask类中各个阶段的代码做了一次try..catch... 但就是这一个小优化,不仅可以使代码整齐(我觉得try...catch太多真的很影响代码美观),而且在最终都可以由一个onPostExecuteSafely(xxx)来整合处理,使得结构更加紧凑。
让AsyncTask附带数据缓存功能
我们在做APP开发的时候,网络访问都会加上缓存处理,其中的原因我想就不必讲了。那么如果让AsyncTask自身就附带网络JSON缓存,岂不是更好?其实实现原理很简单,就是将平时我们写在外面的缓存方法放到AsyncTask内部去实现,注释已经讲解的很清楚了,这里就不再讲了
/**
* 本类主要用于获取网络数据,并将结果缓存至文件,文件名为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());
}
}
}
- 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.