1. 程式人生 > >Android每天一個知識點+Demo—非同步訊息機制實現

Android每天一個知識點+Demo—非同步訊息機制實現

一 前言

 Handler的由來

(1)首先為什麼需要Handler呢?                 因為UI更新只能在UI執行緒。  (2)那為什麼只能在UI執行緒更新UI呢?        因為Android是單執行緒模型。  (3)那為什麼Android是單執行緒模型?         因為如果任意執行緒都可以更新UI的話,執行緒安全問題處理起來會相當麻煩複雜,所以就規定了Android是單執行緒模型,只允許在UI執行緒更新UI操作。

二 非同步訊息的方式與Demo

1 handler+Message+Looper

        Android在設計的時候,就封裝了一套訊息建立、傳遞、處理機制,如果不遵循這樣的機制就沒有辦法更新UI資訊,就會丟擲異常。 Handler就是是Android提供的用來更新UI的一套機制,也是一套訊息處理機制,我們可以通過它傳送訊息,也可以通過它處理訊息。

(1) 相關介紹

Handler:

  Handler 顧名思義也就是處理者的意思,它主要是用於傳送和處理訊息的。傳送訊息一般是使用 Handler 的 sendMessage()方法,而發出的訊息經過一系列地輾轉處理後,最終會傳遞到 Handler 的 handlerMessage()方法中。

Message:

  Message 是線上程之間傳遞的訊息,它可以在內部攜帶少量的資訊,用於在不同執行緒之間交換資料。通常使用 Message 的 what 欄位攜帶命令,除此之外還可以使用 arg1 和arg2 欄位來攜帶一些整形資料,使用 obj 欄位攜帶一個 Object 物件。

MessageQueue:

  MessageQueue 是訊息佇列的意思,它主要用於存放所有通過 Handler 傳送的訊息。這部分訊息會一直存在於訊息佇列中,等待被處理。每個執行緒中只會有一個 MessageQueue 物件。

Looper:

  Looper 是每個執行緒中的 MessageQueue 的管家,呼叫 Looper 的 loop() 方法後,就會進入到一個無限迴圈當中,然後每當發現 MessageQueue 中存在一條訊息,就會將它取出,並傳遞到 Handler 的 handleMessage() 方法中。每個執行緒中也只會有一個 Looper 物件。

(2) 執行機制流程

        首先需要在主執行緒當中建立一個Handler物件,並重寫handleMessage()方法。

        然後當子執行緒中需要進行UI操作的時,就建立一個Message物件,並通過Handler將這條訊息傳送出去。

       之後這條訊息會被新增到MessageQueue的佇列中等待被處理,而Looper則會一直嘗試從MesssageQueue中取出待處理訊息,最後發回Handler的HandleMessage()方法中。

       由於Handler是在主執行緒中建立的,所以此時handleMessage()方法中的內碼表會在主執行緒中執行,於是我們在這裡就可以進行UI操作了。

(3) Demo

package com.demo.handler; 
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener {
 
	protected static final int UPDATE_TEXT = 1;
	private Button button;
	private TextView textView;
	//建立handler物件,並重寫父類handleMessage方法
	private Handler handler = new Handler(){
		//處理傳遞過來的訊息
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case UPDATE_TEXT:
				//在主執行緒裡更新UI操作
				textView.setText("我要更新了");
				break;
			}
		};
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		button = (Button) findViewById(R.id.btn);
		textView = (TextView) findViewById(R.id.text);
		button.setOnClickListener(this);
	}
 
	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.btn:
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					Message message = new Message();
					message.what = UPDATE_TEXT;
					handler.sendMessage(message);//將message物件傳送出去
				}
			}).start();
			break;
		}
		
	}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="點選修改內容" />
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="我要被改動了" />
</LinearLayout>

(4) 其它

另外除了傳送訊息之外,我們還有以下幾種方法可以在子執行緒中進行UI操作:

              Handler的post(Runnable)方法

              View的post(Runnable)方法

              Activity的runOnUiThread()方法

2 AsyncTask

(1) AsyncTask介紹

       AsyncTask是由執行緒池 + 工作執行緒 + Handler進行構建的一個非同步任務工具類。

AsyncTask本質上是一個封裝了執行緒池、工作執行緒和Handler的非同步框架,主要是執行非同步任務,由於AsyncTask裡面封裝了Handler,所以它可以在主執行緒和子執行緒之間靈活的切換。

     AsyncTask 背後的實現原理也是基於非同步訊息處理機制的,只是 Android 做了很好的封裝而已。我們並不需要去考慮非同步訊息處理機制,也不需要專門使用一個 Handler 來發送和接收訊息,只需要呼叫一下 publishProgress()方法就可以輕鬆地從子執行緒切換到 UI 執行緒了。

(2) AsyncTask的機制原理

1) 執行緒池:

     為了在處理多個任務時,避免建立多個執行緒帶來記憶體開銷,內部維護了一個執行緒池,管理每個執行緒。內部的執行緒池通過ThreadPoolExecutor來動態分配固定大小的核心工作執行緒。這樣所有的非同步任務都會放到執行緒池,並維護每個工作執行緒。

2) 工作執行緒:

        該執行緒用於執行一個耗時的操作,內部通過FutureTask接收一個WorkerRunnable介面,並在介面的call方法中,執行非同步任務,此時呼叫AsyncTask類的doInBackground()方法,做耗時操作。由於FutureTask類實現了FutureRunnable介面,通過這個介面,在它的run方法中,執行任務。

3) Handler訊息機制更新UI:

      當FutureTask工作執行緒任務執行完成以後,把結果交給Handler,Handler通過傳送訊息,在主執行緒中接收訊息,並呼叫AsyncTask類的onPostExecute(Result result)方法,來處理結果。

(3)  AsyncTask 的基本用法

        首先來看一下 ,由於 AsyncTask 是一個抽象類,所以如果我們想使用它,就必須建立一個子類去繼承它。在繼承時我們可以為 AsyncTask 類指定三個泛型引數。

三個引數

       Params:啟動任務執行的輸入引數,比如HTTP請求的URL。        Progress:後臺任務執行的百分比。        Result:後臺任務執行最終返回的結果,比如String,Integer等。

/**
 * AsyncTask泛型引數解釋
 * Void 表示在執行AsyncTask的時候不需要傳入引數給後臺任務
 * Integer 表示使用整形資料來作為進度顯示單位
 * Boolean 表示使用布林型別資料來反饋執行結果
 */
class MyTask extends AsyncTask<Void, Integer, Boolean> {

    @Override
    protected Boolean doInBackground(Void... params) {
        return null;
    }
}

四個方法

        onPreExecute():一般會在 UI Thread 中執行。用於進行一些介面上的初始化操作,比如顯示一個進度條對話方塊等。

       doInBackground(Params…):這個方法中的所有程式碼都會在子執行緒 Worker Thread 中執行,我們應該在這裡去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型引數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以呼叫下面這個 publishProgress(Progress...) 方法來完成。

       onProgressUpdate(Progress…):當在後臺任務中呼叫了publishProgress(Progress…)方法後,這個方法就很快會被呼叫,方法中攜帶的引數就是在後臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用引數中的數值就可以對介面元素進行相應的更新。

       onPostExecute(Result):在 UI Thread 中執行。當後臺任務執行完畢並通過return語句進行返回時,這個方法就很快會被呼叫。返回的資料會作為引數傳遞到此方法中,可以利用返回的資料來進行一些UI操作,比如彈出Toast提醒任務執行的結果,以及關閉掉進度條對話方塊等。

       可以知道,使用 AsyncTask ,則可以在onPreExecute()進行一些介面上的初始化操作,在 doInBackground() 方法中去執行具體的耗時任務,在 onProgressUpdate() 方法中進行 UI 操作,在 onPostExecute()方法中執行一些任務的收尾工作。

       而如果想要啟動這個任務,則只需要  new MyTask.execute();

(4) AsyncTask注意事項

     1)記憶體洩漏(類似handler,只是AsyncTask銷燬方法為cancel())

     2)生命週期(不會隨著Activity的銷燬而銷燬,Activity銷燬前若doInBackground()中耗時未進行完會出現問題)

     3)並行or序列(一般做序列,並行不穩定)

     4)但是AsyncTask並不適合進行特別耗時的後臺任務,對於特別耗時的任務來說,建議使用執行緒池

     5)AsyncTask的例項必須在UI thread中建立,啟動例項的execute方法必須在UI thread中呼叫

     6)一個AsyncTask例項只能被執行一次,多次呼叫時將會出現異常

(5) Demo

public class MyActivity extends Activity
{
    private Button btn;
    private TextView tv;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btn = (Button) findViewById(R.id.start_btn);
        tv = (TextView) findViewById(R.id.content);
        btn.setOnClickListener(new Button.OnClickListener(){
            public void onClick(View v) {
                update();
            }
        });
    }
    private void update(){
        UpdateTextTask updateTextTask = new UpdateTextTask(this);
        updateTextTask.execute();
    }
 
    class UpdateTextTask extends AsyncTask<Void,Integer,Integer>{
        private Context context;
        UpdateTextTask(Context context) {
            this.context = context;
        }
 
        /**
         * 執行在UI執行緒中,在呼叫doInBackground()之前執行
         */
        @Override
        protected void onPreExecute() {
            Toast.makeText(context,"開始執行",Toast.LENGTH_SHORT).show();
        }
        /**
         * 後臺執行的方法,可以執行非UI執行緒,可以執行耗時的方法
         */
        @Override
        protected Integer doInBackground(Void... params) {
            int i=0;
            while(i<10){
                i++;
                publishProgress(i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
            return null;
        }
 
        /**
         * 執行在ui執行緒中,在doInBackground()執行完畢後執行
         */
        @Override
        protected void onPostExecute(Integer integer) {
            Toast.makeText(context,"執行完畢",Toast.LENGTH_SHORT).show();
        }
 
        /**
         * 在publishProgress()被呼叫以後執行,publishProgress()用於更新進度
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            tv.setText(""+values[0]);
        }
    }
}

3 HandlerThread

(1) HandlerThread 介紹

       HandlerThread是Google幫我們封裝好的框架,可以用來執行多個耗時操作,而不需要多次開啟執行緒。其內部實現為:

                           Handle + Thread + Loop

      HandlerThread 繼承於 Thread,所以它本質就是個 Thread。與普通的 Thread 的差別就在於,它有個 Looper 成員變數。這個 Looper 其實就是對訊息佇列以及佇列處理邏輯的封裝。與普通執行緒不同的地方就是內部有loop。因為我們如果想在子執行緒中使用Handle,就必須在子執行緒中建立loop物件。 使用Looper.prepare();來建立當前執行緒的loop,接著使用 Looper.loop();來開啟迴圈,這樣我們才能線上程中建立Handle。而Handle的內部就是這樣實現的。

     HandlerThread集合了Thread和Handler,集成了Looper和MessageQueue。當啟動HandlerThread時,會同時生成Looper和MessageQueue,然後等待訊息處理。它只是幫你呼叫了 Looper.prepare() 方法和 Looper.loop() 方法而已。也就是說如果你一個類繼承了 HandlerThread,你可以像在主執行緒那樣使用 Handler。

(2) HandleThread 使用

1) 建立HandlerThread的例項物件

HandlerThread handlerThread = new HandlerThread("myHandlerThread");

2) 啟動我們建立的HandlerThread執行緒

handlerThread.start();

3) 將我們的handlerThread與Handler繫結在一起,就是將執行緒的looper與Handler繫結在一起,程式碼如下:

mThreadHandler = new Handler(mHandlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        checkForUpdate();
        if(isUpdate){
            mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
        }
    }
};

(3) HandlerThread 使用場景

        HandlerThread 所做的就是在新開的子執行緒中建立了 Looper,那它的使用場景就是 Thread + Looper 使用場景的結合,即:在子執行緒中執行耗時的、可能有多個任務的操作。比如說多個網路請求操作,或者多檔案 I/O 等等。使用 HandlerThread 的典型例子就是 IntentService。 

      當我們需要一個工作執行緒,而不是把它當作一次性消耗品,用過即廢的話,就可以使用它。

(4) HandlerThread 特點

       1)它就是一個幫我們建立 Looper 的執行緒,讓我們可以直接線上程中使用 Handler 來處理非同步任務。HandlerThread 繼承自Thread,內部封裝了Looper。

      2)首先Handler和HandlerThread的主要區別是Handler與Activity在同一個執行緒中,HandlerThread與Activity不在同一個執行緒,而是別外新的執行緒中(Handler中不能做耗時的操作)。       3)通過將HandleThread的Loop物件傳遞給Handle物件,可以在handleMessage()中執行非同步任務。        4)與執行緒池併發不同,HandleThread內部只有一個執行緒,是一個序列的佇列。        5)優點是不會堵塞,減少了對效能的消耗。缺點是不能同時進行多工處理,需等待處理,效率較低。

(5) Demo

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
/**
 * 在主執行緒main中建立handler物件,建立訊息,並用handler向工作執行緒傳送訊息,在工作執行緒中處理訊息;
 * 應用環境:比如下載;
 * @author gaohong
 *
 */
public class HandlerThreadActivity extends Activity {
	HandlerThread thread;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        //建立工作執行緒並啟動(工作執行緒帶訊息佇列)
        thread=new HandlerThread("workThread");
        thread.start();
        Looper looper=thread.getLooper();
        
        //建立Handler物件並與工作執行緒的訊息佇列關聯
        Handler handler=new Handler(looper){
        	@Override
        	public void handleMessage(Message msg) {
        		// TODO Auto-generated method stub
        		System.out.println("在"+Thread.currentThread().getName()+"中處理訊息");
        		System.out.println("msg.obj="+msg.obj);
        	}
        };
        
        //建立Message物件
        Message msg=Message.obtain();
        msg.obj="在"+Thread.currentThread().getName()+"執行緒中傳送訊息";
        handler.sendMessage(msg);
    }
    
   @Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		thread.quit();
	}
}

4 IntentService 

(1) IntentService介紹     

        Service是作為後臺服務執行再程式中的。但是Service他依然是執行在主執行緒中的,所以我們依然不能在Service中進行耗時操作。所以當我們在Service處理時,我們需要在Service中開啟一個子執行緒,並且在子執行緒中執行。當然為了簡化我們的操作,在Android中為我們提供了IntentService來進行這一處理。

     IntentService具有Service一樣的生命週期,也提供了後臺執行緒中的非同步處理機制。IntentService可以看做是Service和HandlerThread的結合,完成任務後自動停止,適合需要在工作執行緒處理ui無關任務的場景。

       IntentServie繼承自Service並處理非同步請求的抽象類,用於執行後臺耗時任務,優先順序比單純的執行緒高很多,適合執行一些高優先順序任務,不容易被系統殺死,內部封裝了HanlerThread和Handler。 封裝的一個work-thread,處理接收到的Intent,它可以處理多個請求,但一次只能處理一個。如果啟動IntentService多次,每一個耗時操作會以工作佇列的方式在inentService的onHandleIntent回撥方法中執行,依次執行,使用序列方式,執行完自動結束。

(2) IntentServie特點      1) IntentService會自動啟動一個WorkThread來處理後臺耗時任務,它可以用於在後臺執行耗時的非同步任務,當任務完成後會自動停止。      2) 後臺耗時任務處理完成以後,會自動關閉Service(這樣只需要啟動IntentService即可,後面就不用關注IntentService的關閉了) 

     3) 擁有較高的優先順序,不易被系統殺死(繼承自Service的緣故),因此比較適合執行一些高優先順序的非同步任務

     4) 內部通過HandlerThread和Handler實現非同步操作建立IntentService時,只需實現onHandleIntent和構造方法,onHandleIntent為非同步方法,可以執行耗時操作。

    5) 當然IntentService也有其限制,多個任務會放到任務佇列,順序執行。所以任務會有先後順序的限制,如果要幾個任務同時併發處理,就不適合使用IntentService了

(3) Demo

public class MyIntentService extends IntentService {
    private static final String TAG = MyIntentService.class.getSimpleName();
    private boolean isRunning;
    private int count;

    public MyIntentService() {
        super(TAG);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String action = intent.getStringExtra("task_action");
        Log.e(TAG, "onHandleIntent: " + action);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (action.equals("task_action111")) {
            Log.e(TAG, "onHandleIntent: " + action);
        }
    }

    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy: ");
        super.onDestroy();
    }
}

三 總結

(1) 它們之間的關係                                                        

                                    

(2) 它們之間的區別

Handler: 它主要有兩個用途,一是為了在將來某個時間點處理一個訊息或者執行一個任務;二是將一個任務放入佇列,以便它可以在另外的執行緒中執行。

AsyncTask: 封裝了執行緒池和Handler,為 UI 執行緒與工作執行緒之間進行快速切換提供一種便捷機制。適用於當下立即需要啟動,但是非同步執行的生命週期短暫的使用場景。這個類的設計目的很明確,就是為了“執行一個較為耗時的非同步任務(最多幾秒鐘),然後更新介面”。

                       Handler + 執行緒池 + 工作執行緒 

HandlerThread: 一個已經擁有了Looper的執行緒類,內部可以直接使用Handler。為某些回撥方法或者等待某些任務的執行設定一個專屬的執行緒,並提供執行緒任務的排程機制。使用場景就是 Thread + Looper 使用場景的結合,即在子執行緒中執行耗時的、可能有多個任務的操作。比如說多個網路請求操作,或者多檔案 I/O 等等。

                       Handler + Thread + Loop

IntentService: 適合於執行由 UI 觸發的後臺 Service 任務,並可以把後臺任務執行的情況通過一定的機制反饋給 UI。提供了後臺執行緒中的非同步處理機制,IntentService可以看做是Service和HandlerThread的結合,完成任務後自動停止,適合需要在工作執行緒處理ui無關任務的場景

                       HandlerThread+ Service