1. 程式人生 > >Service解惑&關於IntentService你需要知道的幾個問題

Service解惑&關於IntentService你需要知道的幾個問題

(下面–>問題question簡寫為Q1,answer簡寫為A1,)

Q1.當我們新建一個service代表已經執行在一個新的執行緒裡了嘛?

      A1: NO,NO,NO!  還記得有個小夥伴曾跟我說過:"service其實就是一個子執行緒的封裝~~",
      從而誤導了我許多年^^, 今天我要給自己和大家糾正過來:
      service只不過是依託於UI主執行緒執行的==>一個後臺元件而已,
      你可以把他理解為一個看不見的activity,所以面試回答防止ANR經常會說不要在service做耗時操作!  

這裡附元件超時時間:activity:5S service:20S Broadcast:10S

Q2.別人常說的:“在子執行緒post一下handler就跳到主執行緒了?”,這是真的嘛?

好,於是我傻乎乎的去post一下! 完蛋,報bug嚇我一跳~~別人說的不都是對的嗎?

        (我的錯誤寫法)
         new Thread(() -> { 
          new Handler().post(() ->System.out.println("工作程式碼"))
         }).start();

在handler原始碼的200行報如下異常:
在這裡插入圖片描述
大概就是沒有Loop.prepare的意思.這個稍後分析,我先看下以前正確的程式碼!

    Handler handler=new Handler(){ //在主執行緒中如activity中建立
     @Override
     public void handleMessage(Message msg) {
         super.handleMessage(msg);
     }
 };
    new Thread(new Runnable() {
            @Override
            public void run() {
                handler.sendEmptyMessage(0);
            }
        }).start();

注意:發現這兩者的區別了嘛? 第一個是在子執行緒中建立的handler,(不能正常執行),第二個是在UI主執行緒建立的handler,而UI主執行緒預設就建立了Loop.prepare方法,而我們新建的子執行緒是沒有的,所以這裡就會報錯!

 A2答案:  原來老司機所說的post並不是new Handler().post()這種在activity中常見的寫法啊~~
   而是在主執行緒new handler(),在子執行緒sendMsg一個訊息,
   也可以post一個runnable,這個runnable介面也會被handler轉為msg傳遞,詳見handler原始碼!

Q3: 在同一執行緒中 new handler().post()有什麼好處?我們什麼時候需要在子執行緒手動建立Loop.prepare?

A3.1:當在主執行緒想執行一些耗時操作,但是又不影響後續程式碼的執行時,
我們就可以利用new handler執行耗時操作! 看這個小實驗:

      new Handler().post(() -> System.out.println("1")); //耗時操作1
        System.out.println("2"); //執行操作2
      new Handler().post(() -> System.out.println("3")); //耗時操作3
        System.out.println("4"); //執行操作4
      new Handler().post(() -> System.out.println("5"));
        System.out.println("6");

依次輸出結果: 2–>4–>6–>1–>3–>5 (先執行完所有執行操作(無handler),
再順序執行handler耗時操作,因為handler訊息是先進先出原則!)

~~所以我們可得出結論: 在同一執行緒時,我們想處理一些如載入圖片,繪製百度地圖等必須在UI主執行緒操作卻會阻塞後續執行時,我們用new handler去執行!

A3.2:什麼時候需要建立Loop.prepare呢?這裡其實是第二個問題(Q2)的升級,在第二個
問題可發現,我們是因為誤打誤撞把handler寫在了new Thread()中,所以handler就會在子執行緒回撥,
則當我們需要在子執行緒回撥handler的handleMessage()方法時,我們手動建立Loop.prepare. 

例項:在子執行緒回撥handler並建立loop

class LooperThread extends Thread {
      public Handler mHandler;
      public void run() {
          Looper.prepare();
          mHandler = new Handler() { //這樣就可以在內部回撥handler
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
          Looper.loop();
      }
  }

官方對 Looper介紹: Looper是用於執行一個執行緒中的訊息的類!
執行緒預設沒有Looper,我們呼叫了 Looper.prepare() 方法就為執行緒建立了一個Looper,
然後再用 Looper.loop() 就可以將msg中的訊息迴圈取出來給handler去傳送了!

但是官方是不推薦我們這樣做的,因為直接操作loop有風險! 舉個栗子:

在這裡插入圖片描述

~~這是我們常用的一些寫法: 先通過thread的start呼叫run方法!
~~然後在run中給我們建立了一個looper! 再將looper傳到handler的構造方法中,
~~從而handler的回撥方法就在子執行緒中運行了!
但這裡的風險是: 在run方法執行時,Looper.prepare等方法還沒執行完,
就執行了new Handler(mLooper)時,mLooper還是個空物件,則會丟擲異常!

所以官方給我們封裝了HandlerThread完美的解決了這個問題!~~

Q4:handlerThread是個什麼東西?

先看handlerThread原始碼:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    //也可以指定執行緒的優先順序,注意使用的是 android.os.Process 而不是 java.lang.Thread 的優先順序!
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
// 子類需要重寫的方法,在這裡做一些執行前的初始化工作
protected void onLooperPrepared() {
}

//獲取當前執行緒的 Looper
//如果執行緒不是正常執行的就返回 null
//如果執行緒啟動後,Looper 還沒建立,就 wait() 等待 建立 Looper 後 notify
public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }

    synchronized (this) {
        while (isAlive() && mLooper == null) {    //迴圈等待
            try {
                wait();
            } catch (InterruptedException e) {
        }
    }
    return mLooper;
}

//呼叫 start() 後就會執行的 run()
@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();            //幫我們建立了 Looepr
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();    //Looper 已經建立,喚醒阻塞在獲取 Looper 的執行緒
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();    
    Looper.loop();        //開始迴圈
    mTid = -1;
}

如果瞭解過Android訊息機制原始碼(handler,msg&messageQueue,Loop)的童靴看著應該是so easy , 如不是很瞭解可參考文末的: [Android訊息機制原始碼解讀]
它很好的利用wait和notifyAll機制解決了我們上面所說的問題!

我們看下面的實際運用: 在Google封裝的IntentService就很好的運用了這個!

Q5: IntentService是個什麼東西?

A5:它是繼承自service的抽象類,主要實現是在內部寫了個子執行緒handlerThread,
然後暴露了一個onHandlerIntent()抽象方法供子類重寫,
從而實現將工作程式碼執行在子執行緒中!(因為service預設的start等方法都是執行在UI執行緒的!) 

下面我們來看一下原始碼:

   @Override
    public void onCreate() {
    
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();  //實現一個Q4說的HandlerThread,然後執行start初始化Looper

        mServiceLooper = thread.getLooper(); //將獲取的looper繫結到下面的handler
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;//生成msg,並把intent裝到msg裡
        mServiceHandler.sendMessage(msg);  //傳送到下面的ServiceHandler回撥
    }

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);  //回撥暴露給子類的onHandleIntent方法
            stopSelf(msg.arg1);  //在上面的抽象方法執行完畢後調取此方法就會銷燬服務!
        }
       @WorkerThread  //因為handler的Looper是在工作執行緒建立,所以這也執行在子執行緒
       protected abstract void onHandleIntent(Intent intent);
    }

上面截取了從onCreate到onStart的主要實現,註釋寫的很清楚,只需要注意兩點:
1.繼承IntentService的程式碼通常執行在onHandleIntent這個子執行緒方法裡!
2.當我們的工作程式碼執行完畢後,會自動stop服務,這也是和service的不同!

~~這樣我們就不用每次到在service中新建子執行緒了! 還要注意手動stop了!^^

## Q6: 我們在不同的activity,用不同的context開啟和停止相同的服務,是操作的同一個服務,還是會新建兩個不一樣的服務呢?

A6: 這裡先說結論吧,下面再看實驗(有驚喜喔!) :   
無論你是用相同的還是不同的context去startService,stopService,
他們的執行機制都是一樣的! 即面向的是同一個服務,!
比如你在activity1開啟了一個服務,在activity2關閉,能正常關閉!

(在activity1中) Intent heartIntent= new Intent(Activity1.this, HeartIntentService.class);
startService(heartIntent);
(在activity2中) Intent heartIntent= new Intent(Activity2.this, HeartIntentService.class);
stopService(heartIntent); (成功關閉在1開啟的服務物件)

Q6.1: 那我重複的開啟同一個服務會有什麼後果呢?

A6.1: 這裡說一下本人在intentService中的實測結果! 
疑惑的誕生==>: 
  我看到一篇文章說,如果重複的開啟服務,則會依次將服務排在佇列中,
等待上一個服務執行結束後,就會自動開啟第二次開啟服務!  

My錯誤理解==>:
  那在intentService中,執行完畢會自動關閉服務,那這個自動開啟是不是
又重新開啟了一個新服務(在上一個結束後),然後重新呼叫OnCreate方法?

正確結論!!=>:
  當你多次呼叫同一個已開啟的服務時(無論在哪個context),並不會建立一個新的服務~~ 
那前面的說法(service會等待在佇列中),這就是錯誤的嘛,NO,他說的也有一定道理的,
但我們理解的姿勢一定要對!55^^ 這裡的依次執行,不是服務依次建立~~ 而是
onHandlerIntent這個方法的依次執行!

我下面做了一個小實驗來驗證這個結論(實踐出真知嘛^^):

在IntentService (HeartIntentService.class)中:
 public class HeartIntentService extends IntentService{
  @Override
    protected void onHandleIntent(Intent intent) {
        ii=0;   isRunner=true;//如果不初始化這兩個值,會直接結束服務,
        // 因為即使多次開啟服務,但是同一個例項,所以i的狀態還是為6,isRunner還是保持在false!
        while (isRunner) {
            ii++;
            Log.k("onHandleIntent方法:執行"+ii+"次");
            if (ii > 2) {
                isRunner = false;
            }   
        }
    }
    @Override
    public void onCreate() {
        super.onCreate();
        Log.k("---心跳服務建立");
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.k("---心跳服務銷燬");
}
}
在activity中:重複執行兩次服務: 
     Intent heartIntent= new Intent(Activity1.this,HeartIntentService.class); 
     startService(heartIntent);   //執行兩次!

輸出日誌結果如下~~:
---心跳服務建立
onHandleIntent方法:執行1次
onHandleIntent方法:執行2次
onHandleIntent方法:執行3次
onHandleIntent方法:執行1次
onHandleIntent方法:執行2次
onHandleIntent方法:執行3次
 ---心跳服務銷燬

結論:
~我們重複的開啟IntentService服務,onCreate如果建立過則不會重新建立.
~如果上一個服務的動作沒有執行完,則等待執行完畢後再重複回撥onHandleIntent方法,
~重複開啟服務的例項都是同一個例項!所以導致我們程式碼要將int ii,和boolean isRun重新初始化!
~可理解為重複開啟服務會調取父類Service的onStart方法,則重複回撥onHandleIntent!
~當你使用不同的context去操作的都是同一個服務!

傳送門:Android訊息機制原始碼解讀