多執行緒程式設計之同步和非同步
1. 執行緒的同步和非同步
執行緒是程式執行流的最小單元,Android每個執行的應用程式可能包含多個執行緒。
Andorid系統中預設只有一個主執行緒,也叫UI執行緒,因此View繪製只能在這個執行緒內進行,即修改介面的操作只能在主執行緒中執行。
所以如果阻塞了(某些操作使這個執行緒在此處運行了N秒)這個執行緒,這期間View繪製將不能進行,UI就會卡。所以要極力避免在UI執行緒進行耗時操作。
如果主執行緒中做一些耗時操作,阻塞了主執行緒。阻塞時間超過5秒(或6秒),就會報ANR異常(Application Not Response,應用程式無響應)。開發中要極力避免ANR異常。
網路請求是一個典型耗時操作。
對應Android4.0以上版本,Google更加在意UI介面執行的流暢性,強制要求訪問網路的操作不允許在主執行緒中執行,只能在子執行緒中進行,在主執行緒請求網路時,會報如下錯誤:
android.os.NetworkOnMainThreadException

常見的卡頓
解決方式就是把連線網路的操作放在子執行緒中:

網路操作放到子執行緒
看日誌不是很方便,可以將結果顯示在TextView中。

textview

MainActivity
執行結果:
雖然程式不會報錯,但原則上不允許在子執行緒中修改介面的。
由於在onCreate方法中,程式還沒來得及校驗,因此給了我們一個可以在子執行緒修改UI的假象。
把上面的操作放在Button中體會一下:

Button

點選事件
上面的執行就報錯了:
CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views。
如果子執行緒修改了UI,系統會驗證當前執行緒是不是主執行緒,如果不是主執行緒,就會終止執行。
問題:
如果子執行緒不能修改主執行緒的UI,那麼子執行緒需要修改UI時該怎麼辦?
解決方式:
使用Handler實現子執行緒與主執行緒之間的通訊。在子執行緒進行耗時操作,完成後通過Handler將更新UI的操作傳送到主執行緒執行。這就叫 非同步 。
public class MainActivity extends AppCompatActivity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.textview); } //在主執行緒new的Handler,就會在主執行緒進行後續處理 private Handler handler = new Handler(); public void click(View v){ //建立子執行緒並通過start開啟執行緒 new Thread(new Runnable() { @Override public void run() { final String result = NetUtils.get("http://likedev.applinzi.com"); Log.i("MainActivity",result); handler.post(new Runnable() { @Override public void run() { //在UI執行緒更新UI textView.setText(result); } }); } }).start(); } }
Handler還有另外一種寫法,實現效果和上面一樣:
//在主執行緒new的Handler,就會在主執行緒進行後續處理 @SuppressLint("HandlerLeak") private Handler handler = new Handler(){ //方式2 接收資料 @Override public void handleMessage(Message msg) { super.handleMessage(msg); //處理訊息 String result = (String)msg.obj; textView.setText(result); } }; public void click(View v){ //建立子執行緒並通過start開啟執行緒 new Thread(new Runnable() { @Override public void run() { final String result = NetUtils.get("http://likedev.applinzi.com"); //方式2 Message message = new Message(); message.obj = result;//設定資料 //傳送訊息 handler.sendMessage(message); } }).start(); }
Message是執行緒之間傳遞的訊息,它可以在內部傳遞訊息,主要用於不同執行緒之間的交換資料。message.obj可以傳遞一個Object物件,setData()可以設定傳遞的資料,message.what是一個int型別,一般用來標記訊息型別,arg1和arg2兩個int型別引數可以傳遞少量資料。
使用Handler的sendMessage()方法,而發出的訊息經過一系列輾轉處理後,最終會傳遞到Handler的handleMessage()方法中。
在Activity環境中,可以通過Activity中的runOnUiThread方法簡寫上面的程式碼,程式碼顯得更簡潔:
public void click(View v){ //方式3 new Thread(new Runnable() { @Override public void run() { final String result = NetUtils.get("http://likedev.applinzi.com"); runOnUiThread(new Runnable() { @Override public void run() { textView.setText(result); } }); } }).start(); }
下面是runOnUiThread方法的原始碼,這個方法也是通過封裝Handler實現的,原理是一樣的:

runOnUiThread