关闭 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()); 
            } 
        } 
    }
    上一篇返回首页 下一篇

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

    别人在看

    Edge浏览器百度被劫持/篡改怎么办,地址后边跟着尾巴#tn=68018901_7_oem_dg

    Google Chrome 在 iPhone 上新增了 Safari 数据导入选项

    Windows 11专业版 KMS工具激活产品密钥的方法

    DEDECMS安全策略官方出品

    Microsoft Text Input Application 可以关闭吗?

    新版本QQ如何关闭自带的浏览器?

    C++编程语言中continue的用法和功能,附举例示范代码

    c++ map 的数据结构、基本操作以及其在实际应用中的使用。

    C语言如何避免内存泄漏、缓冲区溢出、空指针解引用等常见的安全问题

    C语言中的break语句详解

    IT头条

    马斯克2026最新采访总结:2040年,全球机器人数量将突破100亿台

    23:52

    专家解读|规范人工智能前沿业态健康发展的新探索:解读《人工智能拟人化互动服务管理暂行办法》

    00:54

    用至强 6高存力搞定MoE卸载!

    17:53

    美国将允许英伟达向中国“经批准的客户”出售H200 GPU

    02:08

    苹果与微信就15%手续费达成一致?腾讯未置可否

    22:00

    技术热点

    PHP 和 Node.js 的10项对比挑战

    Javascript闭包深入解析及实现方法

    windows 7、windows 8.1手动增加右键菜单功能技巧

    MYSQL出错代码大汇总

    windows 7假死机怎么办 windows 7系统假死机的原因以及解决方法

    Ubuntu(Linux)下配置IP地址的方法

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

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