教你寫Http框架(二)——三個樣例帶你深入理解AsyncTask
這個標題大家不要奇怪,扯Http框架怎麽扯到AsyncTask去了,有兩個原因:首先是Http框架除了核心http理論外。其技術實現核心也是線程池 + 模板 + handler,而AsyncTask又正好也是這三者的完美結合。其次,也是自己在面試中發現大量的安卓開發人員全然不了解AsyncTask的原理和技術細節。而AsyncTask的思想在我們設計app框架和性能調優的時候是非常實用的。所以這裏特地寫一篇關於AsyncTask的博文。
老規矩,我的習慣還是通過寫demo,把核心技術一點點剝離出來。一步步看完你就能深入理解其技術本質了。
第一個樣例,先理解Java的線程池和FutureTask。
先說線程池。Java提供了一個非常重要的接口就是Executor。差點兒全部重要的線程池實現都繼承自這個接口。只是這個不是我們今天的重點。詳細請查看Java的API手冊,我們上代碼看一下一般線程池是怎麽實例化的。
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> workQueue =
new LinkedBlockingQueue<Runnable>(10);
private static final ThreadFactory threadFactory = new ThreadFactory()
{
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r)
{
return new Thread(r, "AsyncTask #" + count.getAndIncrement());
}
};
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
workQueue,
threadFactory);
敏感的同學會發現。這個就是AsyncTask的線程池的源代碼,的確。正常的需求,這段代碼實例化出來的線程池基本都能夠滿足了。其它參數看命名都非常容易理解,這裏主要講一下workQueue。由於我們會不斷提交任務給線程池運行,而線程池的線程數量是有限的,當全部核心線程都處於工作狀態時。client再次提交的任務放在哪裏呢?我這麽一問你就懂了吧。
再講一下java的FutureTask,我們知道正常情況下我們須要一個線程運行,提交的是一個Runnable。但有時候我們希望線程運行結束時帶回一個處理完畢的數據。這個時候Runnable就無力了,這個時候就要看FutureTask了。大家有興趣能夠看一下它的源代碼。事實上它也是繼承自Runnable的,所以能夠直接提交給線程來運行。
一般正常調用FutureTask的方法例如以下代碼:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
public class Test1
{
public static void main(String[] args)
{
Test1 test = new Test1();
test.test();
}
public void test()
{
FutureTask<String> fTask = new FutureTask<String>(new Callable<String>()
{
@Override
public String call() throws Exception
{
System.out.println("calling");
return "hello";
}
})
{
@Override
protected void done()
{
try
{
System.out.println("done " + get());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (ExecutionException e)
{
e.printStackTrace();
}
super.done();
}
};
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(fTask);
}
}
以上代碼的運行結果為:
calling
done hello
所以,我們在線程結束時拿到了終於的線程處理結果。而AsyncTask在onPostExecute中給你結果的時候,就是這麽幹的。
第二個樣例,我們來點幹貨。我們先寫個AsyncTask的樣例,跑起來並看下運行結果。先代碼:
package com.amuro.activity;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.amuro.R;
public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_layout);
findViewById(R.id.bt).setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
testAsync();
}
});
}
private void testAsync()
{
for(int i = 0; i < 10; i++)
{
final int j = i;
AsyncTask<String, Integer, String> aTask =
new AsyncTask<String, Integer, String>()
{
@Override
protected void onProgressUpdate(Integer... values)
{
super.onProgressUpdate(values);
}
@Override
protected String doInBackground(String... params)
{
Log.e("amuro", Thread.currentThread().getName());
try
{
Thread.sleep(3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return params[0] + "done";
}
@Override
protected void onPostExecute(String s)
{
Log.e("amuro", "result: " + s + " " + j);
}
};
aTask.execute("DoubleX");
}
}
}
看下運行結果:
03-13 11:23:47.950 22777-23081/com.amuro E/amuro: AsyncTask #1
03-13 11:23:50.955 22777-22777/com.amuro E/amuro: result: DoubleXdone 0
03-13 11:23:50.955 22777-23120/com.amuro E/amuro: AsyncTask #2
03-13 11:23:53.960 22777-22777/com.amuro E/amuro: result: DoubleXdone 1
03-13 11:23:53.960 22777-23195/com.amuro E/amuro: AsyncTask #3
03-13 11:23:56.965 22777-22777/com.amuro E/amuro: result: DoubleXdone 2
03-13 11:23:56.965 22777-23236/com.amuro E/amuro: AsyncTask #4
03-13 11:23:59.960 22777-22777/com.amuro E/amuro: result: DoubleXdone 3
03-13 11:23:59.965 22777-23277/com.amuro E/amuro: AsyncTask #5
03-13 11:24:02.965 22777-22777/com.amuro E/amuro: result: DoubleXdone 4
03-13 11:24:02.965 22777-23277/com.amuro E/amuro: AsyncTask #5
03-13 11:24:05.965 22777-22777/com.amuro E/amuro: result: DoubleXdone 5
03-13 11:24:05.970 22777-23277/com.amuro E/amuro: AsyncTask #5
03-13 11:24:08.975 22777-22777/com.amuro E/amuro: result: DoubleXdone 6
03-13 11:24:08.975 22777-23277/com.amuro E/amuro: AsyncTask #5
03-13 11:24:11.975 22777-22777/com.amuro E/amuro: result: DoubleXdone 7
03-13 11:24:11.975 22777-23277/com.amuro E/amuro: AsyncTask #5
03-13 11:24:14.980 22777-22777/com.amuro E/amuro: result: DoubleXdone 8
03-13 11:24:14.980 22777-23081/com.amuro E/amuro: AsyncTask #1
03-13 11:24:17.985 22777-22777/com.amuro E/amuro: result: DoubleXdone 9
能夠看到,10個任務是順序運行的,而且僅僅有5個線程在工作,好,
我們把AsyncTask剛才那個線程池和FutureTask結合起來,寫一個簡單的樣例實現和它一模一樣的功能。代碼:
package com.amuro;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Test2
{
public static void main(String[] args)
{
Test2 test = new Test2();
test.test();
}
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> workQueue =
new LinkedBlockingQueue<Runnable>(10);
private static final ThreadFactory threadFactory = new ThreadFactory()
{
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r)
{
return new Thread(r, "AsyncTask #" + count.getAndIncrement());
}
};
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
workQueue,
threadFactory);
private static volatile Executor defaultExecutor = new Executor()
{
final ArrayDeque<Runnable> tasks = new ArrayDeque<Runnable>();
Runnable activeRunnable;
@Override
public void execute(final Runnable r)
{
tasks.offer(new Runnable()
{
@Override
public void run()
{
try
{
System.out.println(Thread.currentThread().getName());
r.run();
}
finally
{
scheduleNext();
}
}
});
if(activeRunnable == null)
{
scheduleNext();
}
}
protected synchronized void scheduleNext()
{
if((activeRunnable = tasks.poll()) != null)
{
THREAD_POOL_EXECUTOR.execute(activeRunnable);
}
}
};
public void test()
{
List<FutureTask<String>> fList = new ArrayList<FutureTask<String>>();
for(int i = 0; i < 10; i++)
{
final int j = i;
fList.add(new FutureTask<String>(new Callable<String>()
{
@Override
public String call() throws Exception
{
Thread.sleep(3000);
return "I‘m callable " + j;
}
}){
@Override
protected void done()
{
try
{
System.out.println(get() + " done");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (ExecutionException e)
{
e.printStackTrace();
}
}
}
);
}
for(FutureTask<String> fTask : fList)
{
defaultExecutor.execute(fTask);
}
}
}
先看運行結果:
AsyncTask #1
I’m callable 0 done
AsyncTask #2
I’m callable 1 done
AsyncTask #3
I’m callable 2 done
AsyncTask #4
I’m callable 3 done
AsyncTask #5
I’m callable 4 done
AsyncTask #5
I’m callable 5 done
AsyncTask #5
I’m callable 6 done
AsyncTask #5
I’m callable 7 done
AsyncTask #5
I’m callable 8 done
AsyncTask #5
I’m callable 9 done
是不是一模一樣~沒錯事實上我們正常調用AsyncTask的execute方法的時候,就是調用了這個defaultExecutor。它的作用就是維持了一個雙向的任務隊列,當AsyncTask的execute方法運行的時候,它就把client提交的任務塞到了這個隊列裏,假設這時候沒有任務在運行。activeRunnable就為null,則scheduleNext方法直接調用,這個剛被提交的任務就會從隊列中被取出交給線程池區運行,運行完畢後又會繼續調用scheduleNext方法,有任務就會繼續運行下一個任務。所以你看到的結果就是這樣一個順序運行。而且線程池僅僅使用了5個線程,充分利用了資源。補充一點,AsyncTask的源代碼中,假設你想把全部任務改為並行運行,是能夠傳一個自己的Executor進來的。可是這種方法被hide了。看來是官方不建議大家這麽做。
理解了上面兩個樣例的話。第三個樣例寫起來就so easy了,沒錯,理解輪子的最好試金石就是自己寫個輪子。所以以下我們就是要簡單地寫一個自己的AsyncTask。和java直接run最大的差別就是安卓的非UI線程不能操作UI線程的實例,這個時候,把handler君請過來就好了嘛~ 還是先看代碼。我們自己定義一個MyAsyncTask:
package com.amuro.thread;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import java.util.ArrayDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.LogRecord;
/**
* Created by Echo on 2016/3/12.
*/
public abstract class MyAsyncTask<Params, Result>
{
/*************線程池核心代碼*******************/
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> workQueue =
new LinkedBlockingQueue<Runnable>(10);
private static final ThreadFactory threadFactory = new ThreadFactory()
{
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r)
{
return new Thread(r, "MyAsyncTask #" + count.getAndIncrement());
}
};
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
workQueue,
threadFactory);
private static volatile Executor defaultExecutor = new Executor()
{
final ArrayDeque<Runnable> tasks = new ArrayDeque<Runnable>();
Runnable activeRunnable;
@Override
public void execute(final Runnable r)
{
tasks.offer(new Runnable()
{
@Override
public void run()
{
try
{
r.run();
}
finally
{
scheduleNext();
}
}
});
if(activeRunnable == null)
{
scheduleNext();
}
}
protected synchronized void scheduleNext()
{
if((activeRunnable = tasks.poll()) != null)
{
THREAD_POOL_EXECUTOR.execute(activeRunnable);
}
}
};
/****************消息處理核心代碼************************/
private static final int MESSAGE_POST_RESULT = 0x01;
private static class AsyncTaskResult<Data>
{
final MyAsyncTask mTask;
final Data[] mData;
AsyncTaskResult(MyAsyncTask task, Data... data)
{
mTask = task;
mData = data;
}
}
private static abstract class WorkerRunnable<Params, Result>
implements Callable<Result> {
Params[] mParams;
}
private static final Handler handler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what)
{
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]);
break;
}
}
};
private final WorkerRunnable<Params, Result> workerRunnable;
private final FutureTask<Result> futureTask;
public MyAsyncTask()
{
workerRunnable = new WorkerRunnable<Params, Result>()
{
@Override
public Result call() throws Exception
{
return postResult(doInBackground(mParams));
}
};
futureTask = new FutureTask<Result>(workerRunnable);
}
private Result postResult(Result result)
{
Message message = handler.obtainMessage(
MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
private void finish(Result result)
{
onPostExecute(result);
}
protected void onPreExecute(){}
protected abstract Result doInBackground(Params... params);
protected void onPostExecute(Result result){}
public final MyAsyncTask<Params, Result> execute(Params... params)
{
return executeOnExecutor(defaultExecutor, params);
}
public final MyAsyncTask<Params, Result> executeOnExecutor(Executor executor, Params... params)
{
onPreExecute();
workerRunnable.mParams = params;
executor.execute(futureTask);
return this;
}
}
然後再看一下調用的代碼:
package com.amuro.activity;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.amuro.R;
import com.amuro.thread.MyAsyncTask;
public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_layout);
findViewById(R.id.bt).setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
testAsync();
}
});
findViewById(R.id.bt1).setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
testMyAsync();
}
});
}
private void testAsync()
{
for(int i = 0; i < 10; i++)
{
final int j = i;
AsyncTask<String, Integer, String> aTask =
new AsyncTask<String, Integer, String>()
{
@Override
protected void onProgressUpdate(Integer... values)
{
super.onProgressUpdate(values);
}
@Override
protected String doInBackground(String... params)
{
Log.e("amuro", Thread.currentThread().getName());
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return params[0] + "done";
}
@Override
protected void onPostExecute(String s)
{
Log.e("amuro", "result: " + s + " " + j);
}
};
aTask.execute("DoubleX");
}
}
private void testMyAsync()
{
for(int i = 0; i < 10; i++)
{
final int j = i;
MyAsyncTask<String, String> myTask = new MyAsyncTask<String, String>()
{
@Override
protected String doInBackground(String... params)
{
Log.e("amuro", Thread.currentThread().getName());
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return params[0] + "done";
}
@Override
protected void onPostExecute(String s)
{
Log.e("amuro", "result: " + s + " " + j);
}
};
myTask.execute("outSideParam ");
}
}
}
再看一下運行結果:
03-13 13:15:55.065 20514-20732/com.amuro E/amuro: MyAsyncTask #1
03-13 13:15:56.070 20514-20514/com.amuro E/amuro: result: outSideParam done 0
03-13 13:15:56.070 20514-20747/com.amuro E/amuro: MyAsyncTask #2
03-13 13:15:57.075 20514-20514/com.amuro E/amuro: result: outSideParam done 1
03-13 13:15:57.075 20514-20758/com.amuro E/amuro: MyAsyncTask #3
03-13 13:15:58.075 20514-20514/com.amuro E/amuro: result: outSideParam done 2
03-13 13:15:58.075 20514-20758/com.amuro E/amuro: MyAsyncTask #3
03-13 13:15:59.075 20514-20514/com.amuro E/amuro: result: outSideParam done 3
03-13 13:15:59.075 20514-20758/com.amuro E/amuro: MyAsyncTask #3
03-13 13:16:00.080 20514-20514/com.amuro E/amuro: result: outSideParam done 4
03-13 13:16:00.080 20514-20758/com.amuro E/amuro: MyAsyncTask #3
03-13 13:16:01.080 20514-20514/com.amuro E/amuro: result: outSideParam done 5
03-13 13:16:01.080 20514-20758/com.amuro E/amuro: MyAsyncTask #3
03-13 13:16:02.080 20514-20514/com.amuro E/amuro: result: outSideParam done 6
03-13 13:16:02.080 20514-20758/com.amuro E/amuro: MyAsyncTask #3
03-13 13:16:03.085 20514-20514/com.amuro E/amuro: result: outSideParam done 7
03-13 13:16:03.085 20514-20758/com.amuro E/amuro: MyAsyncTask #3
03-13 13:16:04.085 20514-20514/com.amuro E/amuro: result: outSideParam done 8
03-13 13:16:04.090 20514-20732/com.amuro E/amuro: MyAsyncTask #1
03-13 13:16:05.095 20514-20514/com.amuro E/amuro: result: outSideParam done 9
暴露了我的測試機弱爆了。Orz。
為了簡單起見這裏就不處理onProgressUpdate了,有興趣的同學能夠在這個基礎上自己去實現。
我在這裏總結一下execute方法運行的整個流程。
1. 先回調了onPreExecute方法,這個是在UI線程裏的。然後把外面傳入的params賦值給了workerRunnable,事實上就是FutureTask須要的Callable對象。
2. 然後就把這個FutureTask丟給了我們的defaultExecutor去運行。這個流程和上面的樣例二是一樣一樣的。
3. 運行成功後子線程完畢了結果的生成,這個時候就能夠通過handler把結果丟給UI線程了。這裏封裝了一個AsyncTaskResult類來傳遞結果,原因非常easy。handler是靜態對象。沒法直接拿到當前MyAsyncTask的引用。而我們要把task和result對象同一時候丟給handler。所以要進行一下封裝。
4. OK。handler拿到result之後就會把task拿出來並回調finish方法。
5. finish方法。這個時候已經在UI線程中了,所以能夠回調終於的onPostExecute方法把結果丟給client去處理了。
不管多麽復雜的技術或實現,僅僅要我們抓到其本質,耐心地把它涉及到的知識一點點的吃透,並多寫代碼多做測試。
終於你會發現,再復雜,只是也是小知識的層疊和擴展罷了。這和一個互聯網公司須要深厚的技術積累,道理也是一樣的。
就醬。謝謝欣賞~
教你寫Http框架(二)——三個樣例帶你深入理解AsyncTask