Service完全解析

Service完全解析


1.Service生命週期

兩種啓動Service的模式:
1. context.startService()
當我們首次使用startService啓動一個服務時,系統會實例化一個Service實例,依次調用其onCreate和onStartCommand方法,然後進入運行狀態,此後,如果再使用startService啓動服務時,不再創建新的服務對象,系統會自動找到剛纔創建的Service實例,調用其onStart方法;如果我們想要停掉一個服務,可使用stopService方法,此時onDestroy方法會被調用,需要注意的是,不管前面使用了多個次startService,只需一次stopService,即可停掉服務。
2. context.bindService()
在這種模式下,當調用者首次使用bindService綁定一個服務時,系統會實例化一個Service實例,並一次調用其onCreate方法和onBind方法,然後調用者就可以和服務進行交互了,此後,如果再次使用bindService綁定服務,系統不會創建新的Service實例,也不會再調用onBind方法;如果我們需要解除與這個服務的綁定,可使用unbindService方法,此時onUnbind方法和onDestroy方法會被調用。

兩種模式有以下幾點不同之處:startService模式下調用者與服務無必然聯繫,即使調用者結束了自己的生命週期,只要沒有使用stopService方法停止這個服務,服務仍會運行;通常情況下,bindService模式下服務是與調用者生死與共的,在綁定結束之後,一旦調用者被銷燬,服務也就立即終止,就像江湖上的一句話:不求同生,但願同死。

以上兩種模式的流程如下圖所示:
生命週期

下面我們就結合實例來演示一下這兩種模式的生命週期過程。我們新建一個名爲service的項目,然後創建一個MyService的服務類,代碼如下:

package com.scott.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

    private static final String TAG = "MyService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate called.");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand called.");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        Log.i(TAG, "onStart called.");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind called.");
        return null;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "onUnbind called.");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy called.");
    }
}

然後在AndroidManifest.xml中配置服務信息:

<service android:name=".MyService">
            <intent-filter>
                 <action android:name="android.intent.action.MyService" />
                 <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

如果服務只是在本應用中使用,大可以去掉< intent-filter>屬性。

服務搭建完成之後,我們就來關注一下調用者MainActivity,它很簡單,只有兩個按鈕,一個是啓動服務,另一個是停止服務,我們來看一下他們的點擊事件:

/**
     * 啓動服務
     * @param view
     */
    public void start(View view) {
        Intent intent = new Intent(this, MyService.class);
        startService(intent);
    }

    /**
     * 停止服務
     * @param view
     */
    public void stop(View view) {
        Intent intent = new Intent(this, MyService.class);
        stopService(intent);
    }

接下來我們就先點擊一次啓動按鈕,看看都發生了些什麼。日誌打印結果如下:這裏寫圖片描述

當然我們覺得還不過癮,再點擊一次,我們會發現結果略有不同:
這裏寫圖片描述

我們看到第二次點擊時onCreate方法就不再被調用了,而是直接調用了onStartCommand方法(onStartCommand中又調用了onStart方法)。我們選擇“Settings->Application s->Running services”就會發現我們剛剛啓動的服務:
這裏寫圖片描述

然後我們點擊停止按鈕,試圖停止服務,我們發現如下現象:
這裏寫圖片描述

我們會發現onDestroy方法被調用了,此時服務就停止運行了。我們再次查看“Running services”,就會發現MyService這個服務已全無蹤跡。
在這個過程中,onBind方法和onUnbind方法始終沒被調用,我們下面就讓這兩位show一下自己。

我們修改一下MainActivity的代碼,使其可以可以以bindService的方式啓動一個服務,代碼如下:

private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //connected
            Log.i(TAG, "onServiceConnected called.");
        }

        /**
         *  Called when a connection to the Service has been lost.
         *  This typically happens when the process hosting the service has crashed or been killed.
         *  This does not remove the ServiceConnection itself.
         *  this binding to the service will remain active,
         *  and you will receive a call to onServiceConnected when the Service is next running.
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    /**
     * 綁定服務
     * @param view
     */
    public void bind(View view) {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    /**
     * 解除綁定
     * @param view
     */
    public void unbind(View view) {
        unbindService(conn);
    }

在使用bindService綁定服務時,我們需要一個ServiceConnection代表與服務的連接,它只有兩個方法,onServiceConnected和onServiceDisconnected,前者是在操作者在連接一個服務成功時被調用,而後者是在服務崩潰或被殺死導致的連接中斷時被調用,而如果我們自己解除綁定時則不會被調用,所以我們這裏只研究onServiceConnected這個方法。
看樣子是可以去綁定一個服務了,其實還不行,因爲我們前面服務中的onBind方法返回值爲null,這樣是不行的,要想實現綁定操作,必須返回一個實現了IBinder接口類型的實例,該接口描述了與遠程對象進行交互的抽象協議,有了它我們才能與服務進行交互。我們於是有了這樣的代碼:

@Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind called.");
        return new Binder() {};
    }

我們返回了一個Binder的實例,而這個Binder恰恰是實現了IBinder接口,所以這樣就可以實現綁定服務的操作了,一起來演示一下。
先點擊一下綁定按鈕,我們會發現在MainActivity中打印日誌如下:
這裏寫圖片描述

是的,onServiceConnected方法被調用了,看來綁定連接已經成功了,看看MyService如何:
這裏寫圖片描述

onCreate方法和onBind方法被調用了,此時服務已進入運行階段,如果再次點擊綁定按鈕,onCreate和onBinder並不會再次被調用,這個過程中它們僅被調用一次。
然後點擊解除綁定按鈕,我們會發現MyService打印如下:
這裏寫圖片描述

可以看到onUnbind方法和onDestroy方法被調用了,此時MyService已被銷燬,整個生命週期結束。
另一方面,當我們退出MainActivity時,服務也會隨之而結束,從這一點上看,MyService可以說是誓死追隨着MainActivity。
需要注意的是,在連接中斷狀態再去做解除綁定操作會引起一個異常,在MainActivity銷燬之前沒有進行解除綁定也會導致後臺出現異常信息,此時我們就要想辦法確保不會出現此類情況,可以這樣做:

private boolean binded;

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        binded = true;
    }

    /**
     * 解除綁定
     * @param view
     */
    public void unbind(View view) {
        unbindService();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService();
    }

    /**
     * 解除服務綁定
     */
    private void unbindService() {
        if (binded) {
            unbindService(conn);
            binded = false;
        }
    }

以上就是bindService的生命週期,正如我們上面講的一樣,使用bindService啓動服務後調用者和服務綁定到了一起,當調用者被銷燬,服務也立即結終止。
通常情況下是這樣的,不過也有特殊情況。當startService和bindService在同一場合下使用時,就會出現稍微不同的現象。
如果我們先以startService方式啓動服務,然後再用bindService綁定到這個服務,之後使用unbindService解除綁定,此時服務並不會因此而終止,而是繼續運行,直到我們使用stopService來停止這個服務。下面我們再修改一下代碼以驗證這個過程。MyService保持不變,我們只需修改一下MainActivity。MainActivity最新代碼如下:

package com.scott.service;

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.util.Log;
import android.view.View;

public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected called.");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    /**
     * 啓動服務
     * @param view
     */
    public void start(View view) {
        Intent intent = new Intent(this, MyService.class);
        startService(intent);
    }

    /**
     * 綁定服務
     * @param view
     */
    public void bind(View view) {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    /**
     * 解除綁定
     * @param view
     */
    public void unbind(View view) {
        unbindService(conn);
    }

    /**
     * 停止服務
     * @param view
     */
    public void stop(View view) {
        Intent intent = new Intent(this, MyService.class);
        stopService(intent);
    }
}

在MainActivity中包含了四個按鈕事件,分別是startService、bindService、unbindService和stopService,我們逐一地按下,看看都發生了什麼。
首先按下啓動服務的按鈕,MyService打印如下:
這裏寫圖片描述

恩,意料之中。然後我們再按下綁定服務的按鈕,MyService打印如下:
這裏寫圖片描述
此時,只有onBind被調用,之後兩者就綁定成功。我們再按下解除綁定的按鈕,MyService打印如下:
這裏寫圖片描述

此時,onUnbind方法方法被調用,注意,此時MyService並沒有因解除綁定而終止,而是繼續運行。也許我們心裏會問,如果多次按下綁定服務的按鈕或重複以上兩個步驟,結果如何呢?答案是onBind和onUnbind都不會再被調用了。看不到onBind被調用,是不是沒有綁定成功啊,我們來看一下MainActivity打印信息:
這裏寫圖片描述

重複按下綁定按鈕,幾次都綁定成功了。最後我們按下停止服務的按鈕,MyService打印如下:
這裏寫圖片描述

此時,onDestroy被調用了,此時MyService停止了運行,整個生命週期結束。
以上就是關於MyService生命週期的講解,下面我們來介紹一下如何與服務進行通信。與服務之間的通信可以分爲兩種,進程內的通信和進程間的通信,前者調用者和服務在同一應用進程內,而後者是分佈在不同應用進程中的。


2.進程內與服務通信

進程內與服務通信實際上就是通過bindService的方式與服務綁定,獲取到通信中介Binder實例,然後通過調用這個實例的方法,完成對服務的各種操作。我們上面也介紹了不少關於bindService的內容,下面我們就針對實際需求對代碼做改動。首先是MyService,代碼如下:

package com.scott.service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

    private static final String TAG = "MyService";

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind called.");
        return new MyBinder();
    }

    /**
     * 綁定對象
     * @author user
     *
     */
    public class MyBinder extends Binder {

        /**
         * 問候
         * @param name
         */
        public void greet(String name) {
            Log.i(TAG, "hello, " + name);
        }
    }
}

我們創建了一個MyBinder的內部類,定義了一個greet方法,在onBind方法中就將這個MyBinder的實例返回,只要調用者獲取到這個實例,就可以像拿着遊戲手柄一樣對服務進行操作。我們來看一下調用者的代碼吧,MainActivity代碼如下:

package com.scott.service;

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.view.View;

public class MainActivity extends Activity {

    /**
     * 綁定對象實例
     */
    private MyService.MyBinder binder;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            binder = (MyService.MyBinder) service;  //獲取其實例
            binder.greet("scott");                  //調用其方法
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    /**
     * 綁定服務
     * @param view
     */
    public void bind(View view) {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    /**
     * 解除綁定
     * @param view
     */
    public void unbind(View view) {
        unbindService(conn);
    }
}

在上面的代碼中,我們是在綁定服務成功時將IBinder類型的service參數強轉爲MyService.MyBinder類型,獲取綁定中介實例,然後調用其greet方法。
操作一下,看看效果如何。先點擊綁定服務的按鈕,MyService打印如下:
這裏寫圖片描述

需要注意的是,與服務綁定是一個異步的過程,也就是說,在這一刻我們綁定服務,下一刻我們去操作binder對象,也許它還爲null,這就容易引起空指針異常,正確的做法是把這些操作放到綁定成功之後,確保萬無一失。
以上就是進程內通信的內容。


3.Service和Thread的關係

Service和Thread到底有什麼關係呢?什麼時候應該用Service,什麼時候又應該用Thread?答案可能會有點讓你吃驚,因爲Service和Thread之間沒有任何關係!

之所以有不少人會把它們聯繫起來,主要就是因爲Service的後臺概念。Thread我們大家都知道,是用於開啓一個子線程,在這裏去執行一些耗時操作就不會阻塞主線程的運行。而Service我們最初理解的時候,總會覺得它是用來處理一些後臺任務的,一些比較耗時的操作也可以放在這裏運行,這就會讓人產生混淆了。但是,如果我告訴你Service其實是運行在主線程裏的,你還會覺得它和Thread有什麼關係嗎?讓我們看一下這個殘酷的事實吧。
在MainActivity的onCreate()方法里加入一行打印當前線程id的語句:

Log.d("MyService", "MainActivity thread id is " + Thread.currentThread().getId());  

然後在MyService的onCreate()方法裏也加入一行打印當前線程id的語句:

Log.d("MyService", "MyService thread id is " + Thread.currentThread().getId()); 

現在重新運行一下程序,並點擊Start Service按鈕,會看到如下打印日誌的結果竟然是用一個線程id。

可以看出,它們的線程id完全是一樣的,由此證實了Service確實是運行在主線程裏的,也就是說如果你在Service裏編寫了非常耗時的代碼,程序必定會出現ANR的。

那我要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,完全不需要擔心無法對後臺任務進行控制的情況。


4.前臺Service和普通Service

Service幾乎都是在後臺運行的,一直以來它都是默默地做着辛苦的工作。但是Service的系統優先級還是比較低的,當系統出現內存不足情況時,就有可能會回收掉正在後臺運行的Service。如果你希望Service可以一直保持運行狀態,而不會由於系統內存不足的原因導致被回收,就可以考慮使用前臺Service。前臺Service和普通Service最大的區別就在於,它會一直有一個正在運行的圖標在系統的狀態欄顯示,下拉狀態欄後可以看到更加詳細的信息,非常類似於通知的效果。當然有時候你也可能不僅僅是爲了防止Service被回收才使用前臺Service,有些項目由於特殊的需求會要求必須使用前臺Service,比如說墨跡天氣,它的Service在後臺更新天氣數據的同時,還會在系統狀態欄一直顯示當前天氣的信息,如下圖所示:
這裏寫圖片描述

那麼我們就來看一下如何才能創建一個前臺Service吧,其實並不複雜,修改MyService中的代碼,如下所示:

public class MyService extends Service {  

    public static final String TAG = "MyService";  

    private MyBinder mBinder = new MyBinder();  

    @Override  
    public void onCreate() {  
        super.onCreate();  
        Notification notification = new Notification(R.drawable.ic_launcher,  
                "有通知到來", System.currentTimeMillis());  
        Intent notificationIntent = new Intent(this, MainActivity.class);  
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,  
                notificationIntent, 0);  
        notification.setLatestEventInfo(this, "這是通知的標題", "這是通知的內容",  
                pendingIntent);  
        startForeground(1, notification);  
        Log.d(TAG, "onCreate() executed");  
    }  

    .........  

}  

這裏只是修改了MyService中onCreate()方法的代碼。可以看到,我們首先創建了一個Notification對象,然後調用了它的setLatestEventInfo()方法來爲通知初始化佈局和數據,並在這裏設置了點擊通知後就打開MainActivity。然後調用startForeground()方法就可以讓MyService變成一個前臺Service,並會將通知的圖片顯示出來。
現在重新運行一下程序,並點擊Start Service或Bind Service按鈕,MyService就會以前臺Service的模式啓動了,並且在系統狀態欄會彈出一個通欄圖標,下拉狀態欄後可以看到通知的詳細內容,如下圖所示。
這裏寫圖片描述


5.IntentService

IntentService是Service類的子類,用來處理異步請求。客戶端可以通過startService(Intent)方法傳遞請求給IntentService。IntentService在onCreate()函數中通過HandlerThread單獨開啓一個線程來處理所有Intent請求對象(通過startService的方式發送過來的)所對應的任務,這樣以免事務處理阻塞主線程。執行完所一個Intent請求對象所對應的工作之後,如果沒有新的Intent請求達到,則自動停止Service;否則執行下一個Intent請求所對應的任務。
IntentService在處理事務時,還是採用的Handler方式,創建一個名叫ServiceHandler的內部Handler,並把它直接綁定到HandlerThread所對應的子線程。 ServiceHandler把處理一個intent所對應的事務都封裝到叫做onHandleIntent的虛函數;因此我們直接實現虛函數onHandleIntent,再在裏面根據Intent的不同進行不同的事務處理就可以了。
另外,IntentService默認實現了Onbind()方法,返回值爲null。
  使用IntentService需要兩個步驟:
  1、寫構造函數
  2、實現虛函數onHandleIntent,並在裏面根據Intent的不同進行不同的事務處理就可以了。
好處:處理異步請求的時候可以減少寫代碼的工作量,比較輕鬆地實現項目的需求
注意:IntentService的構造函數一定是參數爲空的構造函數,然後再在其中調用super(“name”)這種形式的構造函數。
因爲Service的實例化是系統來完成的,而且系統是用參數爲空的構造函數來實例化Service的。

IntentService有何優點呢:
- 普通的service ,默認運行在ui main 主線程
- 這是帶有異步處理的service類,
- 異步處理的方法 OnHandleIntent()
- OnHandleIntent() 處理耗時的操作
- Android的進程處理器現在會儘可能的不kill掉你

package com.lenovo.robin.test;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;

public class MyIntentService extends IntentService {
final static String TAG="robin";
 public MyIntentService() {
  super("com.lenovo.robin.test.MyIntentService");
  Log.i(TAG,this+" is constructed");
 }
 @Override
 protected void onHandleIntent(Intent arg0) {
  Log.i(TAG,"begin onHandleIntent() in "+this);
  try {
   Thread.sleep(10*1000);
  } catch (InterruptedException e) {
     e.printStackTrace();
  }
  Log.i(TAG,"end onHandleIntent() in "+this);
 }
 public void onDestroy()
 {
  super.onDestroy();
  Log.i(TAG,this+" is destroy");
 }
}

啓動MyIntentServic的代碼片段

   Intent intent=new Intent(this,MyIntentService.class);
   startService(intent);
   startService(intent);
   startService(intent);

AndroidManifest.xml文件代碼片段:

<service android:name=".MyIntentService" />

運行結果:

運行結果
09-14 22:23:34.730: I/robin(30943): com.lenovo.robin.test.MyIntentService@40541370 is constructed
09-14 22:23:34.730: I/robin(30943): begin onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:23:44.730: I/robin(30943): end onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:23:44.730: I/robin(30943): begin onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:23:54.740: I/robin(30943): end onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:23:54.740: I/robin(30943): begin onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:24:04.739: I/robin(30943): end onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:24:04.739: I/robin(30943): com.lenovo.robin.test.MyIntentService@40541370 is destroy

參考了很多資料,在此感謝他們的付出!
http://blog.csdn.net/liuhe688/article/details/6874378
http://blog.csdn.net/hudashi/article/details/7986130

向前走 就這麼走 wman !

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章