1. 程式人生 > >Android Service全面解析

Android Service全面解析

Service概念及用途

A service is an application component that can perform long-running operations in the background and does not provide a user interface。
通常service用來執行一些耗時操作,或者後臺執行不提供使用者互動介面的操作。其他的應用元件可以啟動Service,即便使用者切換了其他應用,啟動的Service仍可在後臺執行。一個元件可以與Service繫結並與之互動,甚至是跨程序通訊(IPC)。例如,一個Service可以在後臺執行網路請求、播放音樂、執行檔案讀寫操作或者與 content provider互動等。

Service生命週期

為了建立Service,需要繼承Service類。並重寫它的回撥方法,這些回撥方法反應了Service的生命週期,並提供了繫結Service的機制。最重要的Service的生命週期回撥方法如下所示:

  • onStartCommand()
    當其他元件呼叫startService()方法請求啟動Service時,該方法被回撥。一旦Service啟動,它會在後臺獨立執行。當Service執行完以後,需呼叫stopSelf() 或 stopService()方法停止Service。(若您只希望bind Service,則無需呼叫這些方法)
  • onBind()
    當其他元件呼叫bindService()方法請求繫結Service時,該方法被回撥。該方法返回一個IBinder介面,該介面是Service與繫結的元件進行互動的橋樑。若Service未繫結其他元件,該方法應返回null。
  • onCreate()
    當Service第一次建立時,回撥該方法。該方法只被回撥一次,並在onStartCommand() 或 onBind()方法被回撥之前執行。若Service處於執行狀態,該方法不會回撥。
  • onDestroy()
    當Service被銷燬時回撥,在該方法中應清除一些佔用的資源,如停止執行緒、結束繫結註冊的監聽器或broadcast receiver 等。該方法是Service中的最後一個回撥。

這裡寫圖片描述

如果某個元件通過呼叫startService()啟動了Service(系統會回撥onStartCommand()方法),那麼直到在Service中手動呼叫stopSelf()方法、或在其他元件中手動呼叫stopService()方法,該Service才會停止。

如果某個元件通過呼叫bindService()綁定了Service(系統會回撥onBind()方法),只要該元件與Service處於繫結狀態,Service就會一直執行,當Service不再與元件繫結時,該Service將被destroy。

上面兩條路徑並不是毫不相干的:當呼叫startService()後,您仍可以bind該Service。比如,當播放音樂時,需呼叫startService()啟動指定播放的音樂,當需要獲取該音樂的播放進度時,則需要呼叫bindService(),在這種情況下,直到Service被unbind ,呼叫stopService() 或stopSelf()都不能停止該Service。

當系統記憶體低時,系統將強制停止Service的執行;若Service綁定了正在與使用者互動的activity,那麼該Service將不大可能被系統kill。如果建立的是前臺Service,那麼該Service幾乎不會被kill。否則,當建立了一個長時間在後臺執行的Service後,系統會降低該Service在後臺任務棧中的級別——這意味著它容易被kill,所以在開發Service時,需要使Service變得容易被restart,因為一旦Service被kill,再restart它需要其資源可用時才行,當然這也取決於onStartCommand()方法返回的值。
onStartCommand()方法必須返回一個整數,這個整數是一個描述了在系統的kill事件中,系統應該如何繼續這個服務的值。onStartCommand()有4種返回值:

  • START_STICKY
    若系統在onStartCommand()執行並返回後kill了service,那麼service會被recreate並回調onStartCommand()。注意不要重新傳遞最後一個Intent。相反,系統回撥onStartCommand()時回傳一個空的Intent,除非有 pending intents傳遞,否則Intent將為null。該模式適合做一些類似播放音樂的操作。
  • START_NOT_STICKY
    “非粘性的”。若執行完onStartCommand()方法後,系統就kill了service,不要再重新建立service,除非系統回傳了一個pending intent。這避免了在不必要的時候執行service,您的應用也可以restart任何未完成的操作。
  • START_REDELIVER_INTENT
    若系統在onStartCommand()執行並返回後kill了service,那麼service會被recreate並回調onStartCommand()並將最後一個Intent回傳至該方法。任何 pending intents都會被輪流傳遞。該模式適合做一些類似下載檔案的操作。
  • START_STICKY_COMPATIBILITY
    START_STICKY的相容版本,但不保證服務被kill後一定能重啟。

Service的註冊

在manifest檔案中註冊service的方式如下:

<manifest 
   ...
    <application
        ...
        <service android:name="com.hx.servicetest.MyService" /> 
    </application>
</manifest>

除此之外,在<\service>標籤中還可以配置其他屬性:

  • android:name —>服務全限定類名(唯一不可預設的)
  • android:label —>服務的名字,如果此項不設定,那麼預設顯示的服務名則為類名
  • android:icon —>服務的圖示
  • android:permission —>申明此服務的許可權,這意味著只有提供了該許可權的應用才能控制或連線此服務
  • android:process —>表示該服務是否執行在另外一個程序,如果設定了此項,那麼將會在包名後面加上這段字串表示另一程序的名字
  • android:enabled —>如果此項設定為 true,那麼 Service 將會預設被系統啟動,不設定預設此項為 false
  • android:exported —>表示該服務是否能夠被其他應用程式所控制或連線,不設定預設此項為 false

Service的啟動還可以使用隱式Intent,在<\service>中配置intent-filter即可,則Service可以響應帶有指定action的Intent。

<service android:name="com.hx.servicetest.MyRemoteService" >
         <intent-filter>  
              <action android:name="com.hx.servicetest.MyAIDLService"/>  
         </intent-filter>
</service>

Service的啟動

有了 Service 類我們如何啟動他呢,有兩種方法:

  • Context.startService()
  • Context.bindService()

當然,service也可以同時在上述兩種方式下執行。這涉及到Service中兩個回撥方法的執行:onStartCommand()(通過start方式啟動一個service時回撥的方法)、onBind()(通過bind方式啟動一個service回撥的方法)。
無論通過那種方式啟動service(start、bind、start&bind),任何元件(甚至其他應用的元件)都可以使用service。並通過Intent傳遞引數。當然,您也可以將Service在manifest檔案中配置成私有的,不允許其他應用訪問。

startService

其他元件呼叫startService()方法啟動一個Service。一旦啟動,Service將一直執行在後臺即便啟動Service的元件已被destroy。通常,一個被start的Service會在後臺執行單獨的操作,也並不給啟動它的元件返回結果。比如說,一個start的Service執行在後臺下載或上傳一個檔案的操作,完成之後,Service應自己停止。

一般使用如下兩種方式建立一個start Service:

  • 繼承Service類
    請務必在Service中開啟執行緒來執行耗時操作,因為Service執行在主執行緒中。
  • 繼承IntentService類
    IntentService繼承於Service,若Service不需要同時處理多個請求,那麼使用IntentService將是最好選擇:您只需要重寫onHandleIntent()方法,該方法接收一個回傳的Intent引數,您可以在方法內進行耗時操作,因為它預設開啟了一個子執行緒,操作執行完成後也無需手動呼叫stopSelf()方法,onHandleIntent()會自動呼叫該方法。

(1)繼承Service類
如果你需要在Service中執行多執行緒而不是處理一個請求佇列,那麼需要繼承Service類,分別處理每個Intent。在Service中執行操作時,處理每個請求都需要開啟一個新執行緒(new Thread()),並且同一時刻一個執行緒只能處理一個請求,寫法很簡單,不再贅述。
下面演示一個同一個執行緒處理多個任務的例子,使用HandlerThread實現,如果你對HandlerThread不瞭解,可以看這篇文章Android HandlerThread 全面解析

public class MyService extends Service {
    private HandlerThread mThread;
    private Handler mHandler;

    @Override  
    public void onCreate() {  
        super.onCreate();  
        MainActivity.showlog("onCreate()");
        initBackThread();
    }  

    private void initBackThread() {
        mThread = new HandlerThread("ServiceStartArguments");
        mThread.start();
        mHandler = new Handler(mThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                //模擬耗時執行緒操作
                MainActivity.showlog("processing...msg.arg1="+msg.arg1);
                try {
                    Thread.sleep(5000);
                } catch (Exception e) {
                    Thread.currentThread().interrupt();
                }
                MainActivity.showlog("stopSelf...msg.arg1="+msg.arg1);
                //當所有操作完成後,服務自己停止
                stopSelf(msg.arg1);
            }
        };
    }

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");

        Message msg = mHandler.obtainMessage();
        msg.arg1 = startId;
        mHandler.sendMessage(msg);

        return START_STICKY;
    }  

    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        MainActivity.showlog("onDestroy()");  
    }  

    @Override  
    public IBinder onBind(Intent intent) {
        MainActivity.showlog("onBind()");
        return null;
    }    
}  

新增Button點選事件:

    @Override  
    public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.start_service:
            showlog("click Start Service button");
            Intent it1 = new Intent(this, MyService.class);  
            startService(it1);  
            break;  
        case R.id.stop_service: 
            showlog("click Stop Service button"); 
            Intent it2 = new Intent(this, MyService.class);  
            stopService(it2);  
            break;   
        default:  
            break;  
        }  
    }  

這樣的話,一個簡單的帶有Service功能的程式就寫好了,現在我們將程式執行起來,並點選一下Start Service按鈕,可以看到LogCat的列印日誌如下:
這裡寫圖片描述
流程:點選->onCreate->onStartCommand->耗時事件處理(5S)->onDestroy

那麼如果我連續兩次點選Start Service按鈕呢?這個時候的列印日誌如下:
這裡寫圖片描述
流程:第一次點選->onCreate->onStartCommand->耗時事件處理(5S)
第二次點選->onStartCommand->耗時事件處理(5S)->onDestroy
這裡的耗時處理是藉助HandlerThread來實現的,多個任務在同一個執行緒中,第一個事件處理完成後再進行第二個事件處理,直到最後一個任務處理完畢,才會停止Service,使用的是stopSelf(int startId);關於stopSelf的使用,可以參考後面 Service的銷燬 一節內容。

點選Start Service然後再點選Stop Service按鈕就可以將MyService立即停止掉了,Log如下:
這裡寫圖片描述
注:多個啟動Service的請求可能導致onStartCommand()多次呼叫,但只需呼叫stopSelf() 、 stopService()這兩個方法之一,就可立即停止該服務。

上面如果我們的耗時任務時間夠長,在MyService停止之前點選”返回”,Activity被幹掉了,但是我們的服務仍然在執行,可以檢視 設定–>應用–>正在執行,截圖如下:
這裡寫圖片描述

(2)繼承IntentService類
在大多數情況下,start Service並不會同時處理多個請求,因為處理多執行緒較為危險,所以繼承IntentService類帶建立Service是個不錯選擇。
使用IntentService的要點如下:

  • 預設在子執行緒中處理回傳到onStartCommand()方法中的Intent;
  • 在重寫的onHandleIntent()方法中處理按時間排序的Intent佇列,所以不用擔心多執行緒(multi-threading)帶來的問題。
  • 當所有請求處理完成後,自動停止service,無需手動呼叫stopSelf()方法;
  • 預設實現了onBind()方法,並返回null;
  • 預設實現了onStartCommand()方法,並將回傳的Intent以序列的形式傳送給onHandleIntent(),您只需重寫該方法並處理Intent即可。

綜上所述,您只需重寫onHandleIntent()方法即可,當然,還需要建立一個構造方法,示例如下:

public class MyIntentService extends IntentService {        

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override  
    public void onHandleIntent(Intent intent) {
        //模擬耗時執行緒操作
        MainActivity.showlog("processing...");
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            Thread.currentThread().interrupt();
        } 
    }  
}  

如果您還希望在IntentService的繼承類中重寫其他生命週期方法,如onCreate()、onStartCommand() 或 onDestroy(),那麼請先呼叫各自的父類方法以保證子執行緒能夠正常啟動。
比如,要實現onStartCommand()方法,需返回其父類方法:

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");
        return super.onStartCommand(intent, flags, startId);  
    } 

注:除onHandleIntent()外,onBind()方法也無需呼叫其父類方法。

bindService

上面學習了startService()啟動 Service,不過這樣的話Service和Activity的關係並不大,只是Activity通知了Service一下:“你可以啟動了。”然後Service就去忙自己的事情了。那麼有沒有什麼辦法能讓它們倆的關聯更多一些呢?比如說在Activity中可以指定讓Service去執行什麼任務,當然可以,只需要讓Activity和Service建立關聯就好了。
bindService()方法的意思是,把這個 Service 和呼叫 Service 的客戶類綁起來,如果這個客戶類被銷燬,Service 也會被銷燬。用這個方法的一個好處是,bindService() 方法執行後 Service 會回撥 onBind() 方法,你可以從這裡返回一個實現了 IBind 介面的類,在客戶端操作這個類就能和這個服務通訊了,比如得到 Service 執行的狀態或其他操作。如果 Service 還沒有執行,使用這個方法啟動 Service 就會 onCreate() 方法而不會呼叫 onStartCommand()。

觀察MyService中的程式碼,你會發現有一個onBind()方法我們都沒有使用到,這個方法其實就是用於和Activity建立關聯的,重新寫一個MyBindService,如下所示:

public class MyBindService extends Service {    
    private MyBinder mBinder = new MyBinder();

    @Override  
    public void onCreate() {  
        super.onCreate();  
        MainActivity.showlog("onCreate()");  
    }  

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");
        return super.onStartCommand(intent, flags, startId);  
    }  

    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        MainActivity.showlog("onDestroy()");  
    }  

    //Service自定義方法
    public void doSomethingInService(){  
        MainActivity.showlog("doSomethingInService()");  
    }

    //複寫onBind方法,並且返回IBinder的實現類
    @Override  
    public IBinder onBind(Intent intent) {
        MainActivity.showlog("onBind()");
        return mBinder;  
    }  

    @Override  
    public boolean onUnbind(Intent intent) {  
        MainActivity.showlog("onUnbind()");  
        return super.onUnbind(intent);  
    } 

    //內部類,擴充套件自Binder類
    class MyBinder extends Binder {  
        //MyBinder自定義方法
        public void doSomethingInBinder() {  
            MainActivity.showlog("doSomethingInBinder()");  
        }
        public MyBindService getService(){
            return MyBindService.this;  
        }  
    }    
} 

這裡我們新增了一個MyBinder類繼承自Binder類,然後在MyBinder中添加了一個doSomethingInBinder()方法用於在後臺執行任務,而且在Service中還寫了一個doSomethingInService()方法,同樣可以執行後臺任務,其實這裡只是列印了一行日誌。
接下來再修改MainActivity中的程式碼,讓MainActivity和MyBindService之間建立關聯,程式碼如下所示:

public class MainActivity extends Activity implements OnClickListener {    
    private Button startService;    
    private Button stopService; 
    private Button startIntentService;    
    private Button stopIntentService;
    private Button bindService;    
    private Button unbindService;    
    private MyBindService.MyBinder myBinder;  
    private boolean isConnected = false;

    private ServiceConnection connection = new ServiceConnection() {   
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
            showlog("onServiceDisconnected"); 
            isConnected = false;
        }  

        @Override  
        public void onServiceConnected(ComponentName name, IBinder iBinder) {
            showlog("onServiceConnected"); 
            myBinder = (MyBindService.MyBinder) iBinder;  
            myBinder.doSomethingInBinder();  
            MyBindService service = myBinder.getService();  
            service.doSomethingInService();  
            isConnected = true;  
        }  
    };  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        startService = (Button) findViewById(R.id.start_service);  
        stopService = (Button) findViewById(R.id.stop_service);
        startIntentService = (Button) findViewById(R.id.start_intent_service);  
        stopIntentService = (Button) findViewById(R.id.stop_intent_service);
        bindService = (Button) findViewById(R.id.bind_service);  
        unbindService = (Button) findViewById(R.id.unbind_service);  
        startService.setOnClickListener(this);  
        stopService.setOnClickListener(this);  
        startIntentService.setOnClickListener(this);  
        stopIntentService.setOnClickListener(this);
        bindService.setOnClickListener(this);  
        unbindService.setOnClickListener(this);  
    }  

    @Override  
    public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.start_service:
            showlog("click Start Service button");
            Intent it1 = new Intent(this, MyService.class);  
            startService(it1);  
            break;  
        case R.id.stop_service: 
            showlog("click Stop Service button"); 
            Intent it2 = new Intent(this, MyService.class);  
            stopService(it2);  
            break;  
        case R.id.start_intent_service:
            showlog("click Start IntentService button");
            Intent it3 = new Intent(this, MyIntentService.class);  
            startService(it3);  
            break;  
        case R.id.stop_intent_service: 
            showlog("click Stop IntentService button"); 
            Intent it4 = new Intent(this, MyIntentService.class);  
            stopService(it4);  
            break;  
        case R.id.bind_service:  
            showlog("click Bind Service button");
            Intent it5 = new Intent(this, MyBindService.class);  
            bindService(it5, connection, BIND_AUTO_CREATE);  
            break;  
        case R.id.unbind_service:
            showlog("click Unbind Service button"); 
            if(isConnected){  
                unbindService(connection);  
            }  
            break;  
        default:  
            break;  
        }  
    }  

    public static void showlog(String info) {
        System.out.print("Watson "+info+"\n");
    }

}

可以看到,這裡我們首先建立了一個ServiceConnection的匿名類,在裡面重寫了onServiceConnected()方法和onServiceDisconnected()方法,這兩個方法分別會在Activity與Service建立關聯和解除關聯的時候呼叫。在onServiceConnected()方法中,我們又通過向下轉型得到了MyBinder的例項,有了這個例項,Activity和Service之間的關係就變得非常緊密了。現在我們可以在Activity中根據具體的場景來呼叫MyBinder中的任何public方法,即實現了Activity指揮Service幹什麼Service就去幹什麼的功能。
當然,現在Activity和Service其實還沒關聯起來了呢,這個功能是在Bind Service按鈕的點選事件裡完成的。可以看到,這裡我們仍然是構建出了一個Intent物件,然後呼叫bindService()方法將Activity和Service進行繫結。bindService()方法接收三個引數,第一個引數就是剛剛構建出的Intent物件,第二個引數是前面創建出的ServiceConnection的例項,第三個引數是一個標誌位,有兩個flag, BIND_DEBUG_UNBIND BIND_AUTO_CREATE,前者用於除錯,後者預設使用,這裡傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯後自動建立Service,這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行。
現在讓我們重新執行一下程式吧,在MainActivity中點選一下Bind Service按鈕,LogCat裡的列印日誌如下圖所示:
這裡寫圖片描述

由於在繫結Service的時候指定的標誌位是BIND_AUTO_CREATE,說明點選Bind Service按鈕的時候Service也會被建立,這時應該怎麼銷燬Service呢?其實也很簡單,點選一下Unbind Service按鈕,將Activity和Service的關聯解除就可以了。Log如下:
這裡寫圖片描述

另外需要注意,任何一個Service在整個應用程式範圍內都是通用的,即MyService不僅可以和MainActivity建立關聯,還可以和任何一個Activity建立關聯,而且在建立關聯時它們都可以獲取到相同的MyBinder例項。

Service的銷燬

stopService/unbindService

在Service的啟動這一部分,我們已經簡單介紹了銷燬Service的方法。

  • startService—>stopService
  • bindService—>unbindService

以上這兩種銷燬的方式都很好理解。
但有幾點需要注意一下:
(1)你應當知道在呼叫 bindService 繫結到Service的時候,你就應當保證在某處呼叫 unbindService 解除繫結(儘管 Activity 被 finish 的時候繫結會自動解除,並且Service會自動停止);
(2)你應當注意使用 startService 啟動服務之後,一定要使用 stopService停止服務,不管你是否使用bindService;
(3)同時使用 startService 與 bindService 要注意到,Service 的終止,需要unbindService與stopService同時呼叫,才能終止 Service,不管 startService 與 bindService 的呼叫順序,如果先呼叫 unbindService 此時服務不會自動終止,再呼叫 stopService 之後服務才會停止,如果先呼叫 stopService 此時服務也不會終止,而再呼叫 unbindService 或者之前呼叫 bindService 的 Context 不存在了(如Activity 被 finish 的時候)之後服務才會自動停止;
(4)當在旋轉手機螢幕的時候,當手機螢幕在“橫”“豎”變換時,此時如果你的 Activity 如果會自動旋轉的話,旋轉其實是 Activity 的重新建立,因此旋轉之前的使用 bindService 建立的連線便會斷開(Context 不存在了),對應服務的生命週期與上述相同。
(5)unbindService 解除繫結,引數為之前建立的 ServiceConnection 介面物件。另外,多次呼叫 unbindService 來釋放相同的連線會丟擲異常,因此我建立了一個 boolean 變數來判斷是否 unbindService 已經被呼叫過。

stopSelf

對於StartService啟動的服務,Service本身還提供了另外一個方法讓自己停止—>stopSelf。
若系統正在處理多個呼叫onStartCommand()請求,那麼在啟動一個請求時,你不應當在此時停止該Service。為了避免這個問題,您可以呼叫stopSelf(int)方法,以確保請求停止的Service是最新的啟動請求。這就是說,當呼叫stopSelf(int)方法時,傳入的ID代表啟動請求(該ID會傳遞至onStartCommand()),該ID與請求停止的ID一致。則如果在呼叫stopSelf(int)之前,Service收到一個新的Start請求,ID將無法匹配,Service並不會停止。具體的例子參見上面Service啟動一節。

    ...
    private void initBackThread() {
        mThread = new HandlerThread("ServiceStartArguments");
        mThread.start();
        mHandler = new Handler(mThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                //執行緒操作
                MainActivity.showlog("processing...msg.arg1="+msg.arg1);
                try {
                    Thread.sleep(5000);
                } catch (Exception e) {
                    Thread.currentThread().interrupt();
                }
                MainActivity.showlog("stopSelf...msg.arg1="+msg.arg1);
                stopSelf(msg.arg1);
            }
        };
    }

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");

        Message msg = mHandler.obtainMessage();
        msg.arg1 = startId;
        mHandler.sendMessage(msg);

        return START_STICKY;
    }  
    ...

Service和Thread的區別

Thread我們大家都知道,是用於開啟一個子執行緒,在這裡去執行一些耗時操作就不會阻塞主執行緒的執行。而Service我們最初理解的時候,總會覺得它是用來處理一些後臺任務的,一些比較耗時的操作也可以放在這裡執行,這就會讓人產生混淆了。但是,如果我告訴你Service其實是執行在主執行緒裡的,你還會覺得它和Thread有什麼關係嗎?
在MainActivity的onCreate()方法里加入一行列印當前執行緒id的Log:

showlog("MainActivity thread id is " + Thread.currentThread().getId());

同時在MyService的onCreate()方法里加入列印當前執行緒id的Log:

MainActivity.showlog("MyService thread id is " + Thread.currentThread().getId());

現在重新執行一下程式,並點選Start Service按鈕,會看到如下Log資訊:
這裡寫圖片描述
可以看到,它們的執行緒id完全是一樣的,由此證實了Service確實是執行在主執行緒裡的,也就是說如果你在Service裡編寫了非常耗時的程式碼,程式也會出現ANR的。

下面我詳細的來解釋一下:

  • Thread:Thread 是程式執行的最小單元,它是分配CPU的基本單位。可以用 Thread 來執行一些非同步的操作。
  • Service:Service 是android的一種機制,當它執行的時候如果是Local Service,那麼對應的 Service 是執行在主程序的 main 執行緒上的。如:onCreate,onStart 這些函式在被系統呼叫的時候都是在主程序的 main 執行緒上執行的。如果是Remote Service,那麼對應的 Service 則是執行在獨立程序的 main 執行緒上。 因此請不要把 Service 理解成執行緒,它跟執行緒半毛錢的關係都沒有!

Android的後臺就是指,它的執行是完全不依賴UI的。即使Activity被銷燬,或者程式被關閉,只要程序還在,Service就可以繼續執行。比如說一些應用程式,始終需要與伺服器之間始終保持著心跳連線,就可以使用Service來實現。你可能又會問,前面不是剛剛驗證過Service是執行在主執行緒裡的麼?在這裡一直執行著心跳連線,難道就不會阻塞主執行緒的執行嗎?當然會,但是我們可以在Service中再建立一個子執行緒,然後在這裡去處理耗時邏輯就沒問題了。
既然在Service裡也要建立一個子執行緒,那為什麼不直接在Activity裡建立呢?這是因為Activity很難對Thread進行控制,當Activity被銷燬之後,就沒有任何其它的辦法可以再重新獲取到之前建立的子執行緒的例項。而且在一個Activity中建立的子執行緒,另一個Activity無法對其進行操作。但是Service就不同了,所有的Activity都可以與Service進行關聯,然後可以很方便地操作其中的方法,即使Activity被銷燬了,之後只要重新與Service建立關聯,就又能夠獲取到原有的Service中Binder的例項。因此,使用Service來處理後臺任務,Activity就可以放心地finish,完全不需要擔心無法對後臺任務進行控制的情況。你也可以在 Service 裡註冊 BroadcastReceiver,在其他地方通過傳送 broadcast 來控制它,當然這些都是 Thread 做不到的。
一個比較標準的Service就可以寫成:

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            // 開始執行後臺任務  
        }  
    }).start();  
    return super.onStartCommand(intent, flags, startId);  
}  

class MyBinder extends Binder {  

    public void startDownload() {  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                // 執行具體的下載任務  
            }  
        }).start();  
    }    
}  

建立前臺Service

Service幾乎都是在後臺執行的,一直以來它都是默默地做著辛苦的工作。但是Service的系統優先順序還是比較低的,當系統出現記憶體不足情況時,就有可能會回收掉正在後臺執行的Service。如果你希望Service可以一直保持執行狀態,而不會由於系統記憶體不足的原因導致被回收,就可以考慮使用前臺Service。前臺Service和普通Service最大的區別就在於,它會一直有一個正在執行的圖示在系統的狀態列顯示,下拉狀態列後可以看到更加詳細的資訊,非常類似於通知的效果,只有前臺Service被destroy後,狀態列顯示才能消失。當然有時候你也可能不僅僅是為了防止Service被回收才使用前臺Service,有些專案由於特殊的需求會要求必須使用前臺Service,比如說墨跡天氣,它的Service在後臺更新天氣資料的同時,還會在系統狀態列一直顯示當前天氣的資訊,如下圖所示:
這裡寫圖片描述

來看一下如何才能建立一個前臺Service吧,其實並不複雜,修改MyService中的程式碼,如下所示:

    @Override  
    public void onCreate() {  
        super.onCreate();
        MainActivity.showlog("onCreate()");
        initBackThread();

        Intent notificationIntent = new Intent(this, MainActivity.class);  
      /*第二個引數現在不再使用了
        第四個引數描述:
        FLAG_CANCEL_CURRENT:如果當前系統中已經存在一個相同的PendingIntent物件,那麼就將先將已有的PendingIntent取消,然後重新生成一個PendingIntent物件。
        FLAG_NO_CREATE:如果當前系統中存在相同的PendingIntent物件,系統將不會建立該PendingIntent物件而是直接返回null。
        FLAG_ONE_SHOT:該PendingIntent只作用一次。在該PendingIntent物件通過send()方法觸發過後,PendingIntent將自動呼叫cancel()進行銷燬,那麼如果你再呼叫send()方法的話,系統將會返回一個SendIntentException。
        FLAG_UPDATE_CURRENT:如果系統中有一個和你描述的PendingIntent對等的PendingInent,那麼系統將使用該PendingIntent物件,但是會使用新的Intent來更新之前PendingIntent中的Intent物件資料,例如更新Intent中的Extras*/
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);  
        Notification notification = new Notification.Builder(this)
            .setSmallIcon(R.drawable.ic_launcher)
            .setWhen(System.currentTimeMillis())
            .setTicker("有通知到來") 
            .setContentTitle("這是通知的標題") 
            .setContentText("這是通知的內容")
            .setOngoing(true)
            .setContentIntent(pendingIntent)
            .build();
        /*使用startForeground,如果id為0,那麼notification將不會顯示*/
        startForeground(1, notification);
    }

這裡只是修改了MyService中onCreate()方法的程式碼。可以看到,我們建立了一個Notification物件,然後設定了它的佈局和資料,並在這裡設定了點選通知後就開啟MainActivity。然後呼叫startForeground()方法就可以讓MyService變成一個前臺Service,並會將通知的圖片顯示出來。
現在重新執行一下程式,並點選Start Service或Bind Service按鈕,MyService就會以前臺Service的模式啟動了,並且在系統狀態列會彈出一個通欄圖示,下拉狀態列後可以看到通知的詳細內容,如下圖所示。
這裡寫圖片描述

可以呼叫stopForeground(Boolean bool)來移除前臺Service。該方法需傳入一個boolean型變數,表示是否也一併清除狀態列上的notification。該方法並不停止Service,如果停止正在前臺執行的Service,那麼notification 也會一併被清除。

最後我們看一下程序的分類:

  • 前臺程序 Foreground process
    • 當前使用者操作的Activity所在程序
    • 綁定了當前使用者操作的Activity的Service所在程序
    • 呼叫了startForeground()的Service 典型場景:後臺播放音樂
  • 可見程序 Visible process
    • 處於暫停狀態的Activity
    • 繫結到暫停狀態的Activity的Service
  • 服務程序 Service process
    • 通過startService()啟動的Service
  • 後臺程序 Background process
    • 處於停止狀態的Activity
  • 空程序 Empty process

遠端Service的使用

什麼是遠端Service

從上面可知,Service其實是執行在主執行緒裡的,如果直接在Service中處理一些耗時的邏輯,就會導致程式ANR。讓我們來驗證一下吧,修改MyService程式碼,在onCreate()方法中讓執行緒睡眠60秒,如下所示:

@Override  
    public void onCreate() {  
        super.onCreate();  
        MainActivity.showlog("onCreate()");

        try {  
            Thread.sleep(60000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        } 
    }  

點選一下Start Service按鈕或Bind Service按鈕,程式就會阻塞住無法進行任何其它操作,過一段時間後就會彈出ANR的提示框,如下圖所示:
這裡寫圖片描述

現在來看看遠端Service的用法,如果將MyService轉換成一個遠端Service,還會不會有ANR的情況呢?讓我們來動手嘗試一下吧。將一個普通的Service轉換成遠端Service其實非常簡單,只需要在註冊Service的時候將它的android:process屬性指定成:remote就可以了,程式碼如下所示:

<service android:name="com.hx.servicetest.MyService" 
         android:process=":remote"/>

重新執行程式,並點選一下Start Service按鈕,你會看到控制檯立刻列印了onCreate()的資訊,而且主介面並沒有阻塞住,也不會出現ANR。大概過了一分鐘後,又會看到onStartCommand()列印了出來。
為什麼將MyService轉換成遠端Service後就不會導致程式ANR了呢?這是由於,使用了遠端Service後,MyService已經在另外一個程序當中運行了,所以只會阻塞該程序中的主執行緒,並不會影響到當前的應用程式。

那既然遠端Service這麼好用,乾脆以後我們把所有的Service都轉換成遠端Service吧,還省得再開啟執行緒了。其實不然,遠端Service非但不好用,甚至可以稱得上是較為難用。一般情況下如果可以不使用遠端Service,就儘量不要使用它。
下面就來看一下它的弊端吧,首先將MyService的onCreate()方法中讓執行緒睡眠的程式碼去除掉,然後重新執行程式,並點選一下Bind Service按鈕,你會發現程式崩潰了!為什麼點選Start Service按鈕程式就不會崩潰,而點選Bind Service按鈕就會崩潰呢?這是由於在Bind Service按鈕的點選事件裡面我們會讓MainActivity和MyService建立關聯,但是目前MyService已經是一個遠端Service了,Activity和Service執行在兩個不同的程序當中,這時就不能再使用傳統的建立關聯的方式,程式也就崩潰了。

呼叫遠端Service

那麼如何才能讓Activity與一個遠端Service建立關聯呢?這就要使用AIDL來進行跨程序通訊了(IPC)。
AIDL(Android Interface Definition Language)是Android介面定義語言的意思,它可以用於讓某個Service與多個應用程式元件之間進行跨程序通訊,從而可以實現多個應用程式共享同一個Service的功能。
下面我們就來一步步地看一下AIDL的用法到底是怎樣的。首先需要新建一個AIDL檔案,在這個檔案中定義好Activity需要與Service進行通訊的方法。新建MyAIDLService.aidl檔案,程式碼如下所示:

package com.hx.servicetest;

interface MyAIDLService {  
    int plus(int a, int b);  
    String toUpperCase(String str);  
} 

點選儲存之後,在gen目錄下通過aapt就會生成一個對應的Java檔案,如下圖所示:
這裡寫圖片描述

然後我們寫一個MyRemoteService,在裡面實現我們剛剛定義好的MyAIDLService介面,如下所示:

public class MyRemoteService extends Service {

    @Override  
    public void onCreate() {  
        super.onCreate();  
        MainActivity.showlog("onCreate()");  
    }  

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");
        return super.onStartCommand(intent, flags, startId);  
    }  

    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        MainActivity.showlog("onDestroy()");  
    }  

    @Override  
    public IBinder onBind(Intent intent) {
        MainActivity.showlog("onBind()");
        return mBinder;  
    }  

    @Override  
    public boolean onUnbind(Intent intent) {  
        MainActivity.showlog("onUnbind()");  
        return super.onUnbind(intent);  
    } 

    MyAIDLService.Stub mBinder = new Stub() {         
        @Override  
        public String toUpperCase(String str) throws RemoteException {  
            if (str != null) {  
                return str.toUpperCase();  
            }  
            return null;  
        }  

        @Override  
        public int plus(int a, int b) throws RemoteException {  
            return a + b;  
        }  
    }; 
} 

這裡先是對MyAIDLService.Stub進行了實現,重寫裡了toUpperCase()和plus()這兩個方法。這兩個方法的作用分別是將一個字串全部轉換成大寫格式,以及將兩個傳入的整數進行相加。然後在onBind()方法中將MyAIDLService.Stub的實現返回。這裡為什麼可以這樣寫呢?因為Stub其實就是Binder的子類,所以在onBind()方法中可以直接返回Stub的實現。
接下來修改MainActivity中的程式碼,如下所示:

public class MainActivity extends Activity implements OnClickListener {  

    private Button startService;    
    private Button stopService; 
    private Button startIntentService;    
    private Button stopIntentService;
    private Button bindService;    
    private Button unbindService;    
    private Button bindRemoteService;

    private MyAIDLService myAIDLService;   
    private ServiceConnection connection = new ServiceConnection() {  
        @Override  
        public void onServiceDisconnected(ComponentName name) {}  

        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            myAIDLService = MyAIDLService.Stub.asInterface(service);  
            try {  
                int result = myAIDLService.plus(7, 8);  
                String upperStr = myAIDLService.toUpperCase("hello watson");  
                showlog("result is " + result);  
                showlog("upperStr is " + upperStr);  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
    }; 

    @Override  
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main);  
        startService = (Button) findViewById(R.id.start_service);  
        stopService = (Button) findViewById(R.id.stop_service);
        startIntentService = (Button) findViewById(R.id.start_intent_service);  
        stopIntentService = (Button) findViewById(R.id.stop_intent_service);
        bindService = (Button) findViewById(R.id.bind_service);  
        unbindService = (Button) findViewById(R.id.unbind_service); 
        bindRemoteService = (Button) findViewById(R.id.bind_remote_service);  
        startService.setOnClickListener(this);  
        stopService.setOnClickListener(this);  
        startIntentService.setOnClickListener(this);  
        stopIntentService.setOnClickListener(this);
        bindService.setOnClickListener(this);  
        unbindService.setOnClickListener(this);
        bindRemoteService.setOnClickListener(this);
    }  

    @Override  
    public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.start_service:
            showlog("click Start Service button");
            Intent it1 = new Intent(this, MyService.class);  
            startService(it1);  
            break;  
        case R.id.stop_service: 
            showlog("click Stop Service button"); 
            Intent it2 = new Intent(this, MyService.class);  
            stopService(it2);  
            break;  
        case R.id.start_intent_service:
            showlog("click Start IntentService button");
            Intent it3 = new Intent(this, MyIntentService.class);  
            startService(it3);  
            break;  
        case R.id.stop_intent_service: 
            showlog("click Stop IntentService button"); 
            Intent it4 = new Intent(this, MyIntentService.class);  
            stopService(it4);  
            break;  
        case R.id.bind_service:  
            showlog("click Bind Service button");
            Intent it5 = new Intent(this, MyBindService.class);  
            bindService(it5, connection, BIND_AUTO_CREATE);  
            break;  
        case R.id.unbind_service:
            showlog("click Unbind Service button"); 
            if(isConnected == true){  
                unbindService(connection);  
            }  
            break; 
        case R.id.bind_remote_service:
            showlog("click Bind Remote Service button");
            Intent it6 = new Intent(this, MyRemoteService.class);  
            bindService(it6, connection, BIND_AUTO_CREATE);
            break;
        default:  
            break;  
        }  
    }  

    public static void showlog(String inf