編輯: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());
}
}
}
Android aidl Binder框架淺析
1、概述 Binder能干什麼?Binder可以提供系統中任何程序都可以訪問的全局服務。這個功能當然是任何系統都應該提供的,下面我們簡單看一下Android的Bi
Android事件總線還能怎麼玩?
顧名思義,AndroidEventBus ( github鏈接 : https://github.com/bboyfeiyu/AndroidEventBus )是
Android include標簽方法淺析
本文將介紹在Android中如何使用include標簽來更便捷地重用布局代碼,從而減少冗余的Android代碼。 在一個Android項目中我們可能會需要用到相同
Android官方導航欄ActionBar使用詳解
一、ActionBar概述 ActionBar是androiD3.0以後新增的組件,主要用於標示應用程序以及用戶所處的位置並提供相關操作以及全局的導航功能。下面我