Android非同步任務類實現方案
今天向大家介紹一個很有用的非同步任務類處理類,分別包含了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()); } } }
相關文章
- MemQ 實現非同步任務MQ非同步
- AsyncTask非同步任務類非同步
- Android非同步任務Android非同步
- AsyncTask實現非同步任務執行非同步
- 用CompletableFuture實現非同步任務非同步
- Django 使用 Celery 實現非同步任務Django非同步
- Android中非同步任務(AsyncTask)Android非同步
- Swoole來實現實時非同步任務佇列非同步佇列
- celery 與 flask 實現非同步任務排程Flask非同步
- 如何在Java中實現非同步任務排程?Java非同步
- Android的AsyncTask非同步任務淺析Android非同步
- 在Android上測試非同步任務Android非同步
- 同步任務與非同步任務執行順序非同步
- [譯] 在 Flask 中使用 Redis Queue 實現非同步任務FlaskRedis非同步
- 基於Django與Celery實現非同步佇列任務Django非同步佇列
- rsync的自動指令碼實現方案(非rsyncd服務)指令碼
- SprinBoot~實現使用非同步任務優化郵件傳送boot非同步優化
- servlet非同步任務Servlet非同步
- Android執行緒管理之AsyncTask非同步任務Android執行緒非同步
- 淺談JS阻塞方式怎麼實現非同步任務佇列?JS非同步佇列
- js的setTimeout和Promise—同步非同步和微任務巨集任務JSPromise非同步
- SpringBoot與非同步任務、定時任務、郵件任務Spring Boot非同步
- 在dotnet core實現類似crontab的定時任務
- Celery非同步任務框架非同步框架
- 終止非同步任務非同步
- 怎樣實現一個非阻塞的超時重試任務佇列佇列
- 定時任務的實現
- Asynq 實現 Go 後臺作業非同步定時任務處理Go非同步
- centos(php7)下gearman實現非同步處理佇列任務CentOSPHP非同步佇列
- Android開發 - Runnable 類任務介面定義與後臺任務待辦解析Android
- python中非同步非阻塞如何實現Python非同步
- Android 非同步任務知識梳理(1) AsyncTask 原始碼解析Android非同步原始碼
- Android 非同步任務知識梳理(3) AsyncQueryHandler 原始碼解析Android非同步原始碼
- 非同步任務取消、超時非同步
- django + redis + celery 非同步任務DjangoRedis非同步
- Java如何實現定時任務?Java
- JavaScript有同步任務和非同步任務,瀏覽器是怎麼處理的?JavaScript非同步瀏覽器
- 前端急速解決非同步之微任務和巨集任務前端非同步