Android非同步載入AsyncTask詳解
曾看見有人說過,覺得很有道理,分享一下:
技術分為術和道兩種:
(1)具體做事的方法是術;
(2)做事的原理和原則是道;
最近專案發現個重大問題,結果打log跟蹤查是AsyncTask導致的。如果對AsyncTask瞭解的不夠深入透徹,那寫程式碼就是埋雷。以後不定在哪個時間爆炸。首先我們要了解,谷歌為什麼發明AsyncTask,AsyncTask到底是用來解決什麼問題的?Android有一個原則---單執行緒模型的原則:UI操作並不是執行緒安全的並且這些操作必須在UI執行緒中執行。所以谷歌就製造AsyncTask,AsyncTask擴充套件Thread增強了與主執行緒的互動的能力。如果你的應用沒有與主執行緒互動,那麼就直接使用Thread就好了。
在單執行緒模型中始終要記住兩條法則:
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}.)</p>
*
* <p>Here is an example of subclassing:</p>
* <pre class="prettyprint">
* private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
* 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<Contact> contacts = new ArrayList<Contact>();
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,則捨棄。
最後要說明的就是資料要載入一部分就重新整理UI,給使用者一個好的使用者體驗;舉個栗子,
private static final int DISPLAY_NUM = 10;
private List<Contact> mContacts = new ArrayList<Contact>();
private class ContactsListLoader extends
AsyncTask<Object, Integer, Integer> {
int count = 0;
List<Contact> mTempContacts = new ArrayList<Contact>(DISPLAY_NUM);
@Override
protected void onPreExecute() {
super.onPreExecute();
mTempContacts.clear();
mContacts.clear();
}
@Override
protected Integer doInBackground(Object... arg0) {
List<Contact> contacts = new ArrayList<Contact>();
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,可以參考原始碼,如果有人瞭解的更深入,歡迎童鞋拍磚留言;