編輯:關於Android編程
AsyncTask在Android十分常用,那為什麼如此常用呢,不用行不行呢,內部又是怎麼實現的呢,為什麼Java的API中沒有這個類呢,看完本文後,你將會知道答案。
這裡說有設計思想是我根據查看Android源代碼提煉出來的代碼邏輯,所以不會跟Google工程師的原始設計思想100%符合(也有可能是0%),但是本文一定可以幫助你理解AsyncTask,也可能有一些你以前沒有發現的內容。
大家都知道,Android的主線程(又叫UI線程,線程ID為1)有一些限制策略,使得主線程有些事做不了,比如訪問網絡就不允許,否則就是報,但在2.3之後的版本,你可以通過添加以下代碼更改其限制策略,從而強制使得主線程可以訪問網絡:
if (android.os.Build.VERSION.SDK_INT > 9) { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); }不過StrictMode是一個開發工具主要用於偵測主線程中的磁盤和網絡訪問,而不是讓你做這“壞”事,其實Android這樣限制是有好處的,強制讓開發者重視用戶體驗,一個反面例子是Windows,主線程裡什麼都可以做,一些很懶的開發者就把所有任務放到主線程裡,使得線程經常好卡,比如編輯器UE或Notepad++打開了一個網絡上(如samba服務器)的文件,如果突然網絡中斷了,那你的整個編輯器都卡住,要等好久才會有反應,不過我不確定那是不是因為主線程裡訪問了網絡,不過Windows經常因為這個原因卡。還有一個正面例子是iOS,極其注意響應速度,所以當有用戶輸入事件時,其內核都有相應的調度,從而優先響應用戶操作。
還是回到正題,就是因為主線程的這些限制使開發者不得不寫多個線程,當然,你也可以不用AsyncTask,不過你不用也避免不了多線程,如果你不用,就是可能要用Handler和Thread了,我想很多人初學的時候就是那麼干的,包括我,因為那時很有可能還沒有發現有這個類,於是就經常寫Handler和Thread的了,寫著寫著就發現有一些代碼是相同的,你寫的Handler和Thread匿名類肯定是重復代碼,如下:
final Handler handler = new Handler() { public void handleMessage(Message msg) { System.out.println("The hard have done!"); // ... front end code } }; new Thread() { public void run() { doHardWork(); handler.sendEmptyMessage(0); } private void doHardWork() { // ... back end code } }.start();
你可能想到要復用這些代碼,當然,你可以通過Copy的方式來復用這段代碼,用的時候只要在省略號處寫入你的代碼就可以了,但更好的復用是將其用一個類封裝起來,好吧,那我們就簡單的封裝一下吧,於是,就變成了這樣:
public class HandlerAndThread { private Handler handler = new Handler() { public void handleMessage(Message msg) { System.out.println("The hard have done!"); //... } }; public void doInBackground() { new Thread() { public void run() { doHardWork(); handler.sendEmptyMessage(0); } private void doHardWork() { // ... } }; } }這樣好像還不行,因為無法告訴後台線程做什麼事,做完了也不知道通知,要復用還是得Copy代碼,我們可以加兩個方法,一個在前台調用一個在後台調用,只要定義一個新類就可以實現復用,於是代碼變成這樣:
public class HandlerAndThread { private Handler handler = new Handler() { public void handleMessage(Message msg) { System.out.println("The hard have done!"); runInFrontend(); // added } }; public void doInBackground() { new Thread() { public void run() { doHardWork(); handler.sendEmptyMessage(0); } private void doHardWork() { runInBackend(); //added } }; } //added protected void runInBackend() { } //added protected void runInFrontend() { } }一個可復用的類就出爐了,我們寫一個子類,並用一個Activity來調用一下吧:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new SubHandlerAndThread().doInBackground(); } class SubHandlerAndThread extends HandlerAndThread { protected void runInBackend() { try { Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } protected void runInFrontend() { System.out.println("Task has been done"); } } }
這樣是不是比直接寫Thread和Handler簡潔了許多呢,這裡我是用sleep來模似長時間事務的,如果在真實的環境中,我們可能是要下載,如果是下載,我們可能希望傳入個下載地址的參數到後台線程,來讓他按我們的需要下載,我們給加doInBackground方法加一個參數,於是HandlerAndThread類的代碼就變成這樣:
public class HandlerAndThread { ... public void doInBackground(final String url) { // added url new Thread() { public void run() { doHardWork(); handler.sendEmptyMessage(0); } private void doHardWork() { runInBackend(url); // added url } }; } protected void runInBackend(String url) { // added url } ... }而調用類的代碼變成這樣:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String url = "http://path/to/file"; new SubHandlerAndThread().doInBackground(url); //added url } class SubHandlerAndThread extends HandlerAndThread { @Override protected void runInBackend(String url) { // added url System.out.println("Start download from url:" + url); try { Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override protected void runInFrontend() { System.out.println("finish download"); } } }假如是下一個文件呢,我們是不是加一個進度更新的方法呢,於是又變成這樣:
public class HandlerAndThread { private Handler handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { // added case 0: runInFrontend(); break; case 1: runInFrontendProgress(msg.arg1); break; } } }; ... final protected void publishProgress(int progress) { // added handler.obtainMessage(1, progress, 0); } protected void runInFrontendProgress(int progress) { // added } } public class MainActivity extends Activity { ... class SubHandlerAndThread extends HandlerAndThread { @Override protected void runInBackend(String url) { System.out.println("Start download from url:" + url); for (int i = 0; i < 10; ++i) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrack(); } publishProgress(i*10); // added } } ... @Override protected void runInFrontendProgress(int progress) { // added System.out.println("Progress: " + progress); } } }你可能已經沒有耐心一版一版的進化了,那我就跳躍一下吧,一次多加幾條需要:一、我們下載完了可能要得到文件的路徑,所以我們給runInFrontend方法加一個輸入參數filePath表示路徑;二、把子類必須實現的方法改成抽象方法,類也改成抽象方法;我把代碼中的一些方法名改一下,使其更好理解,把doInBackground改為execute,把runInFrontend改為onPostExecute,把runInFrontendProgress改為onProgressUpdate。最終版如下:
public abstract class HandlerAndThread { private Handler handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 0: onPostExecute((String) msg.obj); break; case 1: onProgressUpdate(msg.arg1); break; } } }; public void doInBackground(final String url) { new Thread() { public void run() { String result = runInBackend(url); handler.obtainMessage(0, result); } }; } final protected void publishProgress(int progress) { handler.obtainMessage(1, progress, 0); } abstract protected String runInBackend(String url); protected void onPostExecute(String filePath) { } protected void onProgressUpdate(int progress) { } } public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String url = "http://path/to/file"; new SubHandlerAndThread().doInBackground(url); } class SubHandlerAndThread extends HandlerAndThread { @Override protected String runInBackend(String url) { System.out.println("Start download from url:" + url); for (int i = 0; i < 10; ++i) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } publishProgress(i*10); } return "/path/to/file"; } @Override protected void onPostExecute(String filePath) { System.out.println("Download finished"); } @Override protected void onProgressUpdate(int progress) { System.out.println("Progress: " + progress); } } }這是不是跟Android的AsyncTask很像呢,我想Google就是由於這種需求做出這個類的,Android官網是這樣描述AsyncTask的:
This class allows to perform background operations and publish results on the UI thread without having to manipulate threads
and/or handlers.
意思是這個類使得:不使用Thread和Handler,就可以在後台執行操作然後在發布結果到UI線程。其實他內部的實現就是封裝了Thread和Handler,所以你就不必要直接用這兩個低層類了,但他的目的也是代碼復用,他的實現跟我們上面寫的類也差不多。主要有這幾點不同:一、AsyncTask使用了線程池而不是單個線程去執行後台任務,該線程池是整個進程共用的,是因為他的線程池對象是一個靜態成員變量,這一點很多人搞錯,誤以為AsyncTask越來創建的線程越多,這不是絕對正確的,因為線程池會根據負載動態調整的,而且有最大值和空閒超時,AsyncTask的配置是最小5,最大128,空閒超時1秒,當然你也可以配置成線程數根據任務數線程遞增,關於線程池,可參考這裡,後續我會在博客寫文章討論Java線程池;二、AsyncTask的輸入和輸出參數使用了泛型;三、AsyncTask支持中斷當前任務。
現在知道了AsyncTask的設計思想了吧,是不是很簡單呢,所以建議童鞋們去看一下它的源碼,反正我寫代碼時有查看源碼的習慣,因為我會好奇它是如何實現的,看源碼有很多好處,如可以學到好的API設計思想,軟件架構,特別是當你遇到技術問題無法解決,網上也搜索不到時,你可以通過查看源碼來找到解決辦法,我就經常這麼干。
所以提議大家在開發時學會看源碼利用源碼,一定要耐心,一篇看不懂不要氣餒,多看幾篇一定會看懂的。
參考資料:
Android官網API說明
安裝SVN服務端安裝VisualSVN-Server,我的電腦是XP,所以安裝的VisualSVN-Server-2.5.8.msi版本。 這裡要注
綜述對於MVP (Model View Presenter)架構是從著名的MVC(Model View Controller)架構演變而來的。而對於Android應用的開
今天練手一下,一起來畫個太極圖吧~最終效果如下:最終效果一般都是先講原理,我就反其道而行,先講實現吧。1.繼承實現初始化方法繼承View,實現基本的構造函數:public
歡迎大家關注Android開源網絡框架NoHttp:https://github.com/yanzhenjie/NoHttp 我們在實際開發中,很多App都