[[123542]] Today I would like to introduce a very useful asynchronous task processing class, which includes exception handling in each link of AsyncTask, large number of concurrent executions without exceptions, string data caching and other functions. And I would like to thank @马天宇(http://litesuits.com/) for giving me ideas and advice. Students who have studied the source code of the Android system will find that: in Android 2.3, the thread pool of AsyncTask is a core with 5 threads, the queue can accommodate 10 threads, and the maximum execution of 128 tasks. This has a problem. When you really have 138 concurrent tasks, even if the phone is not blown up by you, the application will definitely crash if it exceeds this indicator. Later, when it was upgraded to 3.0, in order to avoid some problems caused by concurrency, AsyncTask became a sequence executor, that is, even if you execute N AsyncTasks at the same time, it will be executed one by one in a queue. Students must pay attention to this point. After 3.0, AsyncTask is asynchronous, but not concurrent. Regarding the improvement method of this point, I have written an article before, "Thread Concurrent Request Encapsulation - In-depth Understanding of AsyncTask Class". Students who have not read it can read it here. This article is to further optimize AsyncTask on this basis. According to the Android 4.0 source code, we can see that there are two default executors in AsyncTask, ThreadPoolExecutor and SerialExecutor, which represent parallel executors and serial executors respectively. However, the default parallel executor cannot process more than 128 tasks, so we define a parallel executor based on the lru scheduling strategy. The source code can be seen here. -
-
-
-
-
- private static class SmartSerialExecutor implements Executor {
-
-
-
- private ArrayDequeCompat<Runnable> mQueue = new ArrayDequeCompat<Runnable>(
- serialMaxCount);
- private ScheduleStrategy mStrategy = ScheduleStrategy.LIFO;
-
- private enum ScheduleStrategy {
- LIFO, FIFO;
- }
-
-
-
-
-
- private static int serialOneTime;
-
-
-
- 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);
- }
- }
- }
The above is the optimization of concurrent execution of AsyncTask. Next, let’s look at the improvement of exception capture. To be honest, this is not a functional improvement, but just a development technique. The code is too long, so I deleted some of it and left only the important parts. -
-
-
-
- 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);
- }
- }
In fact, it can be seen from the code that only a try...catch is done on the code of each stage in the original AsyncTask class... But this small optimization can not only make the code tidy (I think too much try...catch really affects the beauty of the code), but also can be integrated and processed by an onPostExecuteSafely(xxx) in the end, making the structure more compact. Let AsyncTask come with data caching function When we develop apps, we always add cache processing to network access. I don't think I need to explain the reasons. So wouldn't it be better if AsyncTask itself has network JSON cache? In fact, the implementation principle is very simple, that is, put the cache method we usually write outside into AsyncTask to implement it. The comments have explained it very clearly, so I won't explain it here. -
-
-
-
- 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;
-
-
-
-
-
-
-
- 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;
-
- 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( "_" );
-
- 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();
- }
- }
- }
- });
- }
- }
-
-
-
-
- public void remove(String key) {
-
- String realKey = CipherUtils.md5(key);
- for (Map.Entry<String, Long> entry : cacheMap.entrySet()) {
- if (entry.getKey().startsWith(realKey)) {
- cacheMap.remove(realKey);
- return ;
- }
- }
- }
-
-
-
-
-
- private void saveCache(Result res) {
- if (res != null ) {
- saveResultToCache(res);
- cacheMap.put(cacheName, System.currentTimeMillis());
- }
- }
- }
|