关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android异步任务类实现方案

    Android异步任务类实现方案

    2014-12-04 00:00:00 出处:apkbus
    分享

    今天向大家介绍一个很有用的异步任务类处理类,分别包含了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); 
               } 
           } 
       }

    以上便是对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); 
        } 
    }

    其实从代码就可以看出,仅仅是对原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()); 
            } 
        } 
    }
    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。