Android異步任務(AsyncTask)的設計思想

AsyncTask在Android十分經常使用,那爲何如此經常使用呢,不用行不行呢,內部又是怎麼實現的呢,爲何Java的API中沒有這個類呢,看完本文後,你將會知道答案。html

這裏說有設計思想是我根據查看Android源代碼提煉出來的代碼邏輯,因此不會跟Google工程師的原始設計思想100%符合(也有多是0%),可是本文必定能夠幫助你理解AsyncTask,也可能有一些你之前沒有發現的內容。java

你們都知道,Android的主線程(又叫UI線程,線程ID爲1)有一些限制策略,使得主線程有些事作不了,好比訪問網絡就不容許,不然就是報,但在2.3以後的版本,你能夠經過添加如下代碼更改其限制策略,從而強制使得主線程能夠訪問網絡:android

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來調用一下吧:oracle

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
    }
    
    ...
}

而調用類的代碼變成這樣:ide

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);
        }
    }
}

你可能已經沒有耐心一版一版的進化了,那我就跳躍一下吧,一次多加幾條須要:1、咱們下載完了可能要獲得文件的路徑,因此咱們給runInFrontend方法加一個輸入參數filePath表示路徑;2、把子類必須實現的方法改爲抽象方法,類也改爲抽象方法;我把代碼中的一些方法名改一下,使其更好理解,把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,因此你就沒必要要直接用這兩個低層類了,但他的目的也是代碼複用,他的實現跟咱們上面寫的類也差很少。主要有這幾點不一樣:1、AsyncTask使用了線程池而不是單個線程去執行後臺任務,該線程池是整個進程共用的,是由於他的線程池對象是一個靜態成員變量,這一點不少人搞錯,誤覺得AsyncTask越來建立的線程越多,這不是絕對正確的,由於線程池會根據負載動態調整的,並且有最大值和空閒超時,AsyncTask的配置是最小5,最大128,空閒超時1秒,固然你也能夠配置成線程數根據任務數線程遞增,關於線程池,可參考這裏,後續我會在博客寫文章討論Java線程池;2、AsyncTask的輸入和輸出參數使用了泛型;3、AsyncTask支持中斷當前任務。

如今知道了AsyncTask的設計思想了吧,是否是很簡單呢,因此建議童鞋們去看一下它的源碼,反正我寫代碼時有查看源碼的習慣,由於我會好奇它是如何實現的,看源碼有不少好處,如能夠學到好的API設計思想,軟件架構,特別是當你遇到技術問題沒法解決,網上也搜索不到時,你能夠經過查看源碼來找到解決辦法,我就常常這麼幹。

因此提議你們在開發時學會看源碼利用源碼,必定要耐心,一篇看不懂不要氣餒,多看幾篇必定會看懂的。


參考資料:

Android官網API說明

相關文章
相關標籤/搜索