一套完善的Android異步任務類

jopen 10年前發布 | 14K 次閱讀 Android Android開發 移動開發

集數據并發,異常傳遞,網絡緩存于一身,一套完整的異步任務處理類的實現

今天向大家介紹一個很有用的異步任務類處理類,分別包含了AsyncTask各個環節中的異常處理、大量并發執行而不發生異常、字符串數據緩存等功能。并且感謝@馬天宇(http://litesuits.com/)給我的思路與指點。

研究過Android系統源碼的同學會發現:AsyncTask在android2.3的時候線程池是一個核心數為5線程,隊列可容納10線程,最大執行128個任務,這存在一個問題,當你真的有138個并發時,即使手機沒被你撐爆,那么超出這個指標應用絕對crash掉。 后來升級到4.0,為了避免并發帶來的一些列問題,AsyncTask竟然成為序列執行器了,也就是你即使你同時execute N個AsyncTask,它也是挨個排隊執行的。 這一點請同學們一定注意,AsyncTask在4.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(base2): 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(base16): 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);             }         }     }</pre>


以上便是對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);     } }</pre>

其實從代碼就可以看出,僅僅是對原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());         }     } }</pre>

 來自:http://my.oschina.net/kymjs/blog/350565

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!