一套完善的Android異步任務類

今天向你們介紹一個頗有用的異步任務類處理類,分別包含了AsyncTask各個環節中的異常處理、大量併發執行而不發生異常、字符串數據緩存等功能。而且感謝@馬天宇(http://litesuits.com/)的合做。java

研究過Android系統源碼的同窗會發現:AsyncTask在android2.3的時候線程池是一個核心數爲5線程,隊列可容納10線程,最大執行128個任務,這存在一個問題,當你真的有138個併發時,即便手機沒被你撐爆,那麼超出這個指標應用絕對crash掉。 後來升級到3.0,爲了不併髮帶來的一些列問題,AsyncTask居然成爲序列執行器了,也就是你即便你同時execute N個AsyncTask,它也是挨個排隊執行的。 這一點請同窗們必定注意,AsyncTask在3.0之後,是異步的沒錯,但不是併發的。關於這一點的改進辦法,我以前寫過一篇《Thread併發請求封裝——深刻理解AsyncTask類》沒有看過的同窗能夠看這裏,本文是在這個基礎上對AsyncTask作進一步的優化。 android

根據Android4.0源碼咱們能夠看到,在AsyncTask中默認有兩個執行器,ThreadPoolExecutor和SerialExecutor,分別表示並行執行器和串行執行器。可是默認的並行執行器並不能執行大於128個任務的處理,因此咱們在此定義一個根據lru調度策略的並行執行器。源碼能夠看這裏 git

    /**
     * 用於替換掉原生的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());
        }
    }
}
相關文章
相關標籤/搜索