Android多線程編程


一 線程的基本用法

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個泛型參數。

  1. Params 執行AysncTask時需要傳入的參數。可同於在后台任務中使用。
  2. Progress 后台執行任務時,如果需要在界面上顯示當前的進度,則使用這里指定的泛型作為進度單位。
  3. 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();

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM