View體系之四大元件——Service詳解 在學習Service之前,我們先提出以下幾個問題: 1、什麼是Service?Service的作用是什麼? 2、Service有哪幾種狀态,其生命周期是什麼? 3、Service如何使用?(同一程序/跨程序) 4、Service如何保活(程序保活)? 5、對IntentService的了解 好了,正題開始。 一、什麼是Service?為什麼Android會使用Service? Service顧明思義就是服務,是Andorid提供的實作背景運作的解決方案,主要用于執行背景任務。 比如:通常我們在微信或者qq聊天時,電話進來,然後在打電話的過程中qq或者微信也會一直挂載,這就是Android的背景功能。 服務并不是運作在獨立的程序中,而是會依賴建立服務時的應用程式程序,在這個應用程式程序被殺死後,依賴該程序的服務也會被殺死。 又比如:我們在一個應用中一邊下載下傳視訊,一邊繼續浏覽其他内容。
需要注意的是,服務(除IntentService外)并不會預設開啟線程,服務的執行是在主線程中執行,是以如果在服務中執行耗時任務,也會導緻主線程被阻塞,導緻ANR,是以如果需要執行耗時任務,可以通過使用IntentService或者是開啟線程來執行。
二、Service的狀态和生命周期 1、Service的狀态 根據Service是否與元件進行互動,可分為兩種狀态:啟動狀态和綁定狀态。
- 啟動狀态(不互動)
使用範圍: 通常用于那些不需要互動、無限期背景運作的任務。比如從伺服器下載下傳資料 如何使用:
調用startService()啟動,服務即可在背景無限期的運作,即使啟動Service的元件已經銷毀,也不影響Service的運作。 需要停止Service時,可以通過元件調用stopService()方法或者在Service的子類中調用 stopself()方法即可。
- 綁定狀态
使用範圍: 通常使用與那些需要與背景Service進行互動的情形
如何使用:
在綁定狀态時,元件調用bindService()啟動該Service,同時實作Service與元件的綁定。而綁定的服務中提供了一個IBinder()接口,用于元件和服務進行互動,發送請求,擷取結果。 而當所有的元件都與它解綁後,該Service才會被銷毀。
2、 Service的生命周期分析, 具體如下圖 1:
圖 1 通過圖檔可以發現,啟動狀态和綁定狀态的聲明周期是不同的。 需要注意,在綁定狀态時,同一個元件對該Service多次綁定,隻需要調用一次unbindService()即可解綁。即多次綁定一次解綁 三、Service的具體使用 1、Manifest檔案中聲明 在AndroidManifest.xml中聲明,如下: <service android:enabled=[ "true" | " false "] //是否被系統執行個體化,預設為true android:exported=[ "true" | " false "] //是否可以被隐式調用 android:icon= "drawable resource" android:isolatedProcess=[ "true" | " false "] android:label= "string resource" android:name= "string" //Service類名 android:permission= "string" //權限 android:process= "string" > //是否在單獨的程序中運作 . . . </service>
這裡其他值采用預設,聲明如下: <service android:name=".activity.LocalService"/> 2、繼承Service類。具體如下: package com.sky_wf.de moutils.activity;
import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable;
public class LocalService extends Service { @Nullable @Override public IBinder onBind(Intent intent)//所有繼承Service都必須實作onBind()方法 { return null; }
@Override public void onCreate()//Service建立時調用,隻調用一次 { super.onCreate(); }
@Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); }
@Override public void onDestroy()//Service銷毀時調用 { super.onDestroy(); }
} 四大元件包括Application其實根本上來說,都繼承了Context。是以我們經常在元件中這樣寫: public class LoginActivity extends Activity { private Context context; @override public void onCreate() { context = this; } } 其實在這裡,this代表的就是目前LoginActivty的一個執行個體,而Context則是Activity的父類,context = this。這本身就是一種向上轉型。 這裡也就可以了解,為什麼很多方法或者元件執行個體化時參數需要Context,而我們傳入一個this就可以。又或者我們自己執行個體化一個Context傳入。
Service本身就是一個抽象類,繼承ContextWrapper,而ContextWrapper則是繼承與Context, 在Service這個抽象類中有唯一的一個抽象方法onBind()。
- onBind(Intent intent)
onBind()方法是Service中唯一的一個抽象方法,是以繼承Service類時必須實作這個類。通常用于進行通信,尤其是後面我們會講到的跨程序通信。
- onCreate()
在首次建立Service時調用,當重複執行startService()或者bindService()時,onCreate()不再執行。
- onStartCommand(Intent intent,int flags,int startId)
當元件調用startService()啟動服務時,則系統會調用此方法,如果是第一次調用startService()則會先調用onCreate()再調用onStartCommand(),重複啟動服務,隻會調用 onStartCommand()。 通常我們在onStartCommand中執行一些背景處理。 這裡我們需要特别注意的是其傳回的int值,目前有三種START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT。 START_STICKY 當Service因記憶體不足而被系統kill掉後,一段時間後記憶體再次空閑時,系統将會嘗試重新建立此Service,一旦建立成功後将回調onStartCommand方法,但其中的Intent将是null,除非有挂起的 Intent,如pendingintent。 适用于不執行指令、但無限期運作并等待作業的媒體播放器或類似服務。 START_NOT_STICKY 當Service因記憶體不足而被系統kill後,即使系統記憶體再次空閑時,系統也不會嘗試重新建立此Service。除非程式中再次調用startService啟動此Service,這是最安全的選項,可以避免在不必要時 以及應用能夠輕松重新開機所有未完成的作業時運作服務。 START_REDELIVER_INTENT 當Service因記憶體不足而被系統kill後,則會重建服務,之後調用onStartCommand()。與START_STICKY不同的是,其中的傳遞的Intent将是非空,是最後一次調用startService中的intent。 适用于主動執行應該立即恢複的作業(例如下載下傳檔案)的服務。
- onDestory()當服務停止且要被銷毀時,則會調用此方法。
3、布局檔案和Activity
(1)布局檔案 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn_start" android:layout_width="100dp" android:layout_height="100dp" android:text="啟動服務" /> <Button android:id="@+id/btn_stop" android:layout_width="100dp" android:layout_height="100dp" android:text="停止服務" /> </LinearLayout> (2)Activity package com.sky_wf.demoutils.activity;
import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button;
import com.sky_wf.demoutils.R;
public class TestActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_startService, btn_stopService;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_test); btn_startService = (Button) findViewById(R.id.btn_start); btn_stopService = (Button) findViewById(R.id.btn_stop); btn_startService.setOnClickListener(this); btn_stopService.setOnClickListener(this); }
@Override public void onClick(View v) { Intent intent = new Intent(this, LocalService.class); switch (v.getId()) { case R.id.btn_start: startService(intent); break; case R.id.btn_stop: stopService(intent); break; } } }
(3)在Manifest中聲明對應Activity和Service <activity android:name=".activity.TestActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
<service android:name=".activity.LocalService"/>
(4)運作檢視log
分析log:
- 點選綁定服務Button,首次執行startService()對調用Service的onCreate()和onStartCommand()方法
- 之後多次點選Button,此時隻執行onStartCommand()方法
- 在點選stopService()方法後,則執行Service的onDestory()方法,該Service停止并銷毀。
四、綁定服務與元件通信的問題 前面的執行個體介紹了啟動Service的簡單執行個體,這我們就要詳細将一下Service的綁定狀态通信問題。 相比啟動Service,綁定 Service通常會涉及到Service與元件的一個互動問題,也就是說,元件傳遞資料到Service,然後Service也可能會回應資料到元件。話是這麼說,但是并不是這麼簡單。 比如說,這裡需要考慮,service與元件是在同一程序中還是跨程序,如果是同一程序中,我們可以通過Intent直接顯式啟動,同時可以擷取到Service的執行個體對象,然後通路對應的public方法就好。但是如果是跨程序的,那麼我們無法通過回調Service的執行個體來調用public方法。此時就需要我們使用IPC通信。
1、擴充Binder類 對于服務和用戶端都是在同一程序下運作的情形(思考為什麼?之後會回答),通常會擴充Binder類。 使用步驟: (1)首先繼承Ibinder接口,定義一個方法getService()傳回Service的對象 (2)在Serivice中的onBind()方法中傳回擴充的Ibinder類的對象 (3)用戶端拿到這個binder,然後調用getService獲得Service對象,并通路serviec對象的方法
這裡需要明白Binder和IBinder的關系,如下: public interface IBinder { int FIRST_CALL_TRANSACTION = 0x00000001; int LAST_CALL_TRANSACTION = 0x00ffffff; ..... }
public class Binder implements IBinder { ....... } 也就是說IBinder是一個接口,而Binder則是實作了IBinder接口的實作類,而public IBinder onBinde()方法傳回的的是IBinder接口,是以這裡實際上是對擴充的Binder類進行了一次向上轉型。
(1)定義Service類 package com.sky_wf.demoutils.activity;
import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.support.annotation.Nullable; import android.util.Log;
public class LocalBinderService extends Service { private int count = 0; private LocalBinder binder = new LocalBinder(); private boolean stop = false; private Thread mthread;
public class LocalBinder extends Binder { LocalBinderService getService() { return LocalBinderService.this;//擷取目前LocalBinderService類的對象 } }
@Nullable @Override public IBinder onBind(Intent intent) { Log.d("wf", "LocalService onBind"); return binder; }
@Override public void onCreate() { super.onCreate(); Log.d("wf", "LocalService onCreate"); mthread = new Thread(new Runnable() { @Override public void run() { while(!stop) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count++; } } }); mthread.start(); }
@Override public void onDestroy() { super.onDestroy(); stop = true; Log.d("wf", "LocalService onDestroy"); }
@Override public boolean onUnbind(Intent intent) { Log.d("wf", "LocalService onUnbind"); return super.onUnbind(intent); }
public int getCount() { return count; } }
(2)主Activity和布局檔案(布局檔案隻有三個Button這裡省略) package com.sky_wf.demoutils.activity;
import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button;
import com.sky_wf.demoutils.R;
public class TestBinderActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_startService, btn_stopService, btn_getServiceDatas; private LocalBinderService mservice; private ServiceConnection conn;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_test); btn_startService = (Button) findViewById(R.id.btn_start); btn_stopService = (Button) findViewById(R.id.btn_stop); btn_getServiceDatas = (Button)findViewById(R.id.btn_getServiceData); btn_startService.setOnClickListener(this); btn_stopService.setOnClickListener(this); btn_getServiceDatas.setOnClickListener(this); conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d("wf", "TestBinderActivity onServiceConnected"); LocalBinderService.LocalBinder binder = (LocalBinderService.LocalBinder)service; mservice = binder.getService(); }
@Override public void onServiceDisconnected(ComponentName name) { Log.d("wf", "TestBinderActivity onServiceDisconnected"); mservice = null; } }; }
@Override public void onClick(View v) { Intent intent = new Intent(this, LocalBinderService.class); switch (v.getId()) { case R.id.btn_start: bindService(intent,conn, Service.BIND_AUTO_CREATE); break; case R.id.btn_stop: if(mservice!=null) { mservice = null; unbindService(conn); } break; case R.id.btn_getServiceData: if(mservice!=null) { Log.d("wf", "TestBinderActivity getData="+mservice.getCount()); }else { Log.d("wf", "請綁定伺服器以獲得資料"); } break; } } }
從上圖分析: 1>-調用順序 點選綁定後,調用bindService(),則開始調用onCreate()-->onBind()-->ServiceConn中的 onServiceConnected(),同一元件重複點選綁定,則onCreate()和onBind()不再調用, 點選解除綁定,調用onUnbind() onDestory()(如果還有其他元件綁定,則不會調用Service的onDestory())
2>- bindService(Intent intent,ServerConnction conn,int flags) flags 代表元件與Service綁定時是否自動建立Service執行個體,0,表示不自動建立,BIND_AUTO_CREATE則代表自動建立。 之前提過一個疑問,為什麼隻能在一個程序中? 因為擴充Binder類,是通過Binder對象去擷取Service執行個體,進而去擷取Service的公共方法,而公共方法的使用也需要在統一程序中,而沒有辦法在不同程序中通過擷取執行個體的方法來使用它的公共方法。 這也就了解了為什麼需要使用跨程序通信。
2、使用Messenger 之前介紹的擴充Binder類的方法隻是針對同一程序中的用戶端和服務端。但是對于跨程序之間的通信,則可以采用 Messenger 。 為什麼 Messenger 可以實作跨程序的通信呢?這個目前我還不是很清楚,之後再深入研究一下。這裡先講怎麼用。 Messenger是一種輕量級IPC方案,底層根本上是AIDL,實際上就是對AIDL進行了一個封裝,而Messenger核心是利用Message來進行資料的傳輸,而Message可以攜帶的參數有what,arg1,arg2,obj,replyTo等 。具體流程如下: 分析流程圖: 用戶端和服務端進行通信的前提是建立連接配接,也就是綁定Service。
- 用戶端向服務端發送消息
對于服務端,需要接收消息,而這裡的消息是Message,那麼我們就需要一個Handler來處理Message。而這裡傳遞Message需要利用Messenger。是以在服務端利用Handler對象來建立服務端的一個Messenger。然後通過onBind()接口傳回這個Messenger的IBinder對象。 接下來分析用戶端,用戶端需要發送消息,是以需要組裝對應Message資訊,并通過Messenger來發送,而Messenger則是通過服務端傳回的Binder來建立。 這樣就簡單的建立一個從用戶端到伺服器的簡單連接配接。這裡需要明白,用戶端有一個Messenger對象,而伺服器也有一個Messenger對象,這兩者之間又有什麼關系,為什麼通過Messenger可以建立跨程序的連接配接。
- 服務端收到用戶端消息并回複。
這裡首先分析用戶端,用戶端接收消息,同樣需要建立一個Handler,并根據這個Handler建立一個用于跨程序通信的Messenger對象。同時,修改上面用戶端發送消息的代碼,把該Messenger對象作為Message的replyTo參數,傳遞給服務端。 服務端接收到用戶端建立的Messenger對象後,利用該對象傳遞Message給用戶端。
總結來說,就是服務端根據自己的Handler建立Messenger,并傳回Messenger底層Binder給用戶端,而用戶端拿到Binder後,再對應生成Messenger對象,發送消息。而在用戶端發送消息時,會将自己建立的另一個Messenger作為replyTo參數,然後發送到服務端,這樣兩者之間就可以互相通信了。
以上就是利用Messenger的大緻流程,接下來上代碼: 1>服務端代碼 package com.sky_wf.demoutils.activity;
import android.app.Service; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.annotation.Nullable; import android.util.Log;
public class LocalMessengerService extends Service {
public final static int MSG_FROM_CLIENT = 1; private final Messenger messenger = new Messenger(new MyHandler());
class MyHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_FROM_CLIENT: Log.d("wf", "Service receiver Message from Client"); Messenger messenger = msg.replyTo; Message message = Message.obtain(null,TestMessgenerActivity.MSG_FROM_SERVER); Bundle bundle = new Bundle(); bundle.putString("wf","Service send msg to Client"); message.setData(bundle); try { messenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } break; } } }
@Nullable @Override public IBinder onBind(Intent intent) { return messenger.getBinder();// 注意這裡擷取的Binder和Activity中ServicerConnection中的是同一個對象, 是跨程序通信的關鍵 }
@Override public void onCreate() { super.onCreate(); Log.d("wf", "LocalService onCreate"); }
@Override public boolean onUnbind(Intent intent) { Log.d("wf", "LocalService onUnbind"); return super.onUnbind(intent); }
@Override public void onDestroy() { Log.d("wf", "LocalService onDestroy"); super.onDestroy(); } } 2>用戶端代碼 package com.sky_wf.demoutils.activity;
import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button;
import com.sky_wf.demoutils.R;
public class TestMessgenerActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_startService, btn_stopService, btn_getServiceDatas; private Messenger messenger; private ServiceConnection conn; private Messenger serverMessenger = new Messenger(new MyClientHandler()); public final static int MSG_FROM_SERVER = 2;
class MyClientHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_FROM_SERVER: Log.d("wf", "the client receiver the reply from server"); break; default: super.handleMessage(msg);
} } };
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_test); btn_startService = (Button) findViewById(R.id.btn_start); btn_stopService = (Button) findViewById(R.id.btn_stop); btn_getServiceDatas = (Button) findViewById(R.id.btn_getServiceData); btn_startService.setOnClickListener(this); btn_stopService.setOnClickListener(this); btn_getServiceDatas.setOnClickListener(this); conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d("wf", "onServiceConnected"); messenger = new Messenger(service); }
@Override public void onServiceDisconnected(ComponentName name) { Log.d("wf", "onServiceDisconnected"); messenger = null; } };
}
public void sayHello() { Message msg = Message.obtain(null, LocalMessengerService.MSG_FROM_CLIENT, 0, 0); msg.replyTo = serverMessenger; try { messenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } }
@Override public void onClick(View v) { Intent intent = new Intent(this, LocalMessengerService.class); switch (v.getId()) { case R.id.btn_start: bindService(intent, conn, BIND_AUTO_CREATE); break; case R.id.btn_stop: unbindService(conn); break; case R.id.btn_getServiceData: sayHello(); break; } } }
3>清單檔案 <activity android:name=".activity.TestMessgenerActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".activity.LocalMessengerService" android:process=":remote"/>//用于開啟一個新的程序
注意: (1)通常情況下我們會将service與用戶端的生命周期相結合,這裡以Activity的生命周期為例:
- 在Activity可見時執行Service,不可見時解綁,則需要在onStart()執行綁定操作,而在onStop()時執行解綁操作。
- 在Activity處于背景不可見情況下,仍然可以執行Service,則應在onCreate()情況下執行綁定,而在onDestory()情況下執行解綁。需要注意,這種情形下,Service在另一個程序中獨立運作,是以需要提高程序權重,避免被系統殺死。
- 但是請勿在onResume()和onPause()中執行綁定和解綁,這樣會因生命周期的轉變而頻繁的發生綁定和解綁,是不合理的。
(2)既然是用戶端和服務端的連接配接,那麼不可避免的就會有連接配接異常問題,是以我們應該對連接配接異常執行捕獲,這裡的連接配接異常是 DeadObjectException,通常表示調用的對象已經被銷毀,也就是Service對象被銷毀。
3、Service的啟動和綁定轉換 前面已經介紹Service有啟動Service和綁定Service兩種,但是同一個Service對象也可以有兩種狀态,也就是說,這個Service可以在啟動和綁定狀态中切換。
- 先綁定後啟動
一個Service對象,首先是綁定狀态,然後以啟動狀态去運作,則該Service會由綁定狀态轉變為啟動狀态,并且即使綁定的宿主銷毀,該Service依舊正常運作,除非調用stopService()或者本身調用stopSelf()或者記憶體不足時才會停止。注意,在停止前需要調用解綁。
- 先啟動後綁定
一個Service對象,先是啟動狀态,然後以綁定狀态去執行,則仍然是啟動狀态。但是同樣,需要調用解綁後才能利用stopService()停止服務。 從上面分析可以知道,啟動服務的優先級要明顯高于綁定服務。此外,如果服務沒有在單獨的程序中運作,那麼它就是在托管程序的主線程中運作,也就是常說的UI線程,是以執行耗時任務的話也需要開啟獨立的線程。
4、Service在不同場景下的生命周期分析 (1)startService()-->按傳回退出目前Activity (2)bindService()-->按傳回退出目前Activity (3)startService()-->bindService-->stopService (4)、 startService()-->bindService-->unbindService (5)、startService()-->bindService-->unbindService-->stopService (6)、bindService-->startService-->unbindService (7)、bindService-->startService-->unbindService-->stopService (8)bindService-->startService-->stopService
(9)、startService()-->bindService-->stopService (10)bingService()-->startService-->按傳回退出目前Activity
通過上面的例子分析: (1)無論是先啟動後綁定還是先綁定後啟動,最後停止或者銷毀Service,都是調用stopService()方法,但是都有前提,就是先調用unBindService()做解綁操作,否則直接調用stopService并不能停止Service (2)對于綁定服務,如果手動解綁,則執行onUnbind-->onDestory 如果不手動解綁,直接退出目前Activity,則執行onUnbind-->onDestory 如果是先啟動後綁定或者先綁定後啟動,且不執行手動解綁,則隻執行onUnbind (3)對于啟動服務,如果不手動執行stopService或者stopSelf,則按傳回退出目前Activity,則Service仍在,不銷毀 如果先啟動後綁定或者先綁定後啟動,則隻有執行stopService才會執行銷毀,前提是已解綁
5、Service與Thread的關系 Thread是程式執行的最小機關,也是配置設定cpu的最小機關,通常用于執行一些耗時任務 Service則是android的一種元件,一般運作在主線程下,是以無法執行耗時任務。 通常是Service結合Thread+notification來使用。
6、針對Android5.0以上隐式啟動Service報錯 IllegaArgumentException 在Android5.0以上,針對隐式啟動Service做了修改,如下: 會判斷Component和package是否為空,如果為空,且是在API版本大于5.0,則會報異常。 而啟動Service又分為顯示啟動和隐式啟動,在統一應用中可以顯示啟動,但是在不同的應用中則隻能隐式啟動。 具體解決辦法: (1)添加包名 由于隐式啟動會檢查包名和Component以及API,是以可以在隐式啟動時添加包名。 final Intent serviceIntent=new Intent(); serviceIntent.setAction("com.android.ForegroundService"); serviceIntent.setPackage(getPackageName());//設定應用的包名 startService(serviceIntent); (2)将隐式啟動轉換為顯示啟動
Intent mIntent=new Intent();//輔助 Intent mIntent.setAction("com.android.ForegroundService"); final Intent serviceIntent=new Intent(getExplicitIntent(this,mIntent)); startService(serviceIntent);
四、IntentService IntentService是繼承自Service用于處理異步請求的一個類,在IntentService中你會有一個工作線程來處理耗時操作,并且在任務完成後自動停止。而當多次啟動IntentService時,這些耗時任務會以工作隊列的形式依次在onHandleIntent中執行。 示例如下: public class MyIntentService extends IntentService { private boolean isRunning = true; private int count = 0; private LocalBroadcastManager mLocalBroadcastManager; public MyIntentService() { super("MyIntentService"); }
@Override public void onCreate() { super.onCreate(); mLocalBroadcastManager = LocalBroadcastManager.getInstance(this); sendServiceStatus("服務啟動"); } @Override protected void onHandleIntent(Intent intent) { try { sendThreadStatus("線程啟動", count); Thread.sleep(1_000); sendServiceStatus("服務運作中..."); isRunning = true; count = 0; while (isRunning) { count++; if (count >= 100) { isRunning = false; } Thread.sleep(50); sendThreadStatus("線程運作中...", count); } sendThreadStatus("線程結束", count); } catch (InterruptedException e) { e.printStackTrace(); } }
@Override public void onDestroy() { super.onDestroy(); sendServiceStatus("服務結束"); } // 發送服務狀态資訊
private void sendServiceStatus(String status) { Intent intent = new Intent(IntentServiceFragment.ACTION_TYPE_SERVICE); intent.putExtra("status", status); mLocalBroadcastManager.sendBroadcast(intent); } // 發送線程狀态資訊 private void sendThreadStatus(String status, int progress) { Intent intent = new Intent(IntentServiceFragment.ACTION_TYPE_THREAD); intent.putExtra("status", status); intent.putExtra("progress", progress); mLocalBroadcastManager.sendBroadcast(intent); } }
1、為什麼可以在onHandleIntent()方法中執行耗時任務 在IntentSerivice中的有一個繼承自Thread的HandlerThread,而這個方法中的任務執行則是在該線程中執行的。 @Override public void onCreate() { super.onCreate(); // HandlerThread 繼承自 Thread,内部封裝了 Looper,在這裡建立線程并啟動,是以啟動 IntentService 不需要建立線程。 HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); // 獲得工作線程的 Looper,并維護自己的工作隊列。 mServiceLooper = thread.getLooper(); // mServiceHandler 是屬于工作線程的。 mServiceHandler = new ServiceHandler(mServiceLooper); }
2、IntentService啟動是通過startService()還是bindService() 通過startService()啟動,原因是在源碼中的onBind()方法預設傳回null 3、為什麼多次啟動IntentService會順序執行,而停止服務後,後續的任務不再執行? 實際上,IntentService是通過Handler,Looper,MessageQueue把消息發送到線程中去執行的,而多次啟動,隻是把消息加入消息隊列去執行。而服務停止後,會清空消息隊列的資訊,後續事件得不到執行。
五、應用程序保活問題 1、什麼是應用程序保活 程序保活其實就是保證應用處于背景并且不能永遠被殺死或者被殺死後又重新開機。 通常被殺死的情形分為兩類: 第一類,是系統資源緊張或者由于系統自身的運作規則(比如鎖屏清理)來殺死應用獲得更多資源; 第二類,使用者手動調用安全軟體清理應用(比如360,華為自帶的手機管家,騰訊管家)。 在Android5.0以前,我們基于這兩種情形來做保活,而在Android5.0以後,由于系統的程序管理更為嚴格,殺程序也更加暴力徹底,是以隻能基于第一種情況考慮保活。 2、程序的類型和優先級 目前Android中的程序分為前台程序,可見程序,服務程序,背景程序,空程序,重要性或者優先級從上到下依次遞減
- 前台程序 Foreground process
該狀态下的程序正在與使用者進行互動,是以必須存在。(除非系統錯誤或者使用者手動殺掉,否則前台程序一定不會被殺死) 那些情況屬于前台程序: 目前應用持有一個與使用者互動的Activity,并且處于Resume狀态 目前應用持有一個Service,并且處于以下情形:該Service與使用者正在互動的Activity綁定(如果持有Service的程序和該Activity的程序是同一程序,那自然是前台程序,如果是其他程序的Service, 那麼也是持有Service的程序也是前台程序 ) 該Service調用startForeground()進而使程序位于前台 該Service正在執行它的某個聲明周期的回調,比如onCreate,onDestory 當該應用有一個BroadCastReceiver并且該廣播正在執行onReceiver()方法
- 可見程序 Visible process
可見程序情形中不會出前台程序中的任何情形,常見如下: 某程序持有一個Activity但是目前并非位于前台但仍然可見,也就是處于onPause還沒到onStop狀态。(比如由一個應用跳轉到另一個應用時,此時則前一個應用會由前台程序轉化為可見程序再到 背景程序) 某程序擁有一個Service,該Service與一個前台Activity綁定(需要差別前台程序中一個程序擁有Service,而Service與使用者正在互動的Activity綁定,兩者的差別在于,一個是與使用者互動的Activity,一個是與前台應用的Activity綁定 )
- 服務程序
服務程序一般是不屬于上面兩種,并且使用startService啟動了該程序中的一個Service,那就是一個Service
- 背景程序
這裡指的是一個程序持有一個不可見的Activity,Activity處于onStop但是未處于onDestory
- 空程序
不包含任何活躍的應用元件,就是一個空程序。
如果系統需要殺死程序,一般先空程序,然後背景程序,然後服務程序,然後可見程序,然後前台程序。 Android中根據系統資源殺死程序的規則成為Lower Memory Killer。
3、背景程序常駐政策與選擇 前面我們已經提到過,對于使用者的手動殺死程序我們沒有很好的辦法,但是對于系統這種殺死程序的行為,我們可以嘗試去解決。而通常是因為我們程序的優先級太低同時系統可用資源太少然後被殺死。而我們解決這種問題的,通常會有兩方面:
- 提升程序優先級,降低被殺死的機率
- 在程序被殺死後,進行拉活
前面我們已經說明了五種程序,其中各程序的的重要程度如下:
可以看到,前台程序為0,可見程序為1,服務程序為5,而OOM_ADJ,是LowMemoryKiller回收記憶體時對程序判斷的依據,值越大越容易被回收。 而Android程序被殺死的情形如下: 5、 提升優先級——利用Activity提升權限 設計思想: 監控手機鎖屏時間,在鎖屏後啟動一個像素的Activity,在使用者解鎖後銷毀該Activity,使用者無法感覺該Activity 通過這種方式,可以将應用優先級從4提升到1 使用常場景: 可以解決第三方應用或者系統管理工具檢測到鎖屏事件後一段時間殺死背景程序,進而達到省電目的。 實作方式: 寫一個寬高為1像素的Activity,并設定為透明,然後注冊一個接收系統發送 鎖屏和解鎖的廣播,進而在鎖屏是啟動Activity,解鎖時銷毀Activity 使用版本:所有的Android版本 6、提升優先級——利用Notification權限 設計思想: Android中Service的優先級為4,通過setForeground可以将背景的Service設定為前台Service,程序的優先級從4提升為2。 實作方式: 調用setForeground将背景Service設定為前台Service,但是需要在系統通知欄上發一條通知,此時可以再開一個Service,也發送一個相同ID的通知,然後取消掉,進而關閉通知。 使用版本:目前已知所有版本
7、程序拉活——利用系統廣播 設計思想:在系統發生特定廣播後,程序響應廣播,拉活。 問題:隻有在發生系統時間并發送廣播時才可以拉活,并且如果廣播無法正常接收的話也無法辦到 8、程序拉活——利用第三方廣播拉活 設計思想:通過接收第三方應用的廣播來拉活 問題:首先需要知道第三方應用廣播,其次不穩定,如果第三方應用廣播在後續版本中修改了。 9、程序拉活——利用系統Service機制拉活 設計思想:将Service設定為START_STICKY,在系統機制讓Service挂掉後自動拉活 适用範圍: Service在第一次異常殺死後5s複活,第二次10s,第三次20s。5次之後就不會重新開機 如果程序被root管理工具或者系統工具停止後,無法重新開機 10、利用該Native程序拉活 設計思想:利用linux的fork機制建立native程序,在native程序中監控主程序的存活,在主程序挂掉後,則在native程序中立即對主程序拉活 11、利用JobScheduler機制拉活 設計思想:在Android5.0之後對native程序加強了管理,native拉活的方式失效,但是Android5.0以上版本提供了JobScheduler接口。 使用範圍:主要使用于Android5.0以上的手機,并且不收force-stop影響 ,被強制停止的應用也可以拉活。但是小米手機仍然存在無法拉活的問題 12、利用賬号同步機制拉活 設計思想: Android系統的賬号同步機制會定期同步賬号,可以利用賬号同步機制進行拉活。 使用範圍:使用與所有的Android版本,但是最新的AndroidN無法拉活 13、 其他 利用應用内的push通道,比如國外的GCM,國内的小米推送,華為推送 或者利用輔助功能,将應用加入廠商或者管理軟體的白名單