Android開發系列4——Service詳解

前言

  Service是Android的四大組件之一,在Android開發過程中是一個必不可少的組件。

  Service是一種可在後臺執行長時間運行操作而不提供界面的應用組件。Service可以由Activity、Context等多種組件啓動,而且還可以通過其他應用組件啓動,而且即使用戶切換到其他應用,Activity頁面切換,Service一直都會保持在後臺運行服務。此外,組件可以通過綁定到服務與之進行交互,甚至是執行進程間通信(IPC)。例如,服務可在後臺處理網絡事務、播放音樂,執行文件 I/O 或與內容提供程序進行交互。

一、Service的生命週期

  Service的生命週期比Activity的生命週期簡單,但是需要注意Service幾個狀態(創建、銷燬等),因爲Service一直處於後臺,避免一直在後臺運行。

Service的生命週期從創建到銷燬遵循兩種方式:

  • 啓動服務
    啓動服務在其他組件調用startService()時創建,然後無限期運行,必須通過調用stopSelf()來自行停止運行,或者是通過其他組件通過調用stopService() 來停止服務。服務停止後,系統會將其銷燬。

  • 綁定服務
    該服務在其他組件(客戶端)調用 bindService() 時創建。然後,客戶端通過 IBinder 接口與服務進行通信。客戶端可通過調用 unbindService() 關閉連接。多個客戶端可以綁定到相同服務,而且當所有綁定全部取消後,系統即會銷燬該服務。(服務不必自行停止運行。)

Service對應的兩種啓動方式的生命週期如下所示:

Service生命週期
從上邊的圖上可以簡單的瞭解到Service的生命週期的每個階段,接下來會說明一下每個階段對應的功能,在每個階段怎麼使用。

回調 描述
onCreate() 當服務通過onStartCommand()和onBind()被第一次創建的時候,系統調用該方法。如果service已處於運行中,調用startService()不會執行onCreate()方法。也就是說,onCreate()只會在第一次創建service時候調用,多次執行startService()不會重複調用onCreate(),此方法適合完成一些初始化工作
onStartCommand() 其他組件(如活動)通過調用startService()來請求啓動服務時,系統調用該方法。如果多次執行了Context的startService()方法,那麼Service的onStartCommand()方法也會相應的多次調用
onBind 當其他組件想要通過bindService()來綁定服務時,系統調用該方法。如果你實現該方法,你需要返回IBinder對象來提供一個接口,以便客戶來與服務通信。你必須實現該方法,如果你不允許綁定,則直接返回null。
onUnbind() 當客戶中斷所有服務發佈的特殊接口時,系統調用該方法。
onRebind() 當新的客戶端與服務連接,且此前它已經通過onUnbind(Intent)通知斷開連接時,系統調用該方法。
onDestroy() 當服務不再有用或者被銷燬時,系統調用該方法。你的服務需要實現該方法來清理任何資源,如線程,已註冊的監聽器,接收器等。

二、Service的應用

不管是哪一種的 service ,也都需要在 AndroidManifest.xml中聲明,所以需要把自定義的Service配置如下:

<service android:name=".MyService"
            android:enabled="true"
            android:exported="true"
            android:icon="@drawable/background_blue"
            android:label="string"
            android:process="string"
            android:permission="string">
 </service>
名稱 介紹
android:exported 表示是否允許除了當前程序之外的其他程序訪問這個服務
android:enabled 表示是否啓用這個服務
android:permission 是權限聲明
android:process 是否需要在單獨的進程中運行,當設置爲android:process=”:remote”時,代表Service在單獨的進程中運行。注意“:”很重要,它的意思是指要在當前進程名稱前面附加上當前的包名,所以“remote”和”:remote”不是同一個意思,前者的進程名稱爲:remote,而後者的進程名稱爲:App-packageName:remote。
android:isolatedProcess 設置 true 意味着,服務會在一個特殊的進程下運行,這個進程與系統其他進程分開且沒有自己的權限。與其通信的唯一途徑是通過服務的API(bind and start)。

1.啓動服務

重寫一個新的Service子類MyService,重寫對應的方法。(其中IBinder onBind(Intent intent)方法並沒有實現任何方法,但必須重寫。)接下來用一個簡單的例子來呈現啓動服務

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

public class MyService extends Service {

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

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

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

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

Activity中通過啓動MyService–>銷燬MyService,輸出在整個Service的運行過程的流程。

public class MainActivity extends AppCompatActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.i("Wds", "Thread ID = " + Thread.currentThread().getId());
        Log.i("Wds", "before StartService");

        //連續啓動Service
        Intent intentOne = new Intent(this, MyService.class);
        startService(intentOne);
        Intent intentTwo = new Intent(this, MyService.class);
        startService(intentTwo);
        Intent intentThree = new Intent(this, MyService.class);
        startService(intentThree);

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

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

        Log.i("Wds", "after StartService");
    }
}

Activity在運行中打印出來Service的Log輸出:

I/Wds: Thread ID = 1
I/Wds: before StartService
I/Wds: after StartService
I/Wds: onCreate - Thread ID = 1
I/Wds: onStartCommand - startId = 1, Thread ID = 1
I/Wds: onStartCommand - startId = 2, Thread ID = 1
I/Wds: onStartCommand - startId = 3, Thread ID = 1
I/Wds: onDestroy - Thread ID = 1
I/Wds: onCreate - Thread ID = 1
I/Wds: onStartCommand - startId = 1, Thread ID = 1

從上述的輸出可以看到:

  • 1.Thread ID = 1說明是主線程,
  • 2.Service在通過Activity啓動,Service的運行在主線程上。
  • 3.通過多次調用startService,onCreate只調用一次。每次調用startService都會再次回調onStartCommand方法,多次調用startService生成的startId是不同的Id值。

所以,在使用Service過程中,運行Service過程需要創建一個子線程,把需要的操作放在子線程中執行(避免主線程的堵塞)。

2.綁定服務

綁定服務(client-server模式):

  • 調用者是client
  • service則是server端

綁定服務有一些特徵:

  • 1.service只有一個,但綁定到service上面的client可以有一個或很多個。這裏所提到的client指的是組件,比如某個Activity。
  • 2.client可以通過IBinder接口獲取Service實例,從而實現在client端直接調用Service中的方法以實現靈活交互,這在通過startService方法啓動中是無法實現的。
  • 3.bindService啓動服務的生命週期與其綁定的client息息相關。當client銷燬時,client會自動與Service解除綁定。當然,client也可以明確調用Context的unbindService()方法與Service解除綁定。當沒有任何client與Service綁定時,Service會自行銷燬。

接下來會用一些例子詳細說明綁定服務的特徵:

BindService的對象

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

public class BindService extends Service {

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

    //通過binder實現調用者client與Service之間的通信
    private MyBinder binder = new MyBinder();

    private final Random generator = new Random();

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

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

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

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

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

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

**Client對象 A **

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private BindService BBservice = null;
    private boolean isBind = false;
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            isBind = true;
            BindService.MyBinder myBinder = (BindService.MyBinder) service;

            BBservice = myBinder.getService();
            Log.i("Wds", "MainActivity - onServiceConnected");

            int num = BBservice.getRandomNumber();
            Log.i("Wds", "MainActivity - getRandomNumber = " + num);
        }

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


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


        findViewById(R.id.nextBtn).setOnClickListener(this);
        findViewById(R.id.bindService).setOnClickListener(this);
        findViewById(R.id.unBindService).setOnClickListener(this);
        findViewById(R.id.finishBtn).setOnClickListener(this);

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

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.bindService){
            //單擊了“bindService”按鈕
            Intent intent = new Intent(this, BindService.class);

            intent.putExtra("from", "MainActivity");
            Log.i("Wds", "----------------------------------------------------------------------");
            Log.i("Wds", "MainActivity 執行 bindService");
            bindService(intent, conn, BIND_AUTO_CREATE);

        } else if (v.getId() == R.id.unBindService){

            if (isBind) {
                Log.i("Wds",
                        "----------------------------------------------------------------------");
                Log.i("Wds", "MainActivity 執行 unbind Service");
                unbindService(conn);
            }

        } else if (v.getId() == R.id.finishBtn){

            //單擊了“Finish”按鈕
            Log.i("Wds",
                    "----------------------------------------------------------------------");
            Log.i("Wds", "MainActivity 執行 finish");
            this.finish();

        } else{
            Log.i("Wds",
                    "----------------------------------------------------------------------");
            Log.i("Wds", "MainActivity 啓動 NextActivity");
            //下一個頁面
            Intent intent = new Intent(MainActivity.this,NextActivity.class);
            startActivity(intent);
        }
    }
}

Client對象的頁面XML文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:id="@+id/root"
    >

    <Button
        android:id="@+id/nextBtn"
        android:layout_width="200sp"

        android:layout_height="50sp"
        android:layout_marginStart="59dp"
        android:layout_marginTop="20dp"
        android:onClick="onClickNextBtn"
        android:text="@string/MainBtn"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/bindService"

        android:layout_marginStart="100dp"
        android:layout_marginTop="100dp"
        android:text="Bind Service"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ></Button>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/unBindService"

        android:layout_marginStart="100dp"
        android:layout_marginTop="180dp"
        android:text="unBind Service"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"

        ></Button>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/finishBtn"

        android:layout_marginStart="100dp"
        android:layout_marginTop="260dp"
        android:text="Finish"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"

        ></Button>
</androidx.constraintlayout.widget.ConstraintLayout>

測試情況1:

  • 1.點擊MainActivity的bindService按鈕
  • 2.點擊MainActivity的unbindService按鈕

執行出來的log

I/Wds: ----------------------------------------------------------------------
I/Wds: MainActivity 執行 bindService
I/Wds: BindService - onCreate - Thread = main
I/Wds: BindService - onBind - Thread = main
I/Wds: MainActivity - onServiceConnected
I/Wds: MainActivity - getRandomNumber = -2070009866
I/Wds: ----------------------------------------------------------------------
I/Wds: MainActivity 執行 unbind Service
I/Wds: BindService - onUnbind - from = MainActivity
I/Wds: BindService - onDestroy - Thread = main

從結果打印出來的log可以得出:

  • 1.Client執行bindService(), 如果Service不存在,則Service執行onCreate(),onBind().
  • 2.Client實例ServiceConnection執行onServiceConnected()方法。
  • 3.Client實例解除綁定,Service檢測是否還有其他client與其連接,如果沒有Service執行onUnbind()和onDestroy()

測試情況2:

  • 1.點擊MainActivity 綁定bindService按鈕
  • 2.點擊MainActivity的Finish按鈕

執行出來的log

I/Wds: ----------------------------------------------------------------------
I/Wds:MainActivity 執行 bindService
I/Wds: BindService - onCreate - Thread = main
I/Wds: BindService - onBind - Thread = main
I/Wds: MainActivity - onServiceConnected
I/Wds:MainActivity - getRandomNumber = 9346330
I/Wds: ----------------------------------------------------------------------
I/Wds: MainActivity 執行 finish
I/Wds:-----------Main-------onDestroy------------------- 
I/Wds: BindService - onUnbind - from = MainActivity
I/Wds: BindService - onDestroy - Thread = main

從結果打印出來的log可以得出:

  • 如果Client銷燬,那麼Client上的ServiceConnection被銷燬,ServiceConnection會自動與Service解除綁定

Client B的文件

public class NextActivity extends AppCompatActivity implements View.OnClickListener{

    private BindService BBservice = null;
    private boolean isBind = false;
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            isBind = true;
            BindService.MyBinder myBinder = (BindService.MyBinder) service;

            BBservice = myBinder.getService();
            Log.i("Wds", "NextActivity - onServiceConnected");

            int num = BBservice.getRandomNumber();
            Log.i("Wds", "NextActivity - getRandomNumber = " + num);
        }

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

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


        findViewById(R.id.bindService).setOnClickListener(this);
        findViewById(R.id.unBindService).setOnClickListener(this);
        findViewById(R.id.finishBtn).setOnClickListener(this);

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

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.bindService){
            //單擊了“bindService”按鈕
            Intent intent = new Intent(this, BindService.class);

            intent.putExtra("from", "NextActivity");
            Log.i("Wds", "----------------------------------------------------------------------");
            Log.i("Wds", "NextActivity 執行 bindService");
            bindService(intent, conn, BIND_AUTO_CREATE);

        } else if (v.getId() == R.id.unBindService){

            if (isBind) {
                Log.i("Wds",
                        "----------------------------------------------------------------------");
                Log.i("Wds", "NextActivity 執行 unbind Service");
                unbindService(conn);
            }

        } else{

             //單擊了“Finish”按鈕
            Log.i("Wds",
                    "----------------------------------------------------------------------");
            Log.i("Wds", "NextActivity 執行 finish");
            this.finish();
        }

    }
}

Client B頁面XML文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".NextActivity"
    android:id="@+id/nextActivity"
    >


    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/bindService"

        android:layout_marginStart="100dp"
        android:layout_marginTop="100dp"
        android:text="Bind Service"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ></Button>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/unBindService"

        android:layout_marginStart="100dp"
        android:layout_marginTop="180dp"
        android:text="unBind Service"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"

        ></Button>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/finishBtn"

        android:layout_marginStart="100dp"
        android:layout_marginTop="260dp"
        android:text="Finish"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"

        ></Button>
</androidx.constraintlayout.widget.ConstraintLayout>

測試情況3:
多個Client,對應一個Service。

  • 1.點擊MainActivity的bindService按鈕
  • 2.點擊MainActivity跳轉到NextActivity頁面
  • 3.點擊NextActivity的bindService按鈕
  • 4.點擊NextActivity的unbind Service按鈕
I/Wds: ----------------------------------------------------------------------
I/Wds: MainActivity 執行 bindService
I/Wds: BindService - onCreate - Thread = main
I/Wds: BindService - onBind - Thread = main
I/Wds: MainActivity - onServiceConnected
    MainActivity - getRandomNumber = 1658998008
I/Wds: ----------------------------------------------------------------------
I/Wds: MainActivity 啓動 NextActivity

I/Wds: ----------------------------------------------------------------------
I/Wds: NextActivity 執行 bindService
I/Wds: NextActivity - onServiceConnected
I/Wds: NextActivity - getRandomNumber = -1479193691
I/Wds: ----------------------------------------------------------------------
I/Wds: NextActivity 執行 unbind Service

I/Wds: ----------------------------------------------------------------------
I/Wds: NextActivity 執行 finish

從上邊三個簡單的例子,總結可以得出如下圖的 “綁定服務” 的流程

綁定服務生命流程

總結

  之前的Android開發序列已經講述了Android的四大組件Activity和Intent,Activity的工作主要可以呈現給用戶,可以看到的。Intent就像一個連接器,連接了底層環境,連接了各種組件,以及提供組件之間的數據。

  Service就一個默默在後臺功能強大具有生命週期的“Activity”,其中Service還有很多擴展的服務,例如音樂播放、視頻播放等等都是Service。Service用途非常廣泛,以上只是大概總結了一下Service的使用,以及簡單的列舉了一下Service的使用例子。其中有很多方面都沒有講到。本篇只是拋磚引玉……

持續更新中……

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