---
# System prepended metadata

title: Service
tags: [Android 基礎]

---

---
title: 'Service'
disqus: kyleAlien
---

Service
===

## OverView of Content
如有引用參考請詳註出處，感謝 :smile:

> Service 屬於 Android 四大零組件之一

[TOC]

## Service 概述
* 服務是一種**後台運行**的概念，它不需要使用 UI 跟使用者互交，即使主界面被隱藏 or 切換到別的應用程序，服務仍然可以繼續工作
* **Service 並不是獨立運行在進程中，==服務是依賴於主進程中==，當主進程被結束，Service 也隨之結束**

:::warning
**==Service 默認是運行在主線程中，++Service 並不會新開線程++==，如果處理過於花費時間的任務就會出現 ANR，要特別注意**
:::

### 創建 Service
* File -> New -> Service -> Service，它會詢問是否 Export(讓其他應用訪問)、enable (使否啟用)，當然也可以手動創建

> ![](https://i.imgur.com/Apxi9MY.png)

* 在 Manifest xml 中也會同時創建 service 節點

```xml=
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.oo.jnidemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">
        
        // 服務節點
        <service
            android:name=".MyMVP.MyService"
            android:enabled="true"
            android:exported="true"/>

        <activity android:name=".MyMVP.MyMVP">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
    </application>

</manifest>
```
### Manifest Service 介紹
* Service 在 Manifest 只有 name 是必須的屬性，其他屬性可有可無
```java=
<service
    android:name="string"
    android:enabled=["true" | "false"]
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:isolatedProcess=["true" | "false"]
    android:label="string resource"
    android:permission="string"
    android:process="string" >
</service>
```

| 屬性 | 功能 |
| -------- | -------- |
| name | service **路徑名，必須要有此屬性** |
| enabled | 是否可被調用實例化 |
| exported | **是否其他應用程式可以調用，[AIDL](https://hackmd.io/yNGrVdN-RtelUqYQgn9avg#AIDL0) 就技術就可以使用** |
| icon | 該 service 的圖形 |
| label | 顯示給用戶看該 serice 的名稱 |
| process | 進程控制，**默認為主進程中** 
| permission | 其他組件啟動此服務時需要的權限 |
| isolatedProcess | service 將運行在系統分出的特殊進程中，之後要調用只能透過 Service API ||


## Service 使用
* **Android 中繼承 Service 代表是一個服務**，目前先忽略 onBind 方法
* Service 啟動有兩種方式，如下

| 開啟 | 關閉 | 特色 | 生命週期 |
| -------- | -------- | -------- | - |
| startService | stopService / stopSelf | 簡單方便，但 **無法控制** 服務的內容細節 | 生命週期不按照 Service，關閉只依照 Function 調用 |
| bindService | unbindService | **可以控制服務的細節** | 當綁定的組件取消綁定時 Service 停止（有可能是 APP kill or unbindService） |


### startService / stopService
* 使用 startService 啟動服務後，該服務就與組件沒關係了，可在後台無限期的運行下去，只有 stopSelf / stopService 可以停止它
> onCreate & onStartCommand 的差別在於，**onCreate 並不是每一次都會被調用，而 onStartCommand 是固定每次都會被調用**

```java=
// Service

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 服務啟動後立刻型的動作可以寫在這裡，每次服務啟動都會調用
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        // Service 創建時調用
    }

    @Override
    public void onDestroy() {
        // 回收資源
        super.onDestroy();
    }

    // Service 中唯一抽象方法 onBind
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}
```
* UI 界面啟動服務
```java=
// UI

public class TestService extends AppCompatActivity implements View.OnClickListener{

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

        findViewById(R.id.serviceBtnStart).setOnClickListener(this);
        findViewById(R.id.serviceBtnStop).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Intent intent;
        switch (view.getId()) {
            case R.id.serviceBtnStart:
                intent = new Intent(this, MyService.class);
                startService(intent);
                break;

            case R.id.serviceBtnStop:
                intent = new Intent(this, MyService.class);
                stopService(intent);
                break;
        }
    }
}
```

:::info
Service 可以**經由 stopSelf() 關閉自己**
:::

**--Log 觀察--**
> 分別開關了 2 次
> ![](https://i.imgur.com/7NXoZVw.png)

### bindService / unbindService
* 在繼承 Service 時必須顧及 **onBind 函數，該函數是 Service 中唯一的抽象函數**
* 執行 onBind 就不會執行 onStartCommand 方法

```java=
// Service

public class MyService extends Service {

    public static final String TAG = "MyService";

    public MyService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 服務啟動後立刻型的動作可以寫在這裡，每次服務啟動都會調用
        Log.e(TAG, "Service onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        // Service 創建時調用
        Log.e(TAG, "Service onCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 回收資源
        Log.e(TAG, "Service onDestroy");
    }

    // Service 中唯一抽象方法 onBind
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "Service onBind");
        if(myBindClass == null) {
            throw new UnsupportedOperationException("Not yet implemented");
        } else {
            return myBindClass;
        }
    }

    private MyBindClass myBindClass = new MyBindClass();

    class MyBindClass extends Binder {  // Binder implements IBinder
        public void startTask() {
            Log.d(TAG, "Service start Task");
        }

        public void stopTask() {
            Log.d(TAG, "Service stop Task");
        }
    }
}
```
* **bindService 要使用監聽，以下匿名 ++ServiceConnection++，監聽 Service 是否已經連上，連上後就可以取得內部類來控制 Service**
```java=
public class TestService extends AppCompatActivity implements View.OnClickListener{

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

        findViewById(R.id.serviceBtnStart).setOnClickListener(this);
        findViewById(R.id.serviceBtnStop).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Intent intent;
        switch (view.getId()) {
            case R.id.serviceBtnStart:
                intent = new Intent(this, MyService.class);
                bindService(intent, serviceConnection, BIND_AUTO_CREATE);   // 監聽綁定
                break;

            case R.id.serviceBtnStop:
                unbindService(serviceConnection);
                break;
        }
    }

    // 匿名類
    private ServiceConnection serviceConnection = new ServiceConnection() {
        // "1. "
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            // 獲取控制 Service 的方法
            Log.e(MyService.TAG, "on Service Connected");
            // 向下轉型 !
            MyService.MyBindClass bindClass = (MyService.MyBindClass) iBinder;	
            bindClass.startTask();
            bindClass.stopTask();
        }

        // "2. "
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.e(MyService.TAG, "on Service Disconnected");
        }
    };
}
```
1. 系統調用 onServiceConnected 方法時會傳遞參數 IBinder，而 IBinder 就是 onBind() 返回的 IBinder
2. 當服務崩潰 or 中止時會調用該方法，**當客戶端 ++主動取消綁定時並不會調用該方法++**

**--Log 觀察--**
> ![](https://i.imgur.com/GfC5jiW.png)

## IntentService
* 會發現一般 Service 有幾個地方很危險，而 IntentService 可以很好的解決這個問題
1. **忘記調用 stopSelf 關閉服務**，IntentService 在服務結束後會自動關閉
2. **處理耗時操作要在服務中新開線程，否則會 ANR (因為服務仍是跑在主線程的)，而 ==IntentService 本身就是跑在其他線程==**

* 創建方法 File -> New -> Service -> Service (IntentService)，或是自己手動繼承 IntentService，**在 Manifest.xml 中一定要註冊 Service (export=false)**

```java=
// IntentService Test
public class ISTest extends IntentService {

    public IntentServiceDemo(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 在這裡進行主要操作
    }
}
```

### 測試 IntentService
* 為了要測試 IntentService 是否跑在其他的線程
```java=
// IntentService，記得要在 Manifest 註冊
public class IntentServiceTest extends IntentService {

    public IntentServiceTest() {
        super("My_IntentService");    // 給予線程名稱 1.
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Log.e("tractThread", "Thread Name: " + Thread.currentThread().getName() +
                ", id : " + Thread.currentThread().getId());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();  // 記得要呼叫超類
        Log.e("tractThread", "IntentService onDestroy");
    }
}



// UI
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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


        findViewById(R.id.Sbtn).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Log.e("tractThread", "Thread Name: " + Thread.currentThread().getName() +
                ", id : " + Thread.currentThread().getId());
        Intent intent = new Intent(this, IntentServiceTest.class);
        startService(intent);
    }
}
```
1. 記得要給予名稱，否則會錯誤 (直接跳掉...)，因為 startService 並不會給予建構函數名稱
2. onDestory 要覆寫時記得要用 super 呼叫超類，避免無法關閉，並且 **在任務執行完成後就會自動關閉服務**

**--Log 輸出--**
> ![](https://i.imgur.com/ugUawts.png)



## Appendix & FAQ

:::info
:::

###### tags: `Android 基礎`
