轉載請註明出處(萬分感謝!css
):
http://blog.csdn.net/javazejian/article/details/52709857
出自【zejian的博客】 html
Service所有內容基本會在本篇涉及到。咱們將環繞下面主要知識點進行分析:java
Service(服務)是一個一種可以在後臺執行長時間執行操做而沒有用戶界面的應用組件。服務可由其它應用組件啓動(如Activity),服務一旦被啓動將在後臺一直執行,即便啓動服務的組件(Activity)已銷燬也不受影響。 此外,組件可以綁定到服務。以與之進行交互,甚至是執行進程間通訊 (IPC)。linux
好比,服務可以處理網絡事務、播放音樂。執行文件 I/O 或與內容提供程序交互,而所有這一切都可在後臺進行,Service基本上分爲兩種形式:android
當應用組件(如 Activity)經過調用 startService() 啓動服務時,服務即處於「啓動」狀態。一旦啓動,服務就能夠在後臺無限期執行,即便啓動服務的組件已被銷燬也不受影響,除非手動調用才幹中止服務。 已啓動的服務通常是執行單一操做,而且不會將結果返回給調用方。數據庫
當應用組件經過調用 bindService() 綁定到服務時,服務即處於「綁定」狀態。編程
綁定服務提供了一個client-server接口,贊成組件與服務進行交互、發送請求、獲取結果,甚至是利用進程間通訊 (IPC) 跨進程執行這些操做。 僅當與還有一個應用組件綁定時,綁定服務纔會執行。 多個組件可以同一時候綁定到該服務,但所有取消綁定後,該服務即會被銷燬。安全
前面說過Service分爲啓動狀態和綁定狀態兩種,但無論哪一種具體的Service啓動類型,都是經過繼承Service基類本身定義而來,也都需要在AndroidManifest.xml中聲明。那麼在分析這兩種狀態以前,咱們先來了解一下Service在AndroidManifest.xml中的聲明語法,其格式例如如下:markdown
<service android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</service>
android:exported:表明可否被其它應用隱式調用。其默認值是由service中有無intent-filter決定的。假設有intent-filter,默認值爲true。不然爲false。網絡
爲false的狀況下,即便有intent-filter匹配。也沒法打開。即沒法被其它應用隱式調用。
android:name:對應Service類名
android:permission:是權限聲明
android:process:是否需要在單獨的進程中執行,當設置爲android:process=」:remote」時,表明Service在單獨的進程中執行。注意「:」很是重要,它的意思是指要在當前進程名稱前面附加上當前的包名。因此「remote」和」:remote」不是同一個意思。前者的進程名稱爲:remote,然後者的進程名稱爲:App-packageName:remote。
android:isolatedProcess :設置 true 意味着。服務會在一個特殊的進程下執行,這個進程與系統其它進程分開且沒有本身的權限。與其通訊的惟一途徑是經過服務的API(bind and start)。
android:enabled:可否夠被系統實例化。默以爲 true因爲父標籤 也有 enable 屬性,因此必須兩個都爲默認值 true 的狀況下服務纔會被激活。不然不會激活。
ok~,關於Service在清單文件的聲明咱們先了解這些就能夠。接下來分別針對Service啓動服務和綁定服務進行具體分析
首先要建立服務,必須建立 Service 的子類(或使用它的一個現有子類如IntentService)。在實現中,咱們需要重寫一些回調方法。以處理服務生命週期的某些關鍵過程,下面咱們經過簡單案例來分析需要重寫的回調方法有哪些?
package com.zejian.ipctest.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
/** * Created by zejian * Time 2016/9/29. * Description:service simple demo */
public class SimpleService extends Service {
/** * 綁定服務時纔會調用 * 必需要實現的方法 * @param intent * @return */
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
/** * 首次建立服務時。系統將調用此方法來執行一次性設置程序(在調用 onStartCommand() 或 onBind() 以前)。 * 假設服務已在執行。則不會調用此方法。該方法僅僅被調用一次 */ @Override public void onCreate() { System.out.println("onCreate invoke"); super.onCreate(); } /** * 每次經過startService()方法啓動Service時都會被回調。
* @param intent * @param flags * @param startId * @return */ @Override public int onStartCommand(Intent intent, int flags, int startId) { System.out.println("onStartCommand invoke"); return super.onStartCommand(intent, flags, startId); } /** * 服務銷燬時的回調 */ @Override public void onDestroy() { System.out.println("onDestroy invoke"); super.onDestroy(); } }
從上面的代碼咱們可以看出SimpleService繼承了Service類,並重寫了onBind方法,該方法是必須重寫的。但是因爲此時是啓動狀態的服務。則該方法無須實現,返回null就能夠。僅僅有在綁定狀態的狀況下才需要實現該方法並返回一個IBinder的實現類(這個後面會具體說),接着重寫了onCreate、onStartCommand、onDestroy三個基本的生命週期方法。關於這幾個方法說明例如如下:
當還有一個組件想經過調用 bindService() 與服務綁定(好比執行 RPC)時,系統將調用此方法。在此方法的實現中,必須返回 一個IBinder 接口的實現類。供client用來與服務進行通訊。
無論是啓動狀態仍是綁定狀態。此方法必須重寫,但在啓動狀態的狀況下直接返回 null。
首次建立服務時,系統將調用此方法來執行一次性設置程序(在調用 onStartCommand() 或onBind() 以前)。
假設服務已在執行。則不會調用此方法。該方法僅僅調用一次
當還有一個組件(如 Activity)經過調用 startService() 請求啓動服務時,系統將調用此方法。
一旦執行此方法,服務即會啓動並可在後臺無限期執行。 假設本身實現此方法,則需要在服務工做完畢後,經過調用 stopSelf() 或 stopService() 來中止服務。
(在綁定狀態下,無需實現此方法。)
當服務再也不使用且將被銷燬時。系統將調用此方法。
服務應該實現此方法來清理所有資源,如線程、註冊的偵聽器、接收器等,這是服務接收的最後一個調用。
咱們經過Demo測試一下Service啓動狀態方法的調用順序,MainActivity代碼例如如下:
package com.zejian.ipctest;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.zejian.ipctest.service.SimpleService;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button startBtn;
private Button stopBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startBtn= (Button) findViewById(R.id.startService);
stopBtn= (Button) findViewById(R.id.stopService);
startBtn.setOnClickListener(this);
assert stopBtn != null;
stopBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent it=new Intent(this, SimpleService.class);
switch (v.getId()){
case R.id.startService:
startService(it);
break;
case R.id.stopService:
stopService(it);
break;
}
}
}
記得在清單配置文件裏聲明Service(聲明方式跟Activity類似):
<manifest ... >
...
<application ... >
<service android:name=".service.SimpleService" />
...
</application>
</manifest>
從代碼看出,啓動服務使用startService(Intent intent)方法,僅需要傳遞一個Intent對象就能夠。在Intent對象中指定需要啓動的服務。
而使用startService()方法啓動的服務,在服務的外部,必須使用stopService()方法中止,在服務的內部可以調用stopSelf()方法中止當前服務。
假設使用startService()或者stopSelf()方法請求中止服務,系統會就會盡快銷燬這個服務。
值得注意的是對於啓動服務,一旦啓動將與訪問它的組件無不論什麼關聯,即便訪問它的組件被銷燬了。這個服務也一直執行下去,直到手動調用中止服務才被銷燬,至於onBind方法。僅僅有在綁定服務時纔會起做用,在啓動狀態下,無需關注此方法,ok~,咱們執行程序並屢次調用startService方法。最後調用stopService方法。
Log截圖例如如下:
從Log可以看出。第一次調用startService方法時,onCreate方法、onStartCommand方法將依次被調用。而屢次調用startService時,僅僅有onStartCommand方法被調用,最後咱們調用stopService方法中止服務時onDestory方法被回調,這就是啓動狀態下Service的執行週期。接着咱們又一次回過頭來進一步分析onStartCommand(Intent intent, int flags, int startId),這種方法有3個傳入參數,它們的含義例如如下:
onStartCommand(Intent intent, int flags, int startId)
intent :啓動時。啓動組件傳遞過來的Intent,如Activity可利用Intent封裝所需要的參數並傳遞給Service
flags:表示啓動請求時是否有額外數據。可選值有 0,START_FLAG_REDELIVERY。START_FLAG_RETRY,0表明沒有。它們具體含義例如如下:
START_FLAG_REDELIVERY
這個值表明了onStartCommand方法的返回值爲
START_REDELIVER_INTENT,而且在上一次服務被殺死前會去調用stopSelf方法中止服務。
當中START_REDELIVER_INTENT意味着當Service因內存不足而被系統kill後。則會重建服務,並經過傳遞給服務的最後一個 Intent 調用 onStartCommand()。此時Intent時有值的。
START_FLAG_RETRY
該flag表明當onStartCommand調用後一直沒有返回值時,會嘗試又一次去調用onStartCommand()。
startId : 指明當前服務的惟一ID,與stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地依據ID中止服務。
實際上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後,則會重建服務。並經過傳遞給服務的最後一個 Intent 調用 onStartCommand()。不論什麼掛起 Intent均依次傳遞。與START_STICKY不一樣的是,當中的傳遞的Intent將是非空,是最後一次調用startService中的intent。這個值適用於主動執行應該立刻恢復的做業(比例如如下載文件)的服務。
因爲每次啓動服務(調用startService)時,onStartCommand方法都會被調用。所以咱們可以經過該方法使用Intent給Service傳遞所需要的參數,而後在onStartCommand方法中處理的事件,最後依據需求選擇不一樣的Flag返回值。以達到對程序更友好的控制。好~,以上即是Service在啓動狀態下的分析。接着咱們在來看看綁定狀態的Service又是怎樣處理的?
綁定服務是Service的還有一種變形,當Service處於綁定狀態時。其表明着client-server接口中的server。當其它組件(如 Activity)綁定到服務時(有時咱們可能需要從Activity組建中去調用Service中的方法,此時Activity以綁定的方式掛靠到Service後。咱們就可以輕鬆地方法到Service中的指定方法),組件(如Activity)可以向Service(也就是服務端)發送請求,或者調用Service(服務端)的方法。此時被綁定的Service(服務端)會接收信息並響應,甚至可以經過綁定服務進行執行進程間通訊 (即IPC。這個後面再單獨分析)。與啓動服務不一樣的是綁定服務的生命週期一般僅僅在爲其它應用組件(如Activity)服務時處於活動狀態,不會無限期在後臺執行。也就是說宿主(如Activity)解除綁定後。綁定服務就會被銷燬。
那麼在提供綁定的服務時,該怎樣實現呢?實際上咱們必須提供一個 IBinder接口的實現類,該類用以提供client用來與服務進行交互的編程接口,該接口可以經過三種方法定義接口:
擴展 Binder 類
假設服務是提供給自有應用專用的,而且Service(服務端)與client一樣的進程中執行(常見狀況)。則應經過擴展 Binder 類並從 onBind() 返回它的一個實例來建立接口。client收到 Binder 後。可利用它直接訪問 Binder 實現中以及Service 中可用的公共方法。假設咱們的服務僅僅是自有應用的後臺工做線程,則優先採用這樣的方法。 不採用該方式建立接口的惟一緣由是,服務被其它應用或不一樣的進程調用。
使用 Messenger
Messenger可以翻譯爲信使,經過它可以在不一樣的進程中共傳遞Message對象(Handler中的Messager,所以 Handler 是 Messenger 的基礎),在Message中可以存放咱們需要傳遞的數據。而後在進程間傳遞。
假設需要讓接口跨不一樣的進程工做,則可以使用 Messenger 爲服務建立接口,client就可利用 Message 對象向服務發送命令。
同一時候client也可定義自有 Messenger。以便服務回傳消息。這是執行進程間通訊 (IPC) 的最簡單方法。因爲 Messenger 會在單一線程中建立包含所有請求的隊列,也就是說Messenger是以串行的方式處理client發來的消息。這樣咱們就沒必要對服務進行線程安全設計了。
在此狀況下,服務必須具有多線程處理能力,並採用線程安全式設計。使用AIDL必須建立一個定義編程接口的 .aidl 文件。
Android SDK 工具利用該文件生成一個實現接口並處理 IPC 的抽象類,隨後可在服務內對其進行擴展。
以上3種實現方式,咱們可以依據需求自由的選擇。但需要注意的是大多數應用「都不會」使用 AIDL 來建立綁定服務。因爲它可能要求具有多線程處理能力,並可能致使實現的複雜性添加。
所以,AIDL 並不適合大多數應用。本篇中也不打算闡述怎樣使用AIDL(後面會另開一篇分析AIDL),接下來咱們分別針對擴展 Binder 類和Messenger的使用進行分析。
前面描寫敘述過,假設咱們的服務僅供本地應用使用,不需要跨進程工做。則可以實現自有 Binder 類,讓client經過該類直接訪問服務中的公共方法。其使用開發過程例如如下
注意:此方式僅僅有在client和服務位於同一應用和進程內纔有效。如對於需要將 Activity 綁定到在後臺播放音樂的自有服務的音樂應用。此方式很是有效。還有一點之因此要求服務和client必須在同一應用內,是爲了便於client轉換返回的對象和正確調用其 API。服務和client還必須在同一進程內。因爲此方式不執行不論什麼跨進程編組。
下面是一個擴展 Binder 類的實例,先看看Service端的實現BindService.java
package com.zejian.ipctest.service;
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;
/** * Created by zejian * Time 2016/10/2. * Description:綁定服務簡單實例--服務端 */
public class LocalService extends Service{
private final static String TAG = "wzj";
private int count;
private boolean quit;
private Thread thread;
private LocalBinder binder = new LocalBinder();
/** * 建立Binder對象。返回給client即Activity使用,提供數據交換的接口 */
public class LocalBinder extends Binder {
// 聲明一個方法,getService。(提供給client調用)
LocalService getService() {
// 返回當前對象LocalService,這樣咱們就可在client端調用Service的公共方法了
return LocalService.this;
}
}
/** * 把Binder類返回給client */
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service is invoke Created");
thread = new Thread(new Runnable() {
@Override
public void run() {
// 每間隔一秒count加1 ,直到quit爲true。
while (!quit) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
});
thread.start();
}
/** * 公共方法 * @return */
public int getCount(){
return count;
}
/** * 解除綁定時調用 * @return */
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "Service is invoke onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.i(TAG, "Service is invoke Destroyed");
this.quit = true;
super.onDestroy();
}
}
BindService類繼承自Service。在該類中建立了一個LocalBinder繼承自Binder類,LocalBinder中聲明瞭一個getService方法,client可訪問該方法獲取LocalService對象的實例。僅僅要client獲取到LocalService對象的實例就可調用LocalService服務端的公共方法。如getCount方法,值得注意的是,咱們在onBind方法中返回了binder對象,該對象即是LocalBinder的具體實例。而binder對象終於會返回給client。client經過返回的binder對象便可以與服務端實現交互。接着看看clientBindActivity的實現:
package com.zejian.ipctest.service;
import android.app.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.util.Log;
import android.view.View;
import android.widget.Button;
import com.zejian.ipctest.R;
/** * Created by zejian * Time 2016/10/2. * Description:綁定服務實例--client */
public class BindActivity extends Activity {
protected static final String TAG = "wzj";
Button btnBind;
Button btnUnBind;
Button btnGetDatas;
/** * ServiceConnection表明與服務的鏈接,它僅僅有兩個方法, * onServiceConnected和onServiceDisconnected。 * 前者是在操做者在鏈接一個服務成功時被調用,然後者是在服務崩潰或被殺死致使的鏈接中斷時被調用 */
private ServiceConnection conn;
private LocalService mService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind);
btnBind = (Button) findViewById(R.id.BindService);
btnUnBind = (Button) findViewById(R.id.unBindService);
btnGetDatas = (Button) findViewById(R.id.getServiceDatas);
//建立綁定對象
final Intent intent = new Intent(this, LocalService.class);
// 開啓綁定
btnBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "綁定調用:bindService");
//調用綁定方法
bindService(intent, conn, Service.BIND_AUTO_CREATE);
}
});
// 解除綁定
btnUnBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "解除綁定調用:unbindService");
// 解除綁定
if(mService!=null) {
mService = null;
unbindService(conn);
}
}
});
// 獲取數據
btnGetDatas.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mService != null) {
// 經過綁定服務傳遞的Binder對象。獲取Service暴露出來的數據
Log.d(TAG, "從服務端獲取數據:" + mService.getCount());
} else {
Log.d(TAG, "還沒綁定呢,先綁定,沒法從服務端獲取數據");
}
}
});
conn = new ServiceConnection() {
/** * 與server端交互的接口方法 綁定服務的時候被回調,在這種方法獲取綁定Service傳遞過來的IBinder對象, * 經過這個IBinder對象。實現宿主和Service的交互。 */
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "綁定成功調用:onServiceConnected");
// 獲取Binder
LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
mService = binder.getService();
}
/** * 當取消綁定的時候被回調。但正常狀況下是不被調用的,它的調用時機是當Service服務被意外銷燬時, * 好比內存的資源不足時這種方法才被本身主動調用。 */
@Override
public void onServiceDisconnected(ComponentName name) {
mService=null;
}
};
}
}
在client中咱們建立了一個ServiceConnection對象,該表明與服務的鏈接。它僅僅有兩個方法。 onServiceConnected和onServiceDisconnected,其含義例如如下:
onServiceConnected(ComponentName name, IBinder service)
系統會調用該方法以傳遞服務的 onBind() 方法返回的 IBinder。
當中service即是服務端返回的IBinder實現類對象,經過該對象咱們便可以調用獲取LocalService實例對象,進而調用服務端的公共方法。而ComponentName是一個封裝了組件(Activity, Service, BroadcastReceiver, or ContentProvider)信息的類,如包名,組件描寫敘述等信息,較少使用該參數。
onServiceDisconnected(ComponentName name)
Android 系統會在與服務的鏈接意外中斷時(好比當服務崩潰或被終止時)調用該方法。
注意:當client取消綁定時,系統「絕對不會」調用該方法。
conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "綁定成功調用:onServiceConnected");
// 獲取Binder
LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
mService = binder.getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService=null;
}
};
在onServiceConnected()被回調前。咱們還需先把當前Activity綁定到服務LocalService上。綁定服務是經過經過bindService()方法,解綁服務則使用unbindService()方法,這兩個方法解析例如如下:
bindService(Intent service, ServiceConnection conn, int flags)
該方法執行綁定服務操做,當中Intent是咱們要綁定的服務(也就是LocalService)的意圖。而ServiceConnection表明與服務的鏈接。它僅僅有兩個方法。前面已分析過。flags則是指定綁定時是否本身主動建立Service。0表明不本身主動建立、BIND_AUTO_CREATE則表明本身主動建立。
unbindService(ServiceConnection conn)
該方法執行解除綁定的操做,當中ServiceConnection表明與服務的鏈接,它僅僅有兩個方法,前面已分析過。
Activity經過bindService()綁定到LocalService後。ServiceConnection#onServiceConnected()便會被回調並可以獲取到LocalService實例對象mService。以後咱們就可以調用LocalService服務端的公共方法了。最後還需要在清單文件裏聲明該Service。而client佈局文件實現例如如下:
<?xml version="1.0" encoding="utf-8"?
> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/BindService" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="綁定server" /> <Button android:id="@+id/unBindService" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="解除綁定" /> <Button android:id="@+id/getServiceDatas" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="獲取服務方數據" /> </LinearLayout>
咱們執行程序,點擊綁定服務並屢次點擊綁定服務接着屢次調用LocalService中的getCount()獲取數據,最後調用解除綁定的方法移除服務,其結果例如如下:
經過Log可知,當咱們第一次點擊綁定服務時。LocalService服務端的onCreate()、onBind方法會依次被調用。此時client的ServiceConnection#onServiceConnected()被調用並返回LocalBinder對象,接着調用LocalBinder#getService方法返回LocalService實例對象,此時client便持有了LocalService的實例對象,也就可以隨意調用LocalService類中的聲明公共方法了。更值得注意的是,咱們屢次調用bindService方法綁定LocalService服務端,而LocalService得onBind方法僅僅調用了一次。那就是在第一次調用bindService時纔會回調onBind方法。接着咱們點擊獲取服務端的數據,從Log中看出咱們點擊了3次經過getCount()獲取了服務端的3個不一樣數據,最後點擊解除綁定,此時LocalService的onUnBind、onDestroy方法依次被回調。而且屢次綁定僅僅需一次解綁就能夠。此情景也就說明了綁定狀態下的Service生命週期方法的調用依次爲onCreate()、onBind、onUnBind、onDestroy。ok~,以上即是同一應用同一進程中client與服務端的綁定回調方式。
前面瞭解了怎樣使用IBinder應用內同一進程的通訊後,咱們接着來了解服務與遠程進程(即不一樣進程間)通訊,而不一樣進程間的通訊,最簡單的方式就是使用 Messenger 服務提供通訊接口,利用此方式,咱們無需使用 AIDL 即可執行進程間通訊 (IPC)。下面是 Messenger 使用的主要步驟:
1.服務實現一個 Handler,由其接收來自client的每個調用的回調
2.Handler 用於建立 Messenger 對象(對 Handler 的引用)
3.Messenger 建立一個 IBinder。服務經過 onBind() 使其返回client
4.client使用 IBinder 將 Messenger(引用服務的 Handler)實例化。而後使用Messenger將 Message 對象發送給服務
下面是一個使用 Messenger 接口的簡單服務演示樣例,服務端進程實現例如如下:
package com.zejian.ipctest.messenger;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
/** * Created by zejian * Time 2016/10/3. * Description:Messenger服務端簡單實例,服務端進程 */
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
private static final String TAG ="wzj" ;
/** * 用於接收從client傳遞過來的數據 */
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Log.i(TAG, "thanks,Service had receiver message from client!");
break;
default:
super.handleMessage(msg);
}
}
}
/** * 建立Messenger並傳入Handler實例對象 */
final Messenger mMessenger = new Messenger(new IncomingHandler());
/** * 當綁定Service時,該方法被調用,將經過mMessenger返回一個實現 * IBinder接口的實例對象 */
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "Service is invoke onBind");
return mMessenger.getBinder();
}
}
首先咱們一樣需要建立一個服務類MessengerService繼承自Service,同一時候建立一個繼承自Handler的IncomingHandler對象來接收client進程發送過來的消息並經過其handleMessage(Message msg)進行消息處理。
接着經過IncomingHandler對象建立一個Messenger對象,該對象是與client交互的特殊對象,而後在Service的onBind中返回這個Messenger對象的底層Binder就能夠。下面看看client進程的實現:
package com.zejian.ipctest.messenger;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.zejian.ipctest.R;
/** * Created by zejian * Time 2016/10/3. * Description: 與server交互的client */
public class ActivityMessenger extends Activity {
/** * 與服務端交互的Messenger */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/** * 實現與服務端連接的對象 */
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
/** * 經過服務端傳遞的IBinder對象,建立對應的Messenger * 經過該Messenger對象與服務端進行交互 */
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// 建立與服務交互的消息實體Message
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
//發送消息
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenager);
Button bindService= (Button) findViewById(R.id.bindService);
Button unbindService= (Button) findViewById(R.id.unbindService);
Button sendMsg= (Button) findViewById(R.id.sendMsgToService);
bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("zj","onClick-->bindService");
//當前Activity綁定服務端
bindService(new Intent(ActivityMessenger.this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
});
//發送消息給服務端
sendMsg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sayHello(v);
}
});
unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Unbind from the service
if (mBound) {
Log.d("zj","onClick-->unbindService");
unbindService(mConnection);
mBound = false;
}
}
});
}
}
在client進程中。咱們需要建立一個ServiceConnection對象,該對象表明與服務端的連接,當調用bindService方法將當前Activity綁定到MessengerService時。onServiceConnected方法被調用,利用服務端傳遞給來的底層Binder對象構造出與服務端交互的Messenger對象,接着建立與服務交互的消息實體Message,將要發生的信息封裝在Message中並經過Messenger實例對象發送給服務端。關於ServiceConnection、bindService方法、unbindService方法,前面已分析過,這裏就不反覆了。最後咱們需要在清單文件聲明Service和Activity。因爲要測試不一樣進程的交互,則需要將Service放在單獨的進程中,所以Service聲明例如如下:
<service android:name=".messenger.MessengerService" android:process=":remote" />
當中android:process=":remote"
表明該Service在單獨的進程中建立,最後咱們執行程序,結果例如如下:
接着屢次點擊綁定服務,而後發送信息給服務端,最後解除綁定,Log打印例如如下:
經過上述樣例可知Service服務端確實收到了client發送的信息。而且在Messenger中進行數據傳遞必須將數據封裝到Message中,因爲Message和Messenger都實現了Parcelable接口,可以輕鬆跨進程傳遞數據(關於Parcelable接口可以看博主的還有一篇文章:序列化與反序列化之Parcelable和Serializable淺析)。而Message可以傳遞的信息載體有,what,arg1,arg2,Bundle以及replyTo。至於object字段,對於同一進程中的數據傳遞確實很是有用,但對於進程間的通訊,則顯得至關尷尬,在android2.2前。object不支持跨進程傳輸,但即使是android2.2以後也僅僅能傳遞android系統提供的實現了Parcelable接口的對象,也就是說咱們經過本身定義實現Parcelable接口的對象沒法經過object字段來傳遞。所以object字段的有用性在跨進程中也變得至關低了。只是所幸咱們還有Bundle對象,Bundle可以支持大量的數據類型。
接着從Log咱們也看出無論是使用拓展Binder類的實現方式仍是使用Messenger的實現方式。它們的生命週期方法的調用順序基本是同樣的,即onCreate()、onBind、onUnBind、onDestroy,而且屢次綁定中也僅僅有第一次時才調用onBind()。好~,以上的樣例演示了怎樣在服務端解釋client發送的消息,但有時候咱們可能還需要服務端能迴應client,這時便需要提供雙向消息傳遞了,下面就來實現一個簡單服務端與client雙向消息傳遞的簡單樣例。
先來看看服務端的改動,在服務端,咱們僅僅需改動IncomingHandler,收到消息後,給client回覆一條信息。
/** * 用於接收從client傳遞過來的數據 */
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Log.i(TAG, "thanks,Service had receiver message from client!");
//回覆client信息,該對象由client傳遞過來
Messenger client=msg.replyTo;
//獲取回覆信息的消息實體
Message replyMsg=Message.obtain(null,MessengerService.MSG_SAY_HELLO);
Bundle bundle=new Bundle();
bundle.putString("reply","ok~,I had receiver message from you! ");
replyMsg.setData(bundle);
//向client發送消息
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
接着改動client。爲了接收服務端的回覆,client也需要一個接收消息的Messenger和Handler。事實上現例如如下:
/** * 用於接收server返回的信息 */
private Messenger mRecevierReplyMsg= new Messenger(new ReceiverReplyMsgHandler());
private static class ReceiverReplyMsgHandler extends Handler{
private static final String TAG = "zj";
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
//接收服務端回覆
case MessengerService.MSG_SAY_HELLO:
Log.i(TAG, "receiver message from service:"+msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
除了加入以上代碼,還需要在發送信息時把接收server端的回覆的Messenger經過Message的replyTo參數傳遞給服務端,以便做爲同窗橋樑。代碼例如如下:
public void sayHello(View v) {
if (!mBound) return;
// 建立與服務交互的消息實體Message
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
//把接收server端的回覆的Messenger經過Message的replyTo參數傳遞給服務端
msg.replyTo=mRecevierReplyMsg;
try {
//發送消息
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
ok~。到此服務端與client雙向消息傳遞的簡單樣例改動完畢。咱們執行一下代碼,看看Log打印。例如如下:
由Log可知,服務端和client確實各自收到了信息,到此咱們就把採用Messenge進行跨進程通訊的方式分析完了,最後爲了輔助你們理解,這裏提供一張經過Messenge方式進行進程間通訊的原理圖:
1.多個client可同一時候鏈接到一個服務。只是,僅僅有在第一個client綁定時,系統纔會調用服務的 onBind() 方法來檢索 IBinder。系統隨後無需再次調用 onBind(),即可將同一 IBinder 傳遞至不論什麼其它綁定的client。當最後一個client取消與服務的綁定時。系統會將服務銷燬(除非 startService() 也啓動了該服務)。
2.一般狀況下咱們應該在client生命週期(如Activity的生命週期)的引入 (bring-up) 和退出 (tear-down) 時刻設置綁定和取消綁定操做,以便控制綁定狀態下的Service。通常有下面兩種狀況:
假設僅僅需要在 Activity 可見時與服務交互,則應在 onStart() 期間綁定,在 onStop() 期間取消綁定。
假設但願 Activity 在後臺中止執行狀態下仍可接收響應,則可在 onCreate() 期間綁定,在 onDestroy() 期間取消綁定。需要注意的是,這意味着 Activity 在其整個執行過程當中(甚至包含後臺執行期間)都需要使用服務。所以假設服務位於其它進程內,那麼當提升該進程的權重時。系統很是可能會終止該進程。
3.一般狀況下(注意)。切勿在 Activity 的 onResume() 和 onPause() 期間綁定和取消綁定,因爲每一次生命週期轉換都會發生這些回調,這樣反覆綁定與解綁是不合理的。此外,假設應用內的多個 Activity 綁定到同一服務,而且當中兩個 Activity 之間發生了轉換,則假設當前 Activity 在下一次綁定(恢復期間)以前取消綁定(暫停期間),系統可能會銷燬服務並重建服務,所以服務的綁定不該該發生在 Activity 的 onResume() 和 onPause()中。
4.咱們應該始終捕獲 DeadObjectException DeadObjectException 異常。該異常是在鏈接中斷時引起的,表示調用的對象已死亡,也就是Service對象已銷燬。這是遠程方法引起的惟一異常,DeadObjectException繼承自RemoteException,所以咱們也可以捕獲RemoteException異常。
5.應用組件(client)可經過調用 bindService() 綁定到服務,Android 系統隨後調用服務的 onBind() 方法。該方法返回用於與服務交互的 IBinder,而該綁定是異步執行的。
經過前面對兩種服務狀態的分析,相信你們已對Service的兩種狀態有了比較清晰的瞭解,那麼現在咱們就來分析一下當啓動狀態和綁定狀態同一時候存在時,又會是怎麼的場景?
儘管服務的狀態有啓動和綁定兩種。但實際上一個服務可以同一時候是這兩種狀態,也就是說,它既可以是啓動服務(以無限期執行),也可以是綁定服務。
有點需要注意的是Android系統僅會爲一個Service建立一個實例對象,因此無論是啓動服務仍是綁定服務。操做的是同一個Service實例。而且因爲綁定服務或者啓動服務執行順序問題將會出現下面兩種狀況:
先綁定服務後啓動服務
假設當前Service實例先以綁定狀態執行。而後再以啓動狀態執行,那麼綁定服務將會轉爲啓動服務執行。這時假設以前綁定的宿主(Activity)被銷燬了,也不會影響服務的執行。服務仍是會一直執行下去,指定收到調用中止服務或者內存不足時纔會銷燬該服務。
先啓動服務後綁定服務
假設當前Service實例先以啓動狀態執行。而後再以綁定狀態執行。當前啓動服務並不會轉爲綁定服務。但是仍是會與宿主綁定。僅僅是即便宿主解除綁定後。服務依舊按啓動服務的生命週期在後臺執行。直到有Context調用了stopService()或是服務自己調用了stopSelf()方法抑或內存不足時纔會銷燬服務。
以上兩種狀況顯示出啓動服務的優先級確實比綁定服務高一些。只是無論Service是處於啓動狀態仍是綁定狀態。或處於啓動而且綁定狀態,咱們都可以像使用Activity那樣經過調用 Intent 來使用服務(即便此服務來自還有一應用)。 固然,咱們也可以經過清單文件將服務聲明爲私有服務。阻止其它應用訪問。最後這裏有點需要特殊說明一下的。因爲服務在其託管進程的主線程中執行(UI線程),它既不建立本身的線程。也不在單獨的進程中執行(除非另行指定)。 這意味着,假設服務將執行不論什麼耗時事件或阻止性操做(好比 MP3 播放或聯網)時,則應在服務內建立新線程來完畢這項工做,簡而言之,耗時操做應該另起線程執行。僅僅有經過使用單獨的線程,才幹夠減小發生「應用無響應」(ANR) 錯誤的風險,這樣應用的主線程才幹專一於用戶與 Activity 之間的交互, 以達到更好的用戶體驗。
前臺服務被以爲是用戶主動意識到的一種服務,所以在內存不足時。系統也不會考慮將其終止。 前臺服務必須爲狀態欄提供通知,狀態欄位於「正在進行」標題下方。這意味着除非服務中止或從前臺刪除,不然不能清除通知。
好比將從服務播放音樂的音樂播放器設置爲在前臺執行。這是因爲用戶明白意識到其操做。
狀態欄中的通知可能表示正在播放的歌曲。並贊成用戶啓動 Activity 來與音樂播放器進行交互。假設需要設置服務執行於前臺, 咱們該怎樣才幹實現呢?Android官方給咱們提供了兩個方法,各自是startForeground()和stopForeground(),這兩個方式解析例如如下:
startForeground(int id, Notification notification)
該方法的做用是把當前服務設置爲前臺服務,當中id參數表明惟一標識通知的整型數,需要注意的是提供給 startForeground() 的整型 ID 不得爲 0,而notification是一個狀態欄的通知。
stopForeground(boolean removeNotification)
該方法是用來從前臺刪除服務,此方法傳入一個布爾值,指示是否也刪除狀態欄通知,true爲刪除。
注意該方法並不會中止服務。
但是,假設在服務正在前臺執行時將其中止,則通知也會被刪除。
下面咱們結合一個簡單案例來使用以上兩個方法。ForegroundService代碼例如如下:
package com.zejian.ipctest.foregroundService;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import com.zejian.ipctest.R;
/** * Created by zejian * Time 2016/10/4. * Description:啓動前臺服務Demo */
public class ForegroundService extends Service {
/** * id不可設置爲0,不然不能設置爲前臺service */
private static final int NOTIFICATION_DOWNLOAD_PROGRESS_ID = 0x0001;
private boolean isRemove=false;//是否需要移除
/** * Notification */
public void createNotification(){
//使用兼容版本號
NotificationCompat.Builder builder=new NotificationCompat.Builder(this);
//設置狀態欄的通知圖標
builder.setSmallIcon(R.mipmap.ic_launcher);
//設置通知欄橫條的圖標
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.screenflash_logo));
//禁止用戶點擊刪除button刪除
builder.setAutoCancel(false);
//禁止滑動刪除
builder.setOngoing(true);
//右上角的時間顯示
builder.setShowWhen(true);
//設置通知欄的標題內容
builder.setContentTitle("I am Foreground Service!!!");
//建立通知
Notification notification = builder.build();
//設置爲前臺服務
startForeground(NOTIFICATION_DOWNLOAD_PROGRESS_ID,notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int i=intent.getExtras().getInt("cmd");
if(i==0){
if(!isRemove) {
createNotification();
}
isRemove=true;
}else {
//移除前臺服務
if (isRemove) {
stopForeground(true);
}
isRemove=false;
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
//移除前臺服務
if (isRemove) {
stopForeground(true);
}
isRemove=false;
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在ForegroundService類中。建立了一個notification的通知。並經過啓動Service時傳遞過來的參數推斷是啓動前臺服務仍是關閉前臺服務,最後在onDestroy方法被調用時,也應該移除前臺服務。下面是ForegroundActivity的實現:
package com.zejian.ipctest.foregroundService;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.zejian.ipctest.R;
/** * Created by zejian * Time 2016/10/4. * Description: */
public class ForegroundActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_foreground);
Button btnStart= (Button) findViewById(R.id.startForeground);
Button btnStop= (Button) findViewById(R.id.stopForeground);
final Intent intent = new Intent(this,ForegroundService.class);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
intent.putExtra("cmd",0);//0,開啓前臺服務,1,關閉前臺服務
startService(intent);
}
});
btnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
intent.putExtra("cmd",1);//0,開啓前臺服務,1,關閉前臺服務
startService(intent);
}
});
}
}
代碼比較簡單。咱們直接執行程序看看結果:
ok~,以上即是有關於Service前臺服務的內容。接下來再聊聊服務與線程的差異
二者概念的迥異
Thread 是程序執行的最小單元,它是分配CPU的基本單位,android系統中UI線程也是線程的一種,固然Thread還可以用於執行一些耗時異步的操做。
Service是Android的一種機制,服務是執行在主線程上的,它是由系統進程託管。它與其它組件之間的通訊類似於client和server,是一種輕量級的IPC通訊,這樣的通訊的載體是binder,它是在linux層交換信息的一種IPC,而所謂的Service後臺任務僅僅只是是指沒有UI的組件罷了。
二者的執行任務迥異
在android系統中,線程通常指的是工做線程(即後臺線程),而主線程是一種特殊的工做線程,它負責將事件分派給對應的用戶界面小工具,如畫圖事件及事件響應。所以爲了保證應用 UI 的響應能力主線程上不可執行耗時操做。假設執行的操做不能很是快完畢,則應確保它們在單獨的工做線程執行。
Service 則是android系統中的組件,普通狀況下它執行於主線程中,所以在Service中是不可以執行耗時操做的,不然系統會報ANR異常。之因此稱Service爲後臺服務,大部分緣由是它自己沒有UI,用戶沒法感知(固然也可以利用某些手段讓用戶知道)。但假設需要讓Service執行耗時任務。可在Service中開啓單獨線程去執行。
二者使用場景
當要執行耗時的網絡或者數據庫查詢以及其它堵塞UI線程或密集使用CPU的任務時,都應該使用工做線程(Thread),這樣才幹保證UI線程不被佔用而影響用戶體驗。
在應用程序中,假設需要長時間的在後臺執行,而且不需要交互的狀況下,使用服務。比方播放音樂,經過Service+Notification方式在後臺執行同一時候在通知欄顯示着。
二者的最佳使用方式
在大部分狀況下,Thread和Service都會結合着使用,比方下載文件,一般會經過Service在後臺執行+Notification在通知欄顯示+Thread異步下載,再如應用程序會維持一個Service來從網絡中獲取推送服務。
在Android官方看來也是如此。因此官網提供了一個Thread與Service的結合來方便咱們執行後臺耗時任務。它就是IntentService。(假設想更深刻了解IntentService,可以看博主的還有一篇文章:Android 多線程之IntentService 全然具體解釋),固然 IntentService並不適用於所有的場景,但它的長處是使用方便、代碼簡潔,不需要咱們建立Service實例並同一時候也建立線程。某些場景下仍是很是讚的!
因爲IntentService是單個worker thread,因此任務需要排隊,所以不適合大多數的多任務狀況。
二者的真正關係
關於Service生命週期方法的執行順序。前面咱們已分析得幾乎相同了,這裏又一次給出一張執行的流程圖(出自Android官網)
當中左圖顯示了使用 startService() 所建立的服務的生命週期,右圖顯示了使用 bindService() 所建立的服務的生命週期。經過圖中的生命週期方法,咱們可以監控Service的整體執行過程。包含建立,執行。銷燬,關於Service不一樣狀態下的方法回調在前面的分析中已描寫敘述得很是清晰,這裏就不反覆了,下面給出官網對生命週期的原文描寫敘述:
服務的整個生命週期從調用 onCreate() 開始起,到 onDestroy() 返回時結束。
與 Activity 類似,服務也在 onCreate() 中完畢初始設置,並在 onDestroy() 中釋放所有剩餘資源。好比,音樂播放服務可以在 onCreate() 中建立用於播放音樂的線程,而後在 onDestroy() 中中止該線程。
無論服務是經過 startService() 仍是 bindService() 建立。都會爲所有服務調用 onCreate() 和 onDestroy() 方法。
服務的有效生命週期從調用 onStartCommand() 或 onBind() 方法開始。每種方法均有 Intent 對象,該對象分別傳遞到 startService() 或 bindService()。
對於啓動服務。有效生命週期與整個生命週期同一時候結束(即使是在 onStartCommand() 返回以後,服務仍然處於活動狀態)。對於綁定服務,有效生命週期在 onUnbind() 返回時結束。
從執行流程圖來看。服務的生命週期比 Activity 的生命週期要簡單得多。但是。咱們必須密切關注怎樣建立和銷燬服務,因爲服務可以在用戶沒有意識到的狀況下執行於後臺。管理服務的生命週期(從建立到銷燬)有下面兩種狀況:
啓動服務
該服務在其它組件調用 startService() 時建立,而後無限期執行,且必須經過調用 stopSelf() 來自行中止執行。此外,其它組件也可以經過調用 stopService() 來中止服務。
服務中止後。系統會將其銷燬。
綁定服務
該服務在還有一個組件(client)調用 bindService() 時建立。而後,client經過 IBinder 接口與服務進行通訊。client可以經過調用 unbindService() 關閉鏈接。多個client可以綁定到一樣服務,而且當所有綁定所有取消後,系統即會銷燬該服務。 (服務沒必要自行中止執行)
儘管可以經過以上兩種狀況管理服務的生命週期,但是咱們還必須考慮第二種狀況,也就是啓動服務與綁定服務的結合體,也就是說,咱們可以綁定到已經使用 startService() 啓動的服務。好比。可以經過使用 Intent(標識要播放的音樂)調用 startService() 來啓動後臺音樂服務。
隨後,可能在用戶需要稍加控制播放器或獲取有關當前播放歌曲的信息時,Activity 可以經過調用 bindService() 綁定到服務。在這樣的狀況下,除非所有client均取消綁定,不然 stopService() 或 stopSelf() 不會真正中止服務。所以在這樣的狀況下咱們需要特別注意。
既然有隱式啓動,那麼就會有顯示啓動。那就先來了解一下什麼是隱式啓動和顯示啓動。
//顯示啓動
Intent intent = new Intent(this,ForegroundService.class);
startService(intent);
final Intent serviceIntent=new Intent(); serviceIntent.setAction("com.android.ForegroundService");
startService(serviceIntent);
存在的意義
假設在同一個應用中,二者都可以用。在不一樣應用時。僅僅能用隱式啓動。
Android 5.0以上的隱式啓動問題
Android 5.0以後google出於安全的角度禁止了隱式聲明Intent來啓動Service。
假設使用隱式啓動Service,會出沒有指明Intent的錯誤,例如如下:
主要緣由咱們可以從源代碼中找到,這裏看看Android 4.4的ContextImpl源代碼中的validateServiceIntent(Intent service),可知假設啓動service的intent的component和package都爲空而且版本號大於KITKAT的時候僅僅是報出一個警報,告訴開發人員隱式聲明intent去啓動Service是不安全的.
而在android5.0以後呢?咱們這裏看的是android6.0的源代碼例如如下(sublime text查android各個版本號源代碼就是爽呀!!):
從源代碼可以看出假設啓動service的intent的component和package都爲空而且版本號大於LOLLIPOP(5.0)的時候,直接拋出異常。該異常與以前隱式啓動所報的異常時一致的。
那麼該怎樣解決呢?
解決方式
final Intent serviceIntent=new Intent(); serviceIntent.setAction("com.android.ForegroundService");
serviceIntent.setPackage(getPackageName());//設置應用的包名
startService(serviceIntent);
public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}
調用方式例如如下:
Intent mIntent=new Intent();//輔助Intent
mIntent.setAction("com.android.ForegroundService");
final Intent serviceIntent=new Intent(getExplicitIntent(this,mIntent));
startService(serviceIntent);
到此問題完美解決。
實際上這樣的作法並不推薦,但是既然談到了,咱們這裏就給出一些實現思路吧。
主要分下面3種狀況
/** * 返回 START_STICKY或START_REDELIVER_INTENT * @param intent * @param flags * @param startId * @return */
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// return super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
這裏給出第一種方式的代碼實現例如如下:
package com.zejian.ipctest.neverKilledService;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.support.annotation.Nullable;
/** * Created by zejian * Time 2016/10/4. * Description:用戶經過 settings -> Apps -> Running -> Stop 方式殺死Service */
public class ServiceKilledByAppStop extends Service{
private BroadcastReceiver mReceiver;
private IntentFilter mIF;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Intent a = new Intent(ServiceKilledByAppStop.this, ServiceKilledByAppStop.class);
startService(a);
}
};
mIF = new IntentFilter();
//本身定義action
mIF.addAction("com.restart.service");
//註冊廣播接者
registerReceiver(mReceiver, mIF);
}
@Override
public void onDestroy() {
super.onDestroy();
Intent intent = new Intent();
intent.setAction("com.restart.service");
//發送廣播
sendBroadcast(intent);
unregisterReceiver(mReceiver);
}
}
ok~。以上即是保證服務在必定場景下不被殺死的解決思路,關於第3種狀況,假設有解決方式,請留言哈。好,關於Service的所有介紹就此完結。
主要參考資料:
https://developer.android.com/guide/components/services.html#Notifications
https://developer.android.com/guide/components/processes-and-threads.html
https://developer.android.com/guide/components/bound-services.html#Lifecycle
http://blog.csdn.net/vrix/article/details/45289207 android 開發藝術探索