前言
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的生命週期的每個階段,接下來會說明一下每個階段對應的功能,在每個階段怎麼使用。
回調 | 描述 |
---|---|
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的使用例子。其中有很多方面都沒有講到。本篇只是拋磚引玉……
持續更新中……