1. 程式人生 > >Android 四大元件之Service使用及特點

Android 四大元件之Service使用及特點

1. 生命週期 常用方法

  • 官方說明圖

示意圖

Service的生命週期裡,常用的有:

  • 4個手動呼叫的方法
手動呼叫方法 作用
startService() 啟動服務
stopService() 關閉服務
bindService() 繫結服務
unbindService() 解綁服務
  • 5個自動呼叫的方法
內部自動呼叫的方法 作用
onCreat() 建立服務
onStartCommand() 開始服務
onDestroy() 銷燬服務
onBind() 繫結服務
onUnbind() 解綁服務

Android Service兩種啟動方式

每一個 Service都需要在manifest中配置

第一種方式:通過StartService啟動Service

通過startService啟動後,記憶體富餘的情況下service會一直無限期執行下去,只有外部呼叫了stopService()或stopSelf()方法時,該Service才會停止執行並銷燬。

要建立一個這樣的Service,你需要讓該類繼承Service類,然後重寫以下方法:

  • onCreate()
    1.如果service沒被建立過,呼叫startService()後會執行onCreate()和onStartCommand()方法;
    2.如果service已處於執行中,呼叫startService()不會執行onCreate()方法,只執行onStartCommand()方法。
    也就是說,onCreate()只會在第一次建立service時候呼叫,多次執行startService()不會重複呼叫onCreate(),此方法適合完成一些初始化工作。

  • onStartCommand()
    如果多次執行了Context的startService()方法,那麼Service的onStartCommand()方法也會相應的多次呼叫。onStartCommand()方法很重要,我們在該方法中根據傳入的Intent引數進行實際的操作,比如會在此處建立一個執行緒用於下載資料或播放音樂等。

  • onBind()
    Service中的onBind()方法是抽象方法,Service類本身就是抽象類,所以onBind()方法是必須重寫的,即使我們用不到。

  • onDestroy()
    在銷燬的時候會執行Service的該方法。

這幾個方法都是回撥方法,且在主執行緒中執行,由android作業系統在合適的時機呼叫。

startService程式碼例項

建立TestOneService,並在manifest裡註冊。
在MainActivty中操作TestOneService,code如下:

public class TestOneService extends Service {
    @Override
    public void onCreate() {
        Log.i("-----------","onCreate - Thread ID = " + Thread.currentThread().getId());
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("-----------", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().getId());
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i("-----------", "onBind - Thread ID = " + Thread.currentThread().getId());
        return null;
    }

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

    @Override
    public void onDestroy() {
        Log.i("-----------", "onDestroy - Thread ID = " + Thread.currentThread().getId());
        super.onDestroy();
    }
}
//連續啟動Service
Intent intentOne = new Intent(this, TestOneService.class);
startService(intentOne);
Intent intentTwo = new Intent(this, TestOneService.class);
startService(intentTwo);
Intent intentThree = new Intent(this, TestOneService.class);
startService(intentThree);

//停止Service
Intent intentFour = new Intent(this, TestOneService.class);
stopService(intentFour);

//再次啟動Service
Intent intentFive = new Intent(this, TestOneService.class);
startService(intentFive);

在MainActivity中三次startService,之後stopService,再次呼叫startService

打印出的Log如下:

分析:
1.主執行緒打印出是1,所有回撥方法中打印出的執行執行緒ID都是1,證明回撥方法都是在主執行緒中執行的
2.三次呼叫startService,只觸發一次onCreate回撥,觸發了三次onStartCommand回撥,且startId分別為1,2,3。證明 多次startService不會重複執行onCreate回撥,但每次都會執行onStartCommand回撥

第二種方式:通過bindService啟動Service

bindService啟動服務特點:
1.bindService啟動的服務和呼叫者之間是典型的client-server模式。呼叫者是client,service則是server端。service只有一個,但繫結到service上面的client可以有一個或很多個。這裡所提到的client指的是元件,比如某個Activity。
2.client可以通過IBinder介面獲取Service例項,從而實現在client端直接呼叫Service中的方法以實現靈活互動,這在通過startService方法啟動中是無法實現的。
3.bindService啟動服務的生命週期與其繫結的client息息相關。當client銷燬時,client會自動與Service解除繫結(client會有ServiceConnectionLeaked異常,但程式不會崩潰)。當然,client也可以明確呼叫Context的unbindService()方法與Service解除繫結。當沒有任何client與Service繫結時,Service會自行銷燬

bindService程式碼例項

1.建立一個TestTwoService繼承Service(Server)
2.建立ActivityA,可以通過bindService繫結服務(client)
3.建立ActivityB,可以通過bindService繫結服務(client)
4.ActivityA可以跳轉到ActivityB

TestTwoService建立如下:
要想讓Service支援bindService呼叫方式,需要做以下事情:
1.在Service的onBind()方法中返回IBinder型別的例項。
2.onBInd()方法返回的IBinder的例項需要能夠返回Service例項本身。通常,最簡單的方法就是在service中建立binder的內部類,加入類似getService()的方法返回Service,這樣繫結的client就可以通過getService()方法獲得Service例項了。

public class TestTwoService extends Service{

    //client 可以通過Binder獲取Service例項
    public class MyBinder extends Binder {
        public TestTwoService getService() {
            return TestTwoService.this;
        }
    }

    //通過binder實現呼叫者client與Service之間的通訊
    private MyBinder binder = new MyBinder();

    private final Random generator = new Random();

    @Override
    public void onCreate() {
        Log.i("-------","TestTwoService - onCreate - Thread = " + Thread.currentThread().getName());
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("-------", "TestTwoService - onStartCommand - startId = " + startId + ", Thread = " + Thread.currentThread().getName());
        return START_NOT_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i("-------", "TestTwoService - onBind - Thread = " + Thread.currentThread().getName());
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i("-------", "TestTwoService - onUnbind - from = " + intent.getStringExtra("from"));
        return false;
    }

    @Override
    public void onDestroy() {
        Log.i("-------", "TestTwoService - onDestroy - Thread = " + Thread.currentThread().getName());
        super.onDestroy();
    }

    //getRandomNumber是Service暴露出去供client呼叫的公共方法
    public int getRandomNumber() {
        return generator.nextInt();
    }
}

client端要做的事情:
1.建立ServiceConnection型別例項,並重寫onServiceConnected()方法和onServiceDisconnected()方法。
2.當執行到onServiceConnected回撥時,可通過IBinder例項得到Service例項物件,這樣可實現client與Service的連線。
3.onServiceDisconnected回撥被執行時,表示client與Service斷開連線,在此可以寫一些斷開連線後需要做的處理。


建立ActivityA,程式碼如下:

public class ActivityA extends Activity implements Button.OnClickListener {
    private TestTwoService service = null;
    private boolean isBind = false;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            isBind = true;
            TestTwoService.MyBinder myBinder = (TestTwoService.MyBinder) binder;
            service = myBinder.getService();
            Log.i("----------", "ActivityA - onServiceConnected");
            int num = service.getRandomNumber();
            Log.i("----------", "ActivityA - getRandomNumber = " + num);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBind = false;
            Log.i("----------", "ActivityA - onServiceDisconnected");
        }
    };

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
        Log.i("----------", "ActivityA - onCreate - Thread = " + Thread.currentThread().getName());

        findViewById(R.id.btnBindService).setOnClickListener(this);
        findViewById(R.id.btnUnbindService).setOnClickListener(this);
        findViewById(R.id.btnStartActivityB).setOnClickListener(this);
        findViewById(R.id.btnFinish).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btnBindService) {
            //單擊了“bindService”按鈕
            Intent intent = new Intent(this, TestTwoService.class);
            intent.putExtra("from", "ActivityA");
            Log.i("----------", "----------------------------------------------------------------------");
            Log.i("----------", "ActivityA 執行 bindService");
            bindService(intent, conn, BIND_AUTO_CREATE);
        } else if (v.getId() == R.id.btnUnbindService) {
            //單擊了“unbindService”按鈕
            if (isBind) {
                Log.i("----------",
                        "----------------------------------------------------------------------");
                Log.i("----------", "ActivityA 執行 unbindService");
                unbindService(conn);
            }
        } else if (v.getId() == R.id.btnStartActivityB) {
            //單擊了“start ActivityB”按鈕
            Intent intent = new Intent(this, ActivityB.class);
            Log.i("----------",
                    "----------------------------------------------------------------------");
            Log.i("----------", "ActivityA 啟動 ActivityB");
            startActivity(intent);
        } else if (v.getId() == R.id.btnFinish) {
            //單擊了“Finish”按鈕
            Log.i("----------",
                    "----------------------------------------------------------------------");
            Log.i("----------", "ActivityA 執行 finish");
            this.finish();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i("----------", "ActivityA - onDestroy");
    }
}

建立ActivityB,程式碼如下:

public class ActivityB extends Activity implements Button.OnClickListener {

    private TestTwoService service = null;

    private boolean isBind = false;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            isBind = true;
            TestTwoService.MyBinder myBinder = (TestTwoService.MyBinder)binder;
            service = myBinder.getService();
            Log.i("----------", "ActivityB - onServiceConnected");
            int num = service.getRandomNumber();
            Log.i("----------", "ActivityB - getRandomNumber = " + num);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBind = false;
            Log.i("----------", "ActivityB - onServiceDisconnected");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);

        findViewById(R.id.btnBindService).setOnClickListener(this);
        findViewById(R.id.btnUnbindService).setOnClickListener(this);
        findViewById(R.id.btnFinish).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.btnBindService){
            //單擊了“bindService”按鈕
            Intent intent = new Intent(this, TestTwoService.class);
            intent.putExtra("from", "ActivityB");
            Log.i("----------", "----------------------------------------------------------------------");
            Log.i("----------", "ActivityB 執行 bindService");
            bindService(intent, conn, BIND_AUTO_CREATE);
        }else if(v.getId() == R.id.btnUnbindService){
            //單擊了“unbindService”按鈕
            if(isBind){
                Log.i("----------", "----------------------------------------------------------------------");
                Log.i("----------", "ActivityB 執行 unbindService");
                unbindService(conn);
            }
        }else if(v.getId() == R.id.btnFinish){
            //單擊了“Finish”按鈕
            Log.i("----------", "----------------------------------------------------------------------");
            Log.i("----------", "ActivityB 執行 finish");
            this.finish();
        }
    }
    @Override
    public void onDestroy(){
        super.onDestroy();
        Log.i("----------", "ActivityB - onDestroy");
    }
}

測試步驟1

step1: 點選ActivityA的bindService按鈕
step2: 再點選ActivityA的unbindService按鈕
Log輸出:

總結呼叫bindService之後發生的事情:
1.client執行bindService()
2.如果Service不存在,則Service執行onCreate(),onBind()
3.client例項ServiceConnection執行onServiceConnected()方法

4.如果是在同一個client連續多次呼叫bindService除第一次外其他繫結操作則沒有任何方法執行(因為已經是繫結狀態,不會重複繫結)

5.如果是在不同的client執行繫結操作,則先執行的那個client(activity)執行onCreate(),onBind(),onServiceConnected()後執行的那個client(activity)只執行onServiceConnected()

總結呼叫unbindService之後發生的事情:
1.client執行unbindService()
2.client與Service解除繫結連線狀態(activity與service解綁後,Service不一定會執行onUnbind()和onDestroy(),需要判斷)
3.Service檢測是否還有其他client與其連線,如果沒有Service執行onUnbind()和onDestroy()

注意:單client繫結狀態下activity直接銷燬,雖然service可以解綁,但是會有異常提示 ServiceConnectionLeaked,多client繫結狀態下,某一個activity銷燬只會發生ServiceConnectionLeaked異常,但是service不會解綁

測試步驟2

step1: 點選ActivityA的bindService按鈕
step2: 再點選ActivityA的Finish按鈕
Log 輸出:

總結:如果client銷燬,那麼client會自動與Service解除繫結。

測試步驟3

step1: 點選ActivityA的bindService按鈕
step2: 點選ActivityA的startActivity B按鈕,切換到ActivityB
step3: 點選ActivityB中的bindService按鈕
step4: 點選ActivityB中的unbindService按鈕
step5: 點選ActivityB中的Finish按鈕
step6: 點選ActivityA中的unbindService按鈕
得到Log:

總結bindService的生命週期:
1.點選ActivityA的bindService按鈕
第一次呼叫bindService會例項化TestTwoService,先onCreate()然後執行其onBind()方法,得到IBinder型別的例項,將其作為引數傳入ActivityA的ServiceConnection的onServiceConnected方法中,標誌著ActivityA與TestTwoService建立了繫結

2.點選ActivityB中的bindService按鈕
由於TestTwoService已處於執行狀態,所以再次呼叫bindService不會重新建立它的例項,所以也不會執行TestTwoService的onCreate()方法和onBind()方法。ActivityB與ActivityA共享IBinder例項。此時有兩個client與TestTwoService繫結

3.點選ActivityB中的unbindService按鈕
ActivityB與TestTwoService解除了繫結,當沒有任何client與Service繫結時,才會執行Service的onUnbind()方法。此時,ActivityA還在繫結連線中,所以不會執行Service的解綁方法

4.點選ActivityA中的unbindService按鈕
ActivityA執行unbindService之後,ActivityA與TestTwoService就解除綁定了,這樣就沒有client與TestTwoService繫結,這時候Android會銷燬TestTwoService,在銷燬前會先執行TestTwoService的onUnbind()方法,然後才會執行其onDestroy()方法,這樣TestService就銷燬了。

如何保證Service不被殺死?

1. onStartCommand方式中,返回START_STICKY

首先我們來看看onStartCommand都可以返回哪些值:

呼叫Context.startService方式啟動Service時,如果Android面臨記憶體匱乏,可能會銷燬當前執行的Service,待記憶體充足時可以重建Service。而Service被Android系統強制銷燬並再次重建的行為依賴於Service的onStartCommand()方法的返回值。

  • START_NOT_STICKY
    如果返回START_NOT_STICKY,表示當Service執行的程序被Android系統強制殺掉之後,不會重新建立該Service
    。當然如果在其被殺掉之後一段時間又呼叫了startService,那麼該Service又將被例項化。那什麼情境下返回該值比較恰當呢?
    如果我們某個Service執行的工作被中斷幾次無關緊要或者對Android記憶體緊張的情況下需要被殺掉且不會立即重新建立這種行為也可接受,那麼我們便可將 onStartCommand的返回值設定為START_NOT_STICKY。
    舉個例子,某個Service需要定時從伺服器獲取最新資料:通過一個定時器每隔指定的N分鐘讓定時器啟動Service去獲取服務端的最新資料。當執行到Service的onStartCommand時,在該方法內再規劃一個N分鐘後的定時器用於再次啟動該Service並開闢一個新的執行緒去執行網路操作。假設Service在從伺服器獲取最新資料的過程中被Android系統強制殺掉,Service不會再重新建立,這也沒關係,因為再過N分鐘定時器就會再次啟動該Service並重新獲取資料。

  • START_STICKY
    如果返回START_STICKY,表示Service執行的程序被Android系統強制殺掉之後,Android系統會將該Service依然設定為started狀態(即執行狀態),但是不再儲存onStartCommand方法傳入的intent物件,然後Android系統會嘗試再次重新建立該Service,並執行onStartCommand回撥方法,但是onStartCommand回撥方法的Intent引數為null,也就是onStartCommand方法雖然會執行但是獲取不到intent資訊。如果你的Service可以在任意時刻執行或結束都沒什麼問題,而且不需要intent資訊,那麼就可以在onStartCommand方法中返回START_STICKY,比如一個用來播放背景音樂功能的Service就適合返回該值。

  • START_REDELIVER_INTENT
    如果返回START_REDELIVER_INTENT,表示Service執行的程序被Android系統強制殺掉之後,與返回START_STICKY的情況類似,Android系統會將再次重新建立該Service,並執行onStartCommand回撥方法,但是不同的是,Android系統會再次將Service在被殺掉之前最後一次傳入onStartCommand方法中的Intent再次保留下來並再次傳入到重新建立後的Service的onStartCommand方法中,這樣我們就能讀取到intent引數。只要返回START_REDELIVER_INTENT,那麼onStartCommand重的intent一定不是null。如果我們的Service需要依賴具體的Intent才能執行(需要從Intent中讀取相關資料資訊等),並且在強制銷燬後有必要重新建立執行,那麼這樣的Service就適合返回START_REDELIVER_INTENT。

2.提高Service的優先順序
在AndroidManifest.xml檔案中對於intent-filter可以通過android:priority = "1000"這個屬性設定最高優先順序,1000是最高值,如果數字越小則優先順序越低,同時適用於廣播。

3.提升Service程序的優先順序

當系統程序空間緊張時,會依照優先順序自動進行程序的回收。
Android將程序分為6個等級,按照優先順序由高到低依次為:

  • 前臺程序foreground_app
  • 可視程序visible_app
  • 次要服務程序secondary_server
  • 後臺程序hiddena_app
  • 內容供應節點content_provider
  • 空程序empty_app
    可以使用startForeground將service放到前臺狀態,這樣低記憶體時,被殺死的概率會低一些。

4.在onDestroy方法裡重啟Service
當service走到onDestroy()時,傳送一個自定義廣播,當收到廣播時,重新啟動service。

5.系統廣播監聽Service狀態
6.將APK安裝到/system/app,變身為系統級應用