一 線程的基本用法
1.創建線程
方法一:實現Runnable接口的方法定義一個線程。
class MyThread implements Runnable{ @Override public void run() { //處理的具體的邏輯 } }
然后Thread的構造函數接收一個Runnable參數,並調用start方法,run()方法中的代碼就會在子線程中運行了。
/*啟動線程*/ MyThread myThread = new MyThread(); new Thread(myThread).start();
方法二:使用匿名類的方式。
new Thread(new Runnable(){ @Override public void run(){ //處理具體的邏輯 } }).strat();
2.在子線程中更新UI
在線程中更新UI是不安全的,所以要更新UI,必須要到主線程中更新UI。
實例如下:要實現在線程中點擊按鈕后更改Textview中文字。
布局
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical"> 5 6 <Button 7 android:id="@+id/change_text" 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:text="change text" /> 11 12 <TextView 13 android:id="@+id/text" 14 android:layout_width="match_parent" 15 android:layout_height="wrap_content" 16 android:layout_gravity="center_horizontal" 17 android:gravity="center_horizontal" 18 android:layout_centerInParent="true" 19 android:text="Hello world" 20 android:textSize="20sp" /> 21 22 </LinearLayout>
MainActivity如下:
1 public class MainActivity extends AppCompatActivity implements View.OnClickListener { 2 3 private TextView text; 4 private Button changeText; 5 6 public static final int UPDATE_TEXT = 1; 7 8 private Handler handler = new Handler(){ 9 10 public void handleMessage(Message msg){ 11 switch (msg.what){ 12 case UPDATE_TEXT: 13 //在這里可以進行ui操作 14 text.setText("change ui"); 15 break; 16 default: 17 break; 18 } 19 } 20 }; 21 22 @Override 23 protected void onCreate(Bundle savedInstanceState) { 24 super.onCreate(savedInstanceState); 25 setContentView(R.layout.activity_main); 26 text = (TextView)findViewById(R.id.text); 27 changeText = (Button) findViewById(R.id.change_text); 28 changeText.setOnClickListener(this); 29 } 30 31 @Override 32 public void onClick(View view) { 33 switch (view.getId()){ 34 case R.id.change_text: 35 new Thread(new Runnable() { 36 @Override 37 public void run() { 38 Message message = new Message(); 39 message.what = UPDATE_TEXT; 40 handler.sendMessage(message); //將Message對象發送出去 41 } 42 }).start(); 43 break; 44 default: 45 break; 46 } 47 } 48 }
可以看到我們在這里先定義了一個整型常量UPDATE_TEXT,用於表示更新的這個動作。
1.新增一個Hander對象(處理器),重寫父類的handleMessage()方法,在這里對Message進行處理。
2.在點擊事件中,創建一個子線程,但在子線程中是新建了一個消息對象Message。將他的攜帶的消息what字段設置為UPDATE_TEXT。
3.調用Handler中的sendMessage()方法將這條Message發送。
4.這是Handler會收到這個消息,並在handlerMessage中對消息進行處理,因為這個是主線程中,所以可以進行相關的ui更改操作。
在線程中更改UI使用的是異步消息處理機制。
這種處理機制由四個部分組成。Message,Handler,MessageQueue和Looper。
(1)Message:
Message是在線程之間傳遞的消息,它可以在內部攜帶少量的信息,用於在不同線程之間交換數據。Message的what、arg1和arg2字段可以攜帶一些整型數據,使用obj字段可以攜帶一個Object對象
(2)Handler:
Handler顧名思義也就是處理者的意思,它主要是用於發送和處理消息的。發送消息一般是使用Handler的sendMessage()方法,而發出的消息經過一系列地輾轉處理后,最終會傳遞到Handler的handleMessage()方法
(3)MessageQueue:
MessageQueue是消息隊列的意思,它主要用於存放所有通過Handler發送的消息。這部分消息會一直存在於消息隊列中,等待被處理。每個線程中只會有一個MessageQueue對象
(4)Looper:
Looper是每個線程中的MessageQueue的管家,調用Looper的loop()方法后,就會進入到一個無限循環當中,然后每當發現MessageQueue()中存在一條消息,就會將它去除,並傳遞到Handler的handleMessage()方法中。每個線程也只會有一個Looper對象。
整體流程
首先需要在主線程當中創建一個Handler對象,並重寫handleMessage()方法。然后當子線程中需要進行UI操作時,就創建一個Message對象,並通過Handler將這條消息發送出去。之后這條消息會被添加到MessageQueue的隊列中等待被處理,而Looper則會一直嘗試從MessageQueue中取出待處理消息,最后分發回Handler的handleMessage方法中。由於Handler是在主線程中創建的,所以此時handleMessage()方法中的代碼也會在主線程中運行,所以可以安心的進行UI操作。
3.使用AsyncTask
由於AsyncTask是一個抽象類,需要有一個子類去繼承他。繼承時可以為AsyncTask類指定3個泛型參數。
- Params 執行AysncTask時需要傳入的參數。可同於在后台任務中使用。
- Progress 后台執行任務時,如果需要在界面上顯示當前的進度,則使用這里指定的泛型作為進度單位。
- Result 任務執行完畢后,如果需要對結果進行返回則使用這里指定的泛型作為返回值類型。
class DownloadTask extends AsyncTask<Void,Void,Void>{ ··· }
需要重寫幾個方法才能完成定制。經常需要重寫的四個方法:
- onPreExecute() 在后台任務開始執行之前調用,用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。
- doInBackground(Params) 該方法中所有代碼在子線程中運行,應在這里處理所有耗時任務。任務一旦完成可以通過return語句來將任務的執行結果返回。若AsyncTask第三個泛型參數指定的是Void,則不返回任務執行結果。注意,該方法中不能進行UI操作,若要更新UI操作,比如反饋當前任務的執行進度,可調用publicProgress(Progress…)方法來完成。
- onProgressUpdate(Progress) 當后台任務中調用了publishProgress(Progress)方法后,onProgressUpdate(Progress)方法就會很快被調用。該方法中攜帶的參數就是在后台任務重傳遞進來的。在這個方法中可以對UI進行操作,利用參數中的數值可以對界面元素進行相應的更新。
- onPostExecute(Result) 當后台任務執行完畢並通過return語句進行返回時,該方法很快被調用,返回的數據會作為參數傳遞到此方法中,可利用返回的數據來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。
示例下載代碼:
1 public class DownloadTask extends AsyncTask<Void, Integer, Boolean> { 2 3 /*在后台任務執行開始前調用,用於界面的初始化*/ 4 @Override 5 protected void onPreExecute() { 6 progressDialog.show(); //顯示進度對話框 7 } 8 9 /*這個方法中的代碼都在子線程中運行,處理耗時任務,結果通過return語句返回。 10 * 這個方法中不可以進行ui操作,要調用publishProgress()方法來ui操作*/ 11 @Override 12 protected Boolean doInBackground(Void... params) { 13 try { 14 while (true) { 15 int downloadPercent = doDownload(); //虛構的方法 16 publishProgress(downloadPercent); 17 if (downloadPercent >= 100) { 18 break; 19 } 20 } 21 } catch (Exception e) { 22 return false; 23 } 24 return true; 25 } 26 27 /*在調用publishProgress()方法后,該方法很快被調用,用來更新ui*/ 28 @Override 29 protected void onProgressUpdate(Integer... values) { 30 //在這里更新下載進度 31 progressDialog.setMessage("Downloaded" + values[0] + "%"); 32 } 33 34 @Override 35 protected void onPostExecute(Boolean result){ 36 progressDialog.dismiss();//關閉進度對話框 37 //在這里提示下載結果 38 if(result){ 39 Toast.makeText(context,"Download succeeded",Toast.LENGTH_LONG).show(); 40 }else { 41 Toast.makeText(context,"Download failed",Toast.LENGTH_LONG).show(); 42 } 43 } 44 }
在doInBackground()方法里進行下載任務,虛構的doDownload()方法計算當下下載的進度並返回。然后通過publishProgress()方法將下載進度傳過來,在onProgressUpdate()方法中執行ui更新操作。
下載完后,DoInBackground()會返回一個布爾型變量,onPostExecute()方法被調用,顯示下載結果。
如果想啟動這個任務,只需執行如下代碼:
new DownloadTask().execute();