標籤:android style blog http io color ar os 使用
前言
很久很久以前就聽說了,每一個android的應用程式都會分別運行在一個獨立的dalvik虛擬機器進程中,而在每個虛擬機器在啟動時會運行一個UI主線程(Main Thread),而為啥叫UI主線程而不是AI主線程或者是BI主線程呢?因為它要處理全部和UI相關的事件;因為Android系統採用的是UI單執行緒模式,只能由UI主線程對其進行UI操作,如果子線程抱著眾人拾柴火焰高的覺悟來幫忙UI主線程更新UI介面的話,對不起哦~Android系統就會報錯的。粗俗點講就是:我們只能通過UI主線程來蹂躪UI介面,但是其他線程來的話會被告弓雖女幹滴。。
那麼現在問題來了!鑒於近來挖掘機那麼火,我也不好意思繼續問這個問題了。。。嗯嗯~網路操作之類耗時操作就像挖掘機那樣,我們在下載檔案的時候一樣跟挖掘機挖個大坑一樣需要一定的時間;當挖掘機司機挖好一個大坑要找老闆反饋工作完成一樣,我們下載好一個檔案自然要馬上告訴螢幕前苦逼等待的使用者們,誰知道他們多著急想看**.avi呢;但是你在挖坑時好意思叫老闆在旁邊看你嗎?老闆分分鐘為幾千萬上下的事忙著呢~所以嘛同理,對於網路操作,我們當然也不能在UI主線程中進行網路操作,因為這樣會阻塞主線程造成介面卡死,也會造成ANR(應用程式無響應)。我們應該把檔案下載、檔案讀取諸如此類的耗時操作放到子線程中去進行,等到子線程耗時操作完成時通知UI介面做出響應。
不要在UI主線程中進行耗時操作
如果你不信邪一定要在UI主線程進行下載檔案、載入大檔案之類的耗時操作。如下代碼:
private Button btn;//onCreate之類的生命週期的方法就是允許在UI主線程中@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { downLoad();//調用UI主線程的下載函數 } });} private void downLoad(){ try { Thread.sleep(10000);//休眠10秒,類比網路檔案下載耗時操作 } catch (InterruptedException e) { e.printStackTrace(); }}
你會發現介面卡主了10秒:(類比下載操作的按鈕為深色,說明按鈕一直為按下狀態)
如果這時候你手比較管不住的話,雖然點幾下介面,沒事~Androi系統會馬上送你一份ANR大禮哦,而且還不用998元耶!
小結一個:不要在UI主線程中進行耗時操作,你可能會疑問什麼是UI主線程,UI主線程主要啟動並執行就是Activity、Service等裡面的生命週期方法,所以不要在生命週期方法如onCreate()中進行下載這些大事件。對於耗時操作,我們應該建立一個子線程並交給他處理,但是還需要注意一點。
不要在子線程中更新UI介面
既然我們說下載檔案要在子線程中進行,那麼我們就建立一個子線程把下載操作放到裡面進行咯,代碼如下:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.btn); text = (TextView) findViewById(R.id.text); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { new Thread(){ @Override public void run() { //在子線程中進行下載操作 try { Thread.sleep(10000);//休眠10秒,類比耗時操作 } catch (InterruptedException e) { e.printStackTrace(); } text.setText("下載完成");//設定TextView,通知UI介面下載完成 } }.start(); } });}
10秒後,你覺得會在UI介面完美顯示“下載完成”嗎?一般,出現這個才符合Androi系統的一貫作風
並且在Log中報錯如下
小弟英語其實很廢柴,但是隱隱約約有人告訴我:這不是叫只能在主線程中更新UI嗎?不信,金山翻譯一下去呀。。。。
小結一個:不要在子線程中更新UI介面,這樣會導致android系統報錯、應用崩潰退出。UI介面時單線程模式,我們只能通過UI主線程中對UI的介面進行相關的更新,千萬不要越線辦事,你要記住的是~UI介面是UI主線程的老婆,你們這些子線程誰都別想動!
利用Thread+Handler進行非同步處理
那麼問題來了,現在我們需要進行耗時操作(例如下載檔案)時不能在主線程執行,我們又需要在UI介面通知使用者我們活幹完了不能再子線程中執行。這似乎是一個棘手的熱山芋呀,幸好Google給我們提供了一個救我們於危難之中的Handler,一個能讓主線程監聽子線程發送來訊息的東東,至於Handler的實現原理我會在後面的文章詳細介紹,現在我們只需要先瞭解Handler的用法。
private Button btn;private TextView text; private Handler handler = new Handler(){ private int process = 0; @Override public void handleMessage(Message msg) { switch(msg.what){ case 0://更細下載進度 process += 1; text.setText("下載" + process + "%");//在主線程中更新UI介面 break; case 1://提示下載完成 text.setText("下載完成");//在主線程中更新UI介面 break; default: break; } }};//onCreate之類的生命週期的方法就是允許在UI主線程中@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.btn); text = (TextView) findViewById(R.id.text); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { new Thread(){ @Override public void run() { //在子線程中進行下載操作 for(int i = 0; i < 100; i++){ try { Thread.sleep(200);//休眠0.2秒,類比耗時操作 } catch (InterruptedException e) { e.printStackTrace(); } handler.sendEmptyMessage(0);//發送訊息到handler,通知下載進度 } handler.sendEmptyMessage(1);//發送消失到handler,通知主線程下載完成 } }.start(); } });}
這裡來解釋一下Handler的使用方法:
1、我們為了不阻塞主線程,將下載任務通過子線程來執行。
new Thread(){ @Override public void run() { //在子線程中進行下載操作 for(int i = 0; i < 100; i++){ try { Thread.sleep(200);//休眠0.2秒,類比耗時操作 } catch (InterruptedException e) { e.printStackTrace(); } handler.sendEmptyMessage(0);//發送訊息到handler,通知下載進度 } handler.sendEmptyMessage(1);//發送消失到handler,通知主線程下載完成 }}.start();
2、當子線程需要跟主線程交流時,也就是當子線程要跟UI主線程說:親,偶下載檔案到80%了或者偶已經把檔案下載完成了!執行這句代碼
handler.sendEmptyMessage(1);//發送消失到handler,通知主線程下載完成
3、當發送空訊息之後,在Handler將會收到子線程發來的訊息,觸發回調方法handlerMessage(),我們就在這裡對UI介面進行更新,這個回調方法是運行在UI主線程的
@Overridepublic void handleMessage(Message msg) { switch(msg.what){ case 0://更細下載進度 process += 1; text.setText("下載" + process + "%");//在主線程中更新UI介面 break; case 1://提示下載完成 text.setText("下載完成");//在主線程中更新UI介面 break; default: break; }}
4、最後,UI介面更新成功!(圖嘛,我這裡就不上了。。。。)
小結一個:對於比較耗時間的任務,我們一般需要放在子線程中執行;當子線程更新UI介面時,子線程可以通過Handler來通知主線程更新,一般通過發送訊息來觸發handlerMessage()這個回調方法來執行UI介面的更新。
進一步簡略de操作:handler.post方法和view.post方法
但是如果你覺得每次都要重寫handlerMessage()比較麻煩,我們完全可以用更加簡略的方法來解決我們的需求,就是用handler中的post方法。代碼如下
new Thread(){ @Override public void run() { //在子線程中進行下載操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } handler.post(new Runnable() { @Override public void run() { text.setText("下載完成"); } });//發送消失到handler,通知主線程下載完成 }}.start();
這樣處理的話我們就可以不用重寫handlerMessage()方法了,適合子線程與主線程進行較為單一的交流。但在這裡我們要強調的一點的是,post裡面的Runnable還是在UI主線程中啟動並執行,而不會另外開啟線程運行,千萬不要在Runnable的run()裡面進行耗時任務,不然到時又ANR了可別找我哦。。
如果你有時候連handler都不想搞,還可以這樣寫代碼滴。
我們只需要把handler換成View組件進行post,更新任務自然會載入到UI主線程中進行處理。
text.post(new Runnable() { @Override public void run() { text.setText("下載完成"); }});//發送消失到handler,通知主線程下載完成
至於Handler機制以及這兩種post的原理,我將會在後面的部落格文章中專題介紹,這裡只提供一個使用方法而已。
終於寫完了睡覺,怎麼沒有掌聲呢??雞蛋、啤酒瓶砸幾個上來~~~~哈哈
enjoy風鈴
出處:http://www.cnblogs.com/net168/
本文著作權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文串連,否則下次不給你轉載了。
Android:非同步處理之Handler+Thread的應用(一)