Android異步加載AsyncTask詳解

最近項目發現個重大問題,結果打log跟蹤查是AsyncTask導致的。如果對AsyncTask瞭解的不夠深入透徹,那寫代碼就是埋雷。以後不定在哪個時間爆炸。首先我們要瞭解,谷歌為什麼發明AsyncTask,AsyncTask到底是用來解決什麼問題的?Android有一個原則—單線程模型的原則:UI操作並不是線程安全的並且這些操作必須在UI線程中執行。

在單線程模型中始終要記住兩條法則:
1. 不要阻塞UI線程
2. 確保隻在UI線程中訪問Android UI工具包

首先來說說AsyncTask重寫的4個方法:

(1)doInBackground() //運行在後臺線程中

(2)onPreExecute() //運行在UI線程中

(3)onProgressUpdate() //運行在UI線程中

(4)onPostExecute() //運行在UI線程中

詳細的這幾個方法怎麼使用,具體不清楚的可以谷歌一下,好多同仁講的好詳細的;

為瞭正確的使用AsyncTask類,以下是幾條必須遵守的準則:
1) Task的實例必須在UI thread中創建
2) execute方法必須在UI thread中調用
3) 不要手動的調用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)這幾個方法
4) 該task隻能被執行一次,否則多次調用時將會出現異常
doInBackground方法和onPostExecute的參數必須對應,這兩個參數在AsyncTask聲明的泛型參數列表中指定,第一個為doInBackground 接 受的參數,第二個為顯示進度的參數,第第三個為doInBackground返回和onPostExecute傳入的參數。

以上四點是我從網摘過來的,我覺得說的有道理,針對第4點,我有異議:即使多次調用,也不應該出現異常,因為AsyncTask類有對外公開的接口,cancel(true),isCancelled()。這兩個方法,這兩個方法配合使用就來控制AsyncTask可以手動退出。具體可以參照AsyncTask.java這個源碼類中有例子的。

AsyncTask must be subclassed to be used. The subclass will override at least
 * one method ({@link #doInBackground}), and most often will override a
 * second one ({@link #onPostExecute}.)

* *

Here is an example of subclassing:

*
 * private class DownloadFilesTask extends AsyncTask {
 *     protected Long doInBackground(URL... urls) {
 *         int count = urls.length;
 *         long totalSize = 0;
 *         for (int i = 0; i < count; i++) {
 *             totalSize += Downloader.downloadFile(urls[i]);
 *             publishProgress((int) ((i / (float) count) * 100));
 *             // Escape early if cancel() is called
 *             if (isCancelled()) break;
 *         }
 *         return totalSize;
 *     }
 *
 *     protected void onProgressUpdate(Integer... progress) {
 *         setProgressPercent(progress[0]);
 *     }
 *
 *     protected void onPostExecute(Long result) {
 *         showDialog("Downloaded " + result + " bytes");
 *     }
 * }

這個是AsyncTask中給出的demo,看到

 if (isCancelled()) break;

這個就是用來判斷是否退出後臺線程,如果設置瞭cancel(true),就break跳出循環。在這個地方多說2點:

(1)終止線程最好不要用打斷線程來做,這樣的方式太粗暴瞭,而且不能保證代碼的完整性,最好的處理方式就是在for循環,while循環中加入自己的判斷標志位,就像AsyncTask這種方法來處理是最好的,這也是谷歌來指導我們怎麼來處理終止線程的辦法。

(2)也有同學感到疑惑,說我的代碼就沒有循環,怎麼來加標志位,其實這個一般來說後臺處理,大部分都是處理循環的邏輯,很少說一行代碼或者十幾行代碼很耗時的,(當然網絡相關的另說瞭,還有下載相關的,這個有其他方法來解決的)。即使有的話,比如調用jni,so庫,返回就是慢。那就在幾個耗時的方法的後面都加上標志位的判斷;

通過上述方法就可以做出完整的方案設計,就能設計,當下次再次執行AsyncTask,先判斷自己是否正在運行,如果在運行,就不執行或取消任務重新執行,這個要看具體的需求是什麼瞭;

舉個栗子:

private void stopAyncTaskRunning() {
		if (mContactsListLoader != null
				&& mContactsListLoader.getStatus() == AsyncTask.Status.RUNNING) {
			mContactsListLoader.cancel(true); //if task is still running, stop it;
		}

	}

private void getContactsList() {
		stopAyncTaskRunning();
		mContactsListLoader = new ContactsListLoader();
		mContactsListLoader.executeOnExecutor(AsyncTask.THEAD_POOL_EXECUTOR);
	}

當然,我的這個需求是下次進來的時候,就取消上次的任務,然後重新刷新數據。另外也不要忘記在doInBackground()中的循環語句中加入

@Override
		protected Integer doInBackground(Object... arg0) {
			
			List contacts = new ArrayList();
			ContentResolver cr = mContext.getContentResolver();
			Cursor c = cr.query(ContactsContract.Contacts.CONTENT_URI,
					PROJECTION_CONTACT, null, null, null);
			if (c != null) {
				c.moveToPosition(-1);
				while (c.moveToNext()) {
					if (isCancelled()) {
						break;
					}
。。。 。。。
}

這樣,邏輯按照需求來寫,需求是什麼樣子的,邏輯就相應的怎麼處理;

再來說說AsyncTask坑人的地方,就是在Android3.0以後的版本,AsyncTask的執行方法分為2個瞭:

(1)execute()
(2)executeOnExecutor()

如果用AsyncTask調用(1)的時候,就表示串行執行線程,如果這個Activity中有4個fragment,而且每個fragment都有一個AsyncTask,這樣的話用(1)的話,就必須順序執行,等一個執行完,第二個才執行。如果用方法(2),則可以串行執行,這個UI效果就很好瞭。線程池可以用系統的,也可以用我們自定義的線程池;

另外對系統默認線程池中執行線程數的一些說明,如下:

下面的5代表corePoolSize,10代表阻塞隊列的長度,128代表maximumPoolSize
1:如果線程池的數量小於5,則創建新的線程並執行
2:如果線程數大於5且小於5+10(阻塞隊列大小),則將第6~15的線程加入阻塞隊列,待線程池中的5個正在運行的線程有某個結束後,取出阻塞隊列的線程執行。
3:如果線程數為16~128,則運行的線程數為num-10
4:如果線程數大於128,則舍棄。

詳細的的介紹可以參考博客: Android實戰技巧:深入解析AsyncTask,這篇講解的很詳細;

最後要說明的就是數據要加載一部分就刷新UI,給用戶一個好的用戶體驗;舉個栗子,

private static final int DISPLAY_NUM = 10;
	private List mContacts = new ArrayList();
	
	private class ContactsListLoader extends
			AsyncTask {

		int count = 0;
		List mTempContacts = new ArrayList(DISPLAY_NUM);

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			mTempContacts.clear();
			mContacts.clear();
		}

		@Override
		protected Integer doInBackground(Object... arg0) {

			List contacts = new ArrayList();
			if (c != null) {
				c.moveToPosition(-1);
				while (c.moveToNext()) {
					if (isCancelled()) {
						break;
					}
					... ...
					
					contacts.add(contact);
					mTempContacts.add(contact);
					if (++count >= DISPLAY_NUM) {
						publishProgress();
						count = 0;
					}
				}
			}
			return contacts.size();
		}

		@Override
		protected void onProgressUpdate(Integer... values) {
			super.onProgressUpdate(values);
			mContacts.addAll(mTempContacts);
			mTempContacts.clear();
			mAdapter.notifyDataSetChanged();
		}

		@Override
		protected void onPostExecute(Integer size) {
			if (isCancelled())
				return;
			if (size > 0) {
				if (mTempContacts.size() > 0
						&& mTempContacts.size() != DISPLAY_NUM) {
					mContacts.addAll(mTempContacts);
				}
			} else {
				if (mTempContacts.size() > 0) {
					mContacts.addAll(mTempContacts);
				}
			}
			mAdapter.notifyDataSetChanged();
		}

	}

這個就是我的模型,大傢看懂後,就可以套到自己的代碼中去瞭。這是我用來動態加載刷新UI的邏輯,刷新出10個數據就刷新一次,這樣就可以避免次次刷新影響效率,又能保證用戶不必等到數據都加載完才能看到數據。一舉兩得。

要想瞭解更多AsyncTask,就隻能參考源碼瞭。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *