--- 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 基礎`