一套完善的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());

        }

    }

}

时间: 2024-08-13 12:57:04

一套完善的Android异步任务类的相关文章

Android异步任务类分析

一.为什么要使用异步任务类? Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler对象向UI线程发送消息,完成界面的更新, 这种方式对整个界面的控制非常精细. 但是也有缺点,例如代码臃肿,所以为了简化操作,Android 1.5 sdk 提供了一个工具类 AsyncTask(异步任务类), 使得创建异步任务变的更加简单. 二.异步任务类的实现 异步类实现计数演示过程: 首先在 主Activity文件中:(主Activity用来解析程序的第一个界面) 1. 创建异步类任

android异步类AsyncTask的简单使用

Android为了降低这个开发难度,提供了AsyncTask.AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务,更通俗地说就是一个执行后台任务的线程 而且他还会自动通知主线程更新UI 优点: 结构清晰,容易理解. 缺点 代码量稍大 下面直接看代码 1 private class AsyncLogin extends AsyncTask<Void,Integer,Boolean>{ 2 private EditText passwordEdit; 3 private EditT

Android异步加载全解析之引入一级缓存

Android异步加载全解析之引入缓存 为啥要缓存 通过对图像的缩放,我们做到了对大图的异步加载优化,但是现在的App不仅是高清大图,更是高清多图,动不动就是图文混排,以图代文,如果这些图片都加载到内存中,必定会OOM.因此,在用户浏览完图像后,应当立即将这些废弃的图像回收,但是,这又带来了另一个问题,也就是当用户在浏览完一次图片后,如果还要返回去再进行重新浏览,那么这些回收掉的图像又要重新进行加载,保不准就要那些无聊到蛋疼的人在那一边看你回收GC,一边看你重新加载.这两件事情,肯定是互相矛盾的

Android异步处理三:Handler+Looper+MessageQueue深入详解

Android Loop&Handle学习总结 - New Start - 博客频道 - CSDN.NET ?????? 昨晚偷懒,这篇博客只写了一个标题,今天早晨一看,还有15的阅读量.实在是对不起那些同学.......换了是我,也会BS这样的LZ吧!sorry 啦 -------------------------------------------------------------------------------------------------------------------

Android异步加载全解析之使用多线程

异步加载之使用多线程 初次尝试 异步.异步,其实说白了就是多任务处理,也就是多线程执行,多线程那就会有各种问题,我们一步步来看,首先,我们创建一个class--ImageLoaderWithoutCaches,从命名上,大家也看出来,这个类,我们实现的是不带缓存的图像加载,不多说,我们再创建一个方法--showImageByThread,通过多线程来加载图像: /** * Using Thread * @param imageView * @param url */ public void sh

Android异步加载全解析之使用AsyncTask

Android异步加载全解析之使用AsyncTask 概述 既然前面提到了多线程,就不得不提到线程池,通过线程池,不仅可以对并发线程进行管理,更可以提高他们执行的效率,优化整个App.当然我们可以自己创建一个线程池,不过这样是很烦的,要创建一个高效的线程池还是挺费事的,不过,Android系统给我吗提供了AsyncTask这样一个类,来帮助我们快速实现多线程开发,它的底层实现,其实就是一个线程池. AsyncTask初探 AsyncTask,顾名思义就是用来做异步处理的.通过AsyncTask,

Android异步加载全解析之大图处理

Android异步加载全解析之大图处理 异步加载中非常重要的一部分就是对图像的处理,这也是我们前面用异步加载图像做演示例子的原因.一方面是因为图像处理不好的话会非常占内存,而且容易OOM,另一方面,图像也比文字要大,加载比较慢.所以,在讲解了如何进行多线程.AsyncTask进行多线程加载后,先暂停下后面的学习,来对图像的异步处理进行一些优化工作. 为什么要对图像处理 为什么要对图像进行处理,这是一个很直接的问题,一张图像,不管你拿手机.相机.单反还是什么玩意拍出来,它就有一定的大小,但是在不同

Android异步消息处理机制(3)asyncTask基本使用

本文翻译自android官方文档,结合自己测试,整理如下. 概述 AsyncTask抽象类,翻译过来就是异步任务,能够合理并方便的使用UI线程.该类可以实现将后台操作结果显示在UI线程中,而不需要我们自己实现子线程或者handler(当然它内部也是借助这两者实现的). 虽然AsyncTask可以提供后台运行并将结果显示在UI上,但是理想情况应该是后台操作最多只能是几秒钟,若要执行长时间的操作强烈建议使用java中的Executor,ThreadPoolExecutor,FutureTask等.

Android异步载入全解析之大图处理

Android异步载入全解析之大图处理 异步载入中很重要的一部分就是对图像的处理,这也是我们前面用异步载入图像做示例的原因. 一方面是由于图像处理不好的话会很占内存,并且easyOOM,还有一方面,图像也比文字要大,载入比較慢.所以,在解说了怎样进行多线程.AsyncTask进行多线程载入后,先暂停下后面的学习.来对图像的异步处理进行一些优化工作. 为什么要对图像处理 为什么要对图像进行处理,这是一个很直接的问题.一张图像.无论你拿手机.相机.单反还是什么玩意拍出来,它就有一定的大小,可是在不同