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訊息機制原始碼解讀