HandlerThread和IntentService原始碼解析

-l囧l-發表於2019-03-01

簡介

首先我們先來了解HandlerThread和IntentService是什麼,以及為什麼要將這兩者放在一起分析。
HandlerThread:

HandlerThread 其實是Handler + Thread + Looper的組合,它本質上是一個Thread,因為它繼承了Thread。相比普通的Thread,它不會堵塞,因為他內部通過Looper實現了訊息迴圈機制,保證了多個任務的序列執行。缺點是效率比較低,因為,序列執行比起並行執行,效率肯定會比較較低。

IntentService:

IntentService是繼承並處理非同步請求的一個類,其本質上是一個Service,因為他繼承了Service,所以開啟IntentService和普通的Service一致。但是他和普通的IntentService不同之處在於,他可以處理非同步任務,在任務處理完成之後會自動結束Service。另外我們可以啟動IntentService多次,而每一個耗時任務會已工作佇列的方式在IntentService的onHandleIntent回撥方法中執行,並且是序列執行的。

好了在瞭解HandlerThread和IntentService分別是什麼之後,我們來解決第二個問題,那就是為什麼我要將2者放在一起分析?其實IntentService的內部是通過HandlerThread和Handler來實現非同步操作的,當我們瞭解了HandlerThread的使用和原理之後,再去理解IntentService就會容易的多。好的,下面讓我們開始HandlerThread的原始碼之旅。

#HandlerThread的使用和原理
###HandlerThread的使用
這裡我們要實現一個每隔5s更新TextView中的值的一個demo,原始碼如下:

public class MainActivity extends AppCompatActivity {
  private static final String TAG = "MainActivity";
  private TextView mTv;
  private Button mBtn;
  HandlerThread mHandlerThread;
  Handler mThreadHandler;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
    mBtn.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        initThread();
      }
    });
  }

  private void initView() {
    mTv = (TextView) findViewById(R.id.tv);
    mBtn = (Button) findViewById(R.id.btn);
  }

  private void initThread() {
    //建立一個HandlerThread並開啟執行緒
    mHandlerThread = new HandlerThread("update-msg");
    mHandlerThread.start();

    //從mHandlerThread中得到Looper並建立Handler
    mThreadHandler = new Handler(mHandlerThread.getLooper()) {
      @Override public void handleMessage(Message msg) {
        Log.v(TAG, "currentThread===>" + Thread.currentThread() + "   what====>" + msg.what);
        try {
          update();
        } catch (Exception e) {
          e.printStackTrace();
        }
        mThreadHandler.sendEmptyMessage(200);
      }
    };
    mThreadHandler.sendEmptyMessage(200);
  }

  private void update() throws Exception {
    Thread.sleep(3000);
    runOnUiThread(new Runnable() {
      @Override public void run() {
        String result = "每隔3s更新一次:";
        result += Math.random();
        mTv.setText(result);
      }
    });
  }
}

複製程式碼

輸出的日誌如下:

currentThread===>Thread[update-msg,5,main] what====>200

從日誌我們可以看出handleMessage執行在我們建立的HandlerThread(“update-msg”)之下。我們有理由懷疑這跟我們傳入的mHandlerThread.getLooper()有關。我們的mThreadHandler 是在UI執行緒中建立的,按理來說handleMessage應該執行在UI執行緒中才對。瞭解Handler原理的都知道,handleMessage方法是在Handler的dispatchMessage方法中被呼叫的且dispatchMessage方法沒有進行執行緒切換。所以執行緒切換應該發生在dispatchMessage被呼叫的地方,那dispatchMessage是在哪被呼叫的呢?我們發現Loop的loop()方法中呼叫了dispatchMessage()方法。(這裡我就不貼Handler和Loop相關的程式碼,感興趣的可以參考我以前的文章:Handler的原理)而且我們還發現了loop()方法的註釋如下:

image.png

意思是loop()方法執行在Loop被繫結的執行緒中。

那Loop又是在什麼時候被繫結的呢?

image.png

就是這2個方法對Loop進行了繫結。那這個sThreadLocal又是什麼鬼?它到底有什麼用?別急,我們去看下它建立的地方:

image.png

它其實就是一個ThreadLocal,關於ThreadLocal的原理,大家可以參考:ThreadLocal原始碼深入分析
在這裡我簡單的說下,其實ThreadLocal的作用,就是通過Thread中的threadLocals:ThreadLocalMap變數將我們通過ThreadLocal#set方法傳進來的資料跟Thread進行繫結,從而保證了訪問到的變數屬於當前執行緒,每個執行緒都儲存有一個變數副本,每個執行緒的變數都不同,而同一個執行緒在任何時候訪問這個本地變數的結果都是一致的。當此執行緒結束生命週期時,所有的執行緒本地例項都會被GC。ThreadLocal相當於提供了一種執行緒隔離,將變數與執行緒相繫結。ThreadLocal通常定義為private static型別

通過上面的分析,我們已經知道了Handler#handleMessage方法會執行在Loop說繫結的執行緒上,驗證了我們一開始的猜想。這裡Loop是從我們建立的HandlerThread中得到的,而HandlerThread其實就是一個執行緒,所以Loop繫結在了新建立的HandlerThread上。但是我們並不滿足於此,我們得進一步看看HandlerThread和普通的Thread到底有什麼不一樣。

HandlerThread的原始碼解析

HandlerThread的程式碼量其實並不多,它繼承於Thread,主要的方法其實就是run方法

@Override
    public void run() {
        mTid = Process.myTid();
        //建立Loop並繫結當前執行緒
        Looper.prepare();
        //關鍵程式碼
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
      //設定執行緒優先順序
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
複製程式碼

短短的幾行程式碼確幾乎實現了我們想要的所有功能,我們來看關鍵程式碼run方法中的synchronized 程式碼塊其實對應於getLooper方法中的synchronized程式碼塊,這樣做的目的是為了確保,我們通過getLoop()方法得到的Loop物件一定是被初始化後的Loop。當Loop被初始化以後會呼叫抽象方法onLooperPrepared(),他一般被用於在開啟佇列迴圈之前做一些初始化的操作,然後執行任務佇列。

總結

HandlerThread的原理已經分析完了,我們來總結一下它的特點:

1.HandlerThread它就是一個執行緒,和開啟普通的執行緒得到操作一致

2.HandlerThread需要搭配Handler使用,單獨使用的意義不大

3.HandlerThread會將通過handleMessage傳遞進來的任務進行序列執行,這是由messageQueue的特性決定的,從而也說明了HandlerThread效率相比並行操作會比較低

IntentService的使用和原理

分析完HandlerThread之後我們來分析一下IntentService的使用和原理,老規矩我們先看怎麼使用。

IntentService的使用

public class MyIntentService extends IntentService {
  private static final String TAG = "MyIntentService";

  public MyIntentService() {
    super("MyIntentService");
    Log.v(TAG,
        "MyIntentService===>MyIntentService()" + "  currentThread==>" + Thread.currentThread());
  }

  /**
   * Creates an IntentService.  Invoked by your subclass`s constructor.
   *
   * @param name Used to name the worker thread, important only for debugging.
   */
  public MyIntentService(String name) {
    super(name);
    Log.v(TAG,
        "MyIntentService===>MyIntentService(name)" + "  currentThread==>" + Thread.currentThread());
  }

  @Override public void onCreate() {
    super.onCreate();
    Log.v(TAG, "MyIntentService===>onCreate" + "  currentThread==>" + Thread.currentThread());
  }

  @Override protected void onHandleIntent(@Nullable Intent intent) {
    Log.v(TAG, "MyIntentService===>onHandleIntent" + "  currentThread==>" + Thread.currentThread());
    try {
      Thread.sleep(10000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  @Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    Log.v(TAG, "MyIntentService===>onStartCommand" + "  currentThread==>" + Thread.currentThread());
    return super.onStartCommand(intent, flags, startId);
  }
}
複製程式碼

呼叫服務和我們普通的Service一致
輸出的日誌如下

MyIntentService===>MyIntentService() currentThread==>Thread[main,5,main]
MyIntentService===>onCreate currentThread==>Thread[main,5,main]
MyIntentService===>onStartCommand currentThread==>Thread[main,5,main]
MyIntentService===>onHandleIntent currentThread==>Thread[IntentService[MyIntentService],5,main]

從中我們可以看出onHandleIntent方法是執行在子執行緒中的,更有意思的是,當我們在onHandleIntent 方法中執行延遲操作時,列印的日誌如下描述:
1、當服務沒執行完時又點選了開啟服務的操作,此時,onStartCommand方法會立即執行,而onHandleIntent方法會在上一個任務執行完以後再去執行onHandleIntent方法。
2、當服務已經執行完被自動結束以後,再去呼叫service,輸出的日誌和第一次輸出的日誌一致。

可能我說的比較抽象,大家自取去操作一遍就會發現我所說的有意思的地方。從上面的日誌輸出,我們可以得出以下結論:
1、IntentService在任務執行完以後會自動結束
2、IntentService接收的任務是序列執行的,並且互不干擾
3、IntentService的生命週期和普通的Service一致,只不過多了一個onHandleIntent回撥方法,並且它是序列回撥的,等待上一個任務執行完以後才會再次被呼叫

但是為什麼會這樣呢?大家有沒有想過。當然,所有的答案都隱藏在原始碼裡,讓我們一起去揭開他神祕的面紗吧。

IntentService原始碼解析

首先我們先來看下IntentService的幾個成員變數,如下圖所示:

image.png

關於Loop和Handler我們都很熟悉了,前者是遍歷訊息佇列的訊息泵後者則是處理Handler傳送過來的訊息的。下面我來看下他們初始化得到地方。
Loop初始化

image.png

原來他們都是在Service的onCreate回撥方法中被初始化的。
通過上文HandlerThread的分析,我們知道ServiceHandler的handleMessage方法會執行在mServiceLooper繫結的指定執行緒上。這裡這也就驗證了我們上文日誌的輸出。

下面我們來解決另外一個問題,也就是IntentService的生命週期函式的執行情況。
請看下面的程式碼:

image.png

我們都知道當服務被啟動以後,再次呼叫服務的時候都會回撥onStartCommand方法,onStartCommand又呼叫了onStart方法,而onStart方法中只是通過Handler傳送一個非同步訊息,然後ServiceHandler的handleMessage收到訊息以後呼叫了onHandleIntent,這也就驗證了上文的日誌輸出。

下面我們來重點分析一下Service的stopSelf()方法,他有兩個過載方法,一個有參,一個無參,那他們之間有什麼不同呢?
我們還是通過原始碼來看一下吧。

image.png

可以看到無參方法只是簡單的呼叫了有參方法,並傳入了一個-1的引數。所以我們只有直接分析有參的方法就可以。
由於Android sdk並沒有開放ActivityManageProxy(我們知道ActivityManage在客戶端得到代理是ActivityManageProxy)的程式碼,所以我們只能通過查詢相關資料來解決我們的疑惑。
最終我在官網上得到的答案如下:

image.png

簡單來說就是stopSelf中的startId對應於onStartCommand中的startId,當stopSelf(startId)中的startId等於onStartCommand中的最後一個進來的startId的時候,就代表訊息佇列中沒有更多的訊息需要處理了,所以執行完當前的訊息以後,會去執行Service的stop操作

總結

關於IntentService的分析到這就告一段落了,其實IntentService就是基於HandlerThread機制來實現的,它允許我們在onHandleIntent回撥方法中執行非同步操作。同時要注意他的生命週期回撥函式的差異。下面貼上官網上關於IntentService類的介紹,幫助大家理解。

image.png

相關文章