Android --- Service

出自于此,寫得很清楚。
關于Android Service真正的完全詳解,你需要知道的一切_android service-CSDN博客
出自【zejian的博客】

什么是Service?

Service(服務)是一個一種可以在后臺執行長時間運行操作而沒有用戶界面的應用組件。

服務可由其他應用組件啟動(如Activity),服務一旦被啟動將在后臺一直運行,即使啟動服務的組件(Activity)已銷毀也不受影響。

此外,組件可以綁定到服務,以與之進行交互,甚至是執行進程間通信 (IPC)。 例如,服務可以處理網絡事務、播放音樂,執行文件 I/O 或與內容提供程序交互,而所有這一切均可在后臺進行。

Service基本上分為兩種形式:

  • 啟動狀態

??當應用組件(如 Activity)通過調用 startService() 啟動服務時,服務即處于“啟動”狀態。一旦啟動,服務即可在后臺無限期運行,即使啟動服務的組件已被銷毀也不受影響,除非手動調用才能停止服務, 已啟動的服務通常是執行單一操作,而且不會將結果返回給調用方。

  • 綁定狀態

??當應用組件通過調用 bindService() 綁定到服務時,服務即處于“綁定”狀態。綁定服務提供了一個客戶端-服務器接口,允許組件與服務進行交互、發送請求、獲取結果,甚至是利用進程間通信 (IPC) 跨進程執行這些操作。 僅當與另一個應用組件綁定時,綁定服務才會運行。 多個組件可以同時綁定到該服務,但全部取消綁定后,該服務即會被銷毀。

如何使用Service?

清單文件聲明

使用Service前會在清單文件中聲明配置。

<service android:enabled=["true" | "false"]android:exported=["true" | "false"]android:icon="drawable resource"android:isolatedProcess=["true" | "false"]android:label="string resource"android:name="string"android:permission="string"android:process="string" >. . .
</service>
  • android:exported:代表是否能被其他應用隱式調用,其默認值是由service中有無intent-filter決定的,如果有intent-filter,默認值為true,否則為false。為false的情況下,即使有intent-filter匹配,也無法打開,即無法被其他應用隱式調用。
  • android:name:對應Service類名
  • android:permission:是權限聲明
  • android:process:是否需要在單獨的進程中運行,當設置為android:process=”:remote”時,代表Service在單獨的進程中運行。注意“:”很重要,它的意思是指要在當前進程名稱前面附加上當前的包名,所以“remote”和”:remote”不是同一個意思,前者的進程名稱為:remote,而后者的進程名稱為:App-packageName:remote。
  • android:isolatedProcess :設置 true 意味著,服務會在一個特殊的進程下運行,這個進程與系統其他進程分開且沒有自己的權限。與其通信的唯一途徑是通過服務的API(bind and start)。
  • android:enabled:是否可以被系統實例化,默認為 true因為父標簽 也有 enable 屬性,所以必須兩個都為默認值 true 的情況下服務才會被激活,否則不會激活。

創建Service子類

首先要創建服務,必須創建 Service 的子類(或使用它的一個現有子類如IntentService)。

在實現中,我們需要重寫一些回調方法(其中onBind()方法必須重寫),以處理服務生命周期的某些關鍵過程。

package com.example.androidstudiostudy.service;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;//創建 Service 的子類(或使用它的一個現有子類如IntentService),重寫一些回調方法
public class OneService extends Service {public OneService() {}// 綁定服務時調用@Overridepublic IBinder onBind(Intent intent) {// TODO: Return the communication channel to the service.throw new UnsupportedOperationException("Not yet implemented");}// 首次創建服務時,系統將調用此方法來執行一次性設置程序(在調用 onStartCommand() 或onBind() 之前)。// 如果服務已在運行,則不會調用此方法,該方法只調用一次@Overridepublic void onCreate() {super.onCreate();Log.d("服務","首次創建服務調用此方法來執行一次性設置程序,該方法只調用一次");}// 每次通過startService()方法啟動Service時都會被回調。@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.d("服務","onStartCommand");return super.onStartCommand(intent, flags, startId);}// 服務銷毀時回調@Overridepublic void onDestroy() {super.onDestroy();Log.d("服務","銷毀服務");}
}
  • ?onBind()

??當另一個組件想通過調用 bindService() 與服務綁定(例如執行 RPC)時,系統將調用此方法。在此方法的實現中,必須返回 一個IBinder 接口的實現類,供客戶端用來與服務進行通信。無論是啟動狀態還是綁定狀態,此方法必須重寫,但在啟動狀態的情況下直接返回 null。

  • onCreate()

??首次創建服務時,系統將調用此方法來執行一次性設置程序(在調用 onStartCommand() 或onBind() 之前)。如果服務已在運行,則不會調用此方法,該方法只調用一次

  • onStartCommand()

??當另一個組件(如 Activity)通過調用 startService() 請求啟動服務時,系統將調用此方法。一旦執行此方法,服務即會啟動并可在后臺無限期運行。 如果自己實現此方法,則需要在服務工作完成后,通過調用 stopSelf() 或 stopService() 來停止服務。(在綁定狀態下,無需實現此方法。)

  • onDestroy()

??當服務不再使用且將被銷毀時,系統將調用此方法。服務應該實現此方法來清理所有資源,如線程、注冊的偵聽器、接收器等,這是服務接收的最后一個調用。

啟動Service

使用?startService(intent);

停止Serviece

使用?stopService(intent);

?通過Demo測試一下Service啟動狀態方法的調用順序,依次點擊啟動和停止。StudyService 代碼如下:

public class StudyService extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_study_service);}public void serviceAction(View view) {int id = view.getId();Intent intent = new Intent(this,OneService.class);if(id == R.id.bindService){// 綁定service} else if (id == R.id.stopService) {stopService(intent); // 停止服務} else {startService(intent); // 啟動服務}}
}

?此時的清單文件:

 <applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/study"android:label="@string/app_name"android:networkSecurityConfig="@xml/network_security_config"android:roundIcon="@mipmap/study"android:supportsRtl="true"android:theme="@style/Theme.AndroidStudioStudy"tools:targetApi="31"><activityandroid:name=".service.StudyService"android:exported="false" /><activityandroid:name=".service.studyService"android:exported="false" /><serviceandroid:name=".service.OneService"android:enabled="true"android:exported="true"android:permission=".service.OneService" />

日志打印:

綁定Service

綁定服務是Service的另一種變形,當Service處于綁定狀態時,其代表著客戶端-服務器接口中的服務器。

當其他組件(如 Activity)綁定到服務時,組件(如Activity)可以向Service(也就是服務端)發送請求,或者調用Service(服務端)的方法,此時被綁定的Service(服務端)會接收信息并響應,甚至可以通過綁定服務進行執行進程間通信 。

有時我們可能需要從Activity組件中去調用Service中的方法,此時Activity以綁定的方式掛靠到Service后,我們就可以輕松地方法到Service中的指定方法

與啟動服務不同的是綁定服務的生命周期通常只在為其他應用組件(如Activity)服務時處于活動狀態,不會無限期在后臺運行,也就是說宿主(如Activity)解除綁定后,綁定服務就會被銷毀。

那么在提供綁定的服務時,該如何實現呢?

實際上我們必須提供一個 IBinder接口的實現類,該類用以提供客戶端用來與服務進行交互的編程接口,該接口可以通過三種方法定義接口:

  • 擴展 Binder 類

如果服務是提供給自有應用專用的,并且Service(服務端)與客戶端相同的進程中運行(常見情況),則應通過擴展 Binder 類并從 onBind() 返回它的一個實例來創建接口。

客戶端收到 Binder 后,可利用它直接訪問 Binder 實現中以及Service 中可用的公共方法。如果我們的服務只是自有應用的后臺工作線程,則優先采用這種方法。

不采用該方式創建接口的唯一原因是,服務被其他應用或不同的進程調用。

  1. 在Service子類中創建一個擴展 Binder 的類(OneServiceBinder),在類中聲明了一個getService方法,客戶端可訪問該方法獲取 Service子類 對象的實例,只要客戶端獲取到 OneServiceBinder 對象的實例就可調用服務端的公共方法。
  2. 創建一個實現IBinder 接口的實例對象并提供公共方法給客戶端調用
  3. 從 onBind() 回調方法返回此 Binder 實例。
private OneServiceBinder oneServiceBinder = new OneServiceBinder();// 當另一個組件想通過調用 bindService() 與服務綁定(例如執行 RPC)時,系統將調用此方法。
// 在此方法的實現中,必須返回 一個IBinder 接口的實現類,供客戶端用來與服務進行通信。
// 無論是啟動狀態還是綁定狀態,此方法必須重寫,但在啟動狀態的情況下直接返回 null。
@Override
public IBinder onBind(Intent intent) {return oneServiceBinder;}/*** 創建Binder對象,返回給客戶端即Activity使用,提供數據交換的接口*/public class OneServiceBinder extends Binder {// 聲明一個方法,getService。(提供給客戶端調用)OneService getService() {// 返回當前對象LocalService,這樣我們就可在客戶端端調用Service的公共方法了return OneService.this;}}

完整service代碼

package com.example.androidstudiostudy.service;import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;//創建 Service 的子類(或使用它的一個現有子類如IntentService),重寫一些回調方法
public class OneService extends Service {private OneServiceBinder oneServiceBinder = new OneServiceBinder();private Thread thread;// 當另一個組件想通過調用 bindService() 與服務綁定(例如執行 RPC)時,系統將調用此方法。// 在此方法的實現中,必須返回 一個IBinder 接口的實現類,供客戶端用來與服務進行通信。// 無論是啟動狀態還是綁定狀態,此方法必須重寫,但在啟動狀態的情況下直接返回 null。@Overridepublic IBinder onBind(Intent intent) {return oneServiceBinder;}/*** 創建Binder對象,返回給客戶端即Activity使用,提供數據交換的接口*/public class OneServiceBinder extends Binder {// 聲明一個方法,getService。(提供給客戶端調用)OneService getService() {// 返回當前對象LocalService,這樣我們就可在客戶端端調用Service的公共方法了return OneService.this;}}// 首次創建服務時,系統將調用此方法來執行一次性設置程序(在調用 onStartCommand() 或onBind() 之前)。// 如果服務已在運行,則不會調用此方法,該方法只調用一次private int count = 0;private boolean quit = false;@Overridepublic void onCreate() {super.onCreate();Log.d("服務", "首次創建服務調用此方法來執行一次性設置程序,該方法只調用一次");thread = new Thread(new Runnable() {@Overridepublic void run() {while (!quit) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}count++;}}});thread.start();}// 當另一個組件(如 Activity)通過調用 startService() 請求啟動服務時,系統將調用此方法。// 一旦執行此方法,服務即會啟動并可在后臺無限期運行。 如果自己實現此方法,則需要在服務工作完成后,通過調用 stopSelf() 或 stopService() 來停止服務。// (在綁定狀態下,無需實現此方法。)@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.d("服務", "onStartCommand");return super.onStartCommand(intent, flags, startId);}// 當服務不再使用且將被銷毀時,系統將調用此方法。服務應該實現此方法來清理所有資源,如線程、注冊的偵聽器、接收器等,這是服務接收的最后一個調用。@Overridepublic void onDestroy() {super.onDestroy();this.quit = true;Log.d("服務", "銷毀服務");}//--------------------公共方法------------------public int getCount() {return count;}//--------------------解除綁定時調用------------------@Overridepublic boolean onUnbind(Intent intent) {Log.d("服務", "解除綁定");return super.onUnbind(intent);}
}

客戶端綁定到服務步驟:

1.ServiceConnection代表與服務的連接,它只有兩個方法,實現ServiceConnection,重寫這兩個回調方法。

  • onServiceConnected()—系統會調用該方法以傳遞服務的onBind()返回的IBinder;
  • onServiceDisconnected()—Android系統會在服務崩潰或被殺死導致的連接中斷時調用(或者隨著activity 的生命周期stop)時調用該方法,當客戶端取消綁定的時候,不會回調該方法
private ServiceConnection serviceConnection;private OneService myService;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_study_service);serviceConnection = new ServiceConnection() {// 綁定成功時調用@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {Log.d("綁定服務","成功綁定服務");OneService.OneServiceBinder oneServiceBinder = (OneService.OneServiceBinder) iBinder;myService = oneServiceBinder.getService();}// Android 系統會在與服務的連接意外中斷時(例如當服務崩潰或被終止時)調用該方法@Overridepublic void onServiceDisconnected(ComponentName componentName) {Log.d("綁定服務","與服務的連接意外中斷");myService = null;}};}

2.調用bindService(),傳遞ServiceConnection

3.當系統調用onServiceConnected()的回調方法時,可以使用接口定義的方法開始調用服務

4.要斷開與服務的連接,請調用unBindService()

如果應用在客戶端與服務仍然綁定的狀態下被銷毀了,則銷毀會導致客戶端取消綁定。

Activity代碼?????????

public class StudyService extends AppCompatActivity {private ServiceConnection serviceConnection;private OneService myService;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_study_service);serviceConnection = new ServiceConnection() {// 綁定成功時調用@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {Log.d("綁定服務","成功綁定服務");OneService.OneServiceBinder oneServiceBinder = (OneService.OneServiceBinder) iBinder;myService = oneServiceBinder.getService();}// Android 系統會在與服務的連接意外中斷時(例如當服務崩潰或被終止時)調用該方法@Overridepublic void onServiceDisconnected(ComponentName componentName) {Log.d("綁定服務","與服務的連接意外中斷");myService = null;}};}public void serviceAction(View view) {int id = view.getId();Intent intent = new Intent(this,OneService.class);if(id == R.id.bindService){// 綁定servicebindService(intent,serviceConnection,Service.BIND_AUTO_CREATE);if (myService != null) {// 通過綁定服務傳遞的Binder對象,獲取Service暴露出來的數據Log.d("獲取綁定數據", "從服務端獲取數據:" + myService.getCount());} else {Log.d("獲取綁定數據", "還沒綁定呢,先綁定,無法從服務端獲取數據");}} else if (id == R.id.stopService) {stopService(intent);} else {startService(intent);}}
}

打印數據

  • 使用 Messenger

前面了解到應用內同一進程的通信可以使用IBinder,而不同進程間的通信,最簡單的方式則是使用 Messenger 服務提供通信接口,利用此方式,我們無需使用 AIDL 便可執行進程間通信 (IPC)。Messenger底層也是通過aidl實現,不過封裝了一層,AIDL 支持多線程并發。messenger是同步,如果沒有多線程并發要求,就可以使用輕量級的Messenger。

以下是使用 Messenger 綁定Service的主要步驟:

主要步驟

1.創建一個服務子類(MessengerService?)并在里面實現一個 Handler,由其接收來自客戶端的每個調用的回調

// 用于接收從客戶端傳遞過來的數據class ServiceReciveHandle extends Handler {@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what) {case MSG_SAY_HELLO:Log.i(TAG, "thanks,Service had receiver message from client!");break;default:super.handleMessage(msg);}}}

2.將該Handler 用于創建 Messenger 對象(對 Handler 的引用)

3.Messenger 會創建一個 IBinder,MessengerService?通過 onBind() 返回這個Messenger對象的底層Binder。

final Messenger messenger = new Messenger(new ServiceReciveHandle());@Overridepublic IBinder onBind(Intent intent) {Log.i(TAG, "服務綁定");return messenger.getBinder();}

4.客戶端使用 IBinder 將 Messenger(引用MessengerService?的 Handler)實例化,然后使用Messenger將 Message 對象發送給服務。

// 實現與服務端鏈接的對象private final ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {// 通過服務端傳遞的IBinder對象,創建相應的Messenger// 通過該Messenger對象與服務端進行交互Log.i(TAG, "服務鏈接綁定");myService = new Messenger(iBinder);mBound = true;}@Overridepublic void onServiceDisconnected(ComponentName componentName) {Log.i(TAG, "服務鏈接綁定取消");myService = null;mBound = false;}};

5.MessengerService?在其 Handler 中(在 handleMessage() 方法中)接收每個 Message。

完整Service代碼:

package com.example.androidstudiostudy.service;import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;import androidx.annotation.NonNull;public class MessengerService extends Service {static final int MSG_SAY_HELLO = 1;private static final String TAG = "MessengerService";// 用于接收從客戶端傳遞過來的數據class ServiceReciveHandle extends Handler {@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what) {case MSG_SAY_HELLO:Log.i(TAG, "服務器接收到來自客戶端的消息");break;default:super.handleMessage(msg);}}}final Messenger messenger = new Messenger(new ServiceReciveHandle());@Overridepublic IBinder onBind(Intent intent) {Log.i(TAG, "服務綁定");return messenger.getBinder();}@Overridepublic void onCreate() {Log.i(TAG, "服務onCreate");super.onCreate();}@Overridepublic void onDestroy() {Log.i(TAG, "服務Destroy");super.onDestroy();}
}

Activity代碼

package com.example.androidstudiostudy.service;public class MessengerServiceActivity extends AppCompatActivity {private static final String TAG = "MessengerService-Activity";// 與服務端交互的Messengerprivate Messenger myService = null;// 是否綁定boolean mBound = false;// 實現與服務端鏈接的對象private final ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {// 通過服務端傳遞的IBinder對象,創建相應的Messenger// 通過該Messenger對象與服務端進行交互Log.i(TAG, "服務鏈接綁定");myService = new Messenger(iBinder);mBound = true;}@Overridepublic void onServiceDisconnected(ComponentName componentName) {Log.i(TAG, "服務鏈接綁定取消");myService = null;mBound = false;}};private Button sendMsg, bindService, unbindService, createService, destoryService;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_messenger_service);sendMsg = findViewById(R.id.sendMessageToService);sendMsg.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (!mBound)return;Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);try {// 發送消息myService.send(msg);} catch (RemoteException e) {throw new RuntimeException(e);}}});Intent intent = new Intent(MessengerServiceActivity.this, MessengerService.class);unbindService = findViewById(R.id.unbindMessengerService);unbindService.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {unbindService(mConnection);}});bindService = findViewById(R.id.bindMessengerService);bindService.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Log.i(TAG, "bd");bindService(intent, mConnection, Context.BIND_AUTO_CREATE);}});createService = findViewById(R.id.startMessengerService);createService.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {startService(intent);}});destoryService = findViewById(R.id.destoreyMessengerService);destoryService.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {stopService(intent);}});}
}
?服務器與客戶端的雙向通信

上述代碼能夠實現客戶端向服務器的通信,如果想要服務器向客戶端通信,則需要在客戶端也創建一個接收消息的Messenger和Handler,改造?MessengerService 中的handler,在接受到信息時發送信息

MessengerService:

在服務器端的handler中發送返回消息

// 用于接收從客戶端傳遞過來的數據private static class ServiceReciveHandle extends Handler {@Overridepublic void handleMessage(@NonNull Message msg) {if (msg.what == MSG_SAY_HELLO) {Log.i(TAG, "服務器接收到來自客戶端的消息");Messenger replyMessenger = msg.replyTo;Message replyMessenge = Message.obtain(null, MessengerService.MSG_SAY_HELLO);Bundle bundle=new Bundle();bundle.putString("reply","ok~,I had receiver message from you! ");replyMessenge.setData(bundle);try {replyMessenger.send(replyMessenge);} catch (RemoteException e) {throw new RuntimeException(e);}} else {super.handleMessage(msg);}}}

Activity:

1.創建一個用于接收服務器端消息的Messenger和Handler

2.在發送消息時,把接收服務器端的回復的Messenger通過Message的replyTo參數傳遞給服務端

 private final Messenger activityRecevierMessenger = new Messenger(new activityRecevierHandler());private static class activityRecevierHandler extends Handler{@Overridepublic void handleMessage(@NonNull Message msg) {if (msg.what == MessengerService.MSG_SAY_HELLO) {Log.i(TAG, "客戶端接收到來自服務的消息" + msg.getData().getString("reply"));} else {super.handleMessage(msg);}}}
sendMsg = findViewById(R.id.sendMessageToService);sendMsg.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (!mBound)return;Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);// 把接收服務器端的回復的Messenger通過Message的replyTo參數傳遞給服務端msg.replyTo =activityRecevierMessenger;try {// 發送消息myService.send(msg);} catch (RemoteException e) {throw new RuntimeException(e);}}});

綁定服務的注意點
??

1.多個客戶端可同時連接到一個服務。不過,只有在第一個客戶端綁定時,系統才會調用服務的 onBind() 方法來檢索 IBinder。系統隨后無需再次調用 onBind(),便可將同一 IBinder 傳遞至任何其他綁定的客戶端。當最后一個客戶端取消與服務的綁定時,系統會將服務銷毀(除非 startService() 也啟動了該服務)。

2.通常情況下我們應該在客戶端生命周期(如Activity的生命周期)的引入 (bring-up) 和退出 (tear-down) 時刻設置綁定和取消綁定操作,以便控制綁定狀態下的Service,一般有以下兩種情況:

  • 如果只需要在 Activity 可見時與服務交互,則應在 onStart() 期間綁定,在 onStop() 期間取消綁定。
  • 如果希望 Activity 在后臺停止運行狀態下仍可接收響應,則可在 onCreate() 期間綁定,在 onDestroy() 期間取消綁定。需要注意的是,這意味著 Activity 在其整個運行過程中(甚至包括后臺運行期間)都需要使用服務,因此如果服務位于其他進程內,那么當提高該進程的權重時,系統很可能會終止該進程。

??3.通常情況下(注意),切勿在 Activity 的 onResume() 和 onPause() 期間綁定和取消綁定,因為每一次生命周期轉換都會發生這些回調,這樣反復綁定與解綁是不合理的。此外,如果應用內的多個 Activity 綁定到同一服務,并且其中兩個 Activity 之間發生了轉換,則如果當前 Activity 在下一次綁定(恢復期間)之前取消綁定(暫停期間),系統可能會銷毀服務并重建服務,因此服務的綁定不應該發生在 Activity 的 onResume() 和 onPause()中

??4.我們應該始終捕獲 DeadObjectException DeadObjectException 異常,該異常是在連接中斷時引發的,表示調用的對象已死亡,也就是Service對象已銷毀,這是遠程方法引發的唯一異常,DeadObjectException繼承自RemoteException,因此我們也可以捕獲RemoteException異常。

??5.應用組件(客戶端)可通過調用 bindService() 綁定到服務,Android 系統隨后調用服務的 onBind() 方法,該方法返回用于與服務交互的 IBinder,而該綁定是異步執行的。

關于啟動服務與綁定服務間的轉換問題

通過前面對兩種服務狀態的分析,相信大家已對Service的兩種狀態有了比較清晰的了解,那么現在我們就來分析一下當啟動狀態和綁定狀態同時存在時,又會是怎么的場景?
??雖然服務的狀態有啟動和綁定兩種,但實際上一個服務可以同時是這兩種狀態,也就是說,它既可以是啟動服務(以無限期運行),也可以是綁定服務。有點需要注意的是Android系統僅會為一個Service創建一個實例對象,所以不管是啟動服務還是綁定服務,操作的是同一個Service實例,而且由于綁定服務或者啟動服務執行順序問題將會出現以下兩種情況:

先綁定服務后啟動服務

??如果當前Service實例先以綁定狀態運行,然后再以啟動狀態運行,那么綁定服務將會轉為啟動服務運行,這時如果之前綁定的宿主(Activity)被銷毀了,也不會影響服務的運行,服務還是會一直運行下去,指定收到調用停止服務或者內存不足時才會銷毀該服務。

先啟動服務后綁定服務

??如果當前Service實例先以啟動狀態運行,然后再以綁定狀態運行,當前啟動服務并不會轉為綁定服務,但是還是會與宿主綁定,只是即使宿主解除綁定后,服務依然按啟動服務的生命周期在后臺運行,直到有Context調用了stopService()或是服務本身調用了stopSelf()方法抑或內存不足時才會銷毀服務。

以上兩種情況顯示出啟動服務的優先級確實比綁定服務高一些。不過無論Service是處于啟動狀態還是綁定狀態,或處于啟動并且綁定狀態,我們都可以像使用Activity那樣通過調用 Intent 來使用服務(即使此服務來自另一應用)。 當然,我們也可以通過清單文件將服務聲明為私有服務,阻止其他應用訪問。

最后這里有點需要特殊說明一下的,由于服務在其托管進程的主線程中運行(UI線程),它既不創建自己的線程,也不在單獨的進程中運行(除非另行指定)。

這意味著,如果服務將執行任何耗時事件或阻止性操作(例如 MP3 播放或聯網)時,則應在服務內創建新線程來完成這項工作,簡而言之,耗時操作應該另起線程執行。只有通過使用單獨的線程,才可以降低發生“應用無響應”(ANR) 錯誤的風險,這樣應用的主線程才能專注于用戶與 Activity 之間的交互, 以達到更好的用戶體驗。

前臺服務以及通知發送?

前臺服務被認為是用戶主動意識到的一種服務,因此在內存不足時,系統也不會考慮將其終止。 前臺服務必須為狀態欄提供通知,狀態欄位于“正在進行”標題下方,這意味著除非服務停止或從前臺刪除,否則不能清除通知。

例如將從服務播放音樂的音樂播放器設置為在前臺運行,這是因為用戶明確意識到其操作。 狀態欄中的通知可能表示正在播放的歌曲,并允許用戶啟動 Activity 來與音樂播放器進行交互。如果需要設置服務運行于前臺, 我們該如何才能實現呢?Android官方給我們提供了兩個方法,分別是startForeground()和stopForeground(),這兩個方式解析如下:

  • startForeground(int id, Notification notification)

該方法的作用是把當前服務設置為前臺服務,其中id參數代表唯一標識通知的整型數,需要注意的是提供給 startForeground() 的整型 ID 不得為 0,而notification是一個狀態欄的通知。

  • stopForeground(boolean removeNotification)

該方法是用來從前臺刪除服務此方法傳入一個布爾值,指示是否也刪除狀態欄通知,true為刪除。 注意該方法并不會停止服務。

但是,如果在服務正在前臺運行時將其停止,則通知也會被刪除。

服務Service與線程Thread的區別

兩者的真正關系 = 沒有關系。

兩者概念的迥異

Thread 是程序執行的最小單元,它是分配CPU的基本單位,android系統中UI線程也是線程的一種,當然Thread還可以用于執行一些耗時異步的操作。

Service是Android的一種機制,服務是運行在主線程上的,它是由系統進程托管。它與其他組件之間的通信類似于client和server,是一種輕量級的IPC通信,這種通信的載體是binder,它是在linux層交換信息的一種IPC,而所謂的Service后臺任務只不過是指沒有UI的組件罷了

兩者的執行任務迥異

在android系統中,線程一般指的是工作線程(即后臺線程),而主線程是一種特殊的工作線程,它負責將事件分派給相應的用戶界面小工具,如繪圖事件及事件響應,因此為了保證應用 UI 的響應能力主線程上不可執行耗時操作。如果執行的操作不能很快完成,則應確保它們在單獨的工作線程執行。

Service 則是android系統中的組件,一般情況下它運行于主線程中,因此在Service中是不可以執行耗時操作的,否則系統會報ANR異常,之所以稱Service為后臺服務,大部分原因是它本身沒有UI,用戶無法感知(當然也可以利用某些手段讓用戶知道),但如果需要讓Service執行耗時任務,可在Service中開啟單獨線程去執行。

兩者使用場景

當要執行耗時的網絡或者數據庫查詢以及其他阻塞UI線程或密集使用CPU的任務時,都應該使用工作線程(Thread),這樣才能保證UI線程不被占用而影響用戶體驗。

在應用程序中,如果需要長時間的在后臺運行,而且不需要交互的情況下,使用服務。比如播放音樂,通過Service+Notification方式在后臺執行同時在通知欄顯示著。

兩者的最佳使用方式

在大部分情況下,Thread和Service都會結合著使用:

  • 比如下載文件:一般會通過Service在后臺執行+Notification在通知欄顯示+Thread異步下載;
  • 再如應用程序會維持一個Service來從網絡中獲取推送服務。

在Android官方看來也是如此,所以官網提供了一個Thread與Service的結合來方便我們執行后臺耗時任務,它就是IntentService,當然 IntentService并不適用于所有的場景,但它的優點是使用方便、代碼簡潔,不需要我們創建Service實例并同時也創建線程,某些場景下還是非常贊的!由于IntentService是單個worker thread,所以任務需要排隊,因此不適合大多數的多任務情況。

管理服務生命周期?

  • 左圖顯示了使用 startService() 所創建的服務的生命周期。
  • 右圖顯示了使用 bindService() 所創建的服務的生命周期。

通過圖中的生命周期方法,我們可以監控Service的整體執行過程,包括創建,運行,銷毀

  • 服務的整個生命周期從調用 onCreate() 開始起,到 onDestroy() 返回時結束。與 Activity 類似,服務也在 onCreate() 中完成初始設置,并在 onDestroy() 中釋放所有剩余資源。例如,音樂播放服務可以在 onCreate() 中創建用于播放音樂的線程,然后在 onDestroy() 中停止該線程。
  • 無論服務是通過 startService() 還是 bindService() 創建,都會為所有服務調用 onCreate() 和 onDestroy() 方法。
  • 服務的有效生命周期從調用 onStartCommand() 或 onBind() 方法開始。每種方法均有 Intent 對象,該對象分別傳遞到 startService() 或 bindService()。
  • 對于啟動服務,有效生命周期與整個生命周期同時結束(即便是在 onStartCommand() 返回之后,服務仍然處于活動狀態)。對于綁定服務,有效生命周期在 onUnbind() 返回時結束。

??從執行流程圖來看,服務的生命周期比 Activity 的生命周期要簡單得多。但是,我們必須密切關注如何創建和銷毀服務,因為服務可以在用戶沒有意識到的情況下運行于后臺。管理服務的生命周期(從創建到銷毀)有以下兩種情況:

  • 啟動服務

該服務在其他組件調用 startService() 時創建,然后無限期運行,且必須通過調用 stopSelf() 來自行停止運行。此外,其他組件也可以通過調用 stopService() 來停止服務。服務停止后,系統會將其銷毀。

  • 綁定服務

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

  • 啟動服務與綁定服務的結合體

我們可以綁定到已經使用 startService() 啟動的服務。例如,可以通過使用 Intent(標識要播放的音樂)調用 startService() 來啟動后臺音樂服務。隨后,可能在用戶需要稍加控制播放器或獲取有關當前播放歌曲的信息時,Activity 可以通過調用 bindService() 綁定到服務。在

這種情況下,除非所有客戶端均取消綁定,否則 stopService() 或 stopSelf() 不會真正停止服務。

如何保證服務不被殺死

  • 因內存資源不足而殺死Service

這種情況比較容易處理,可將onStartCommand() 方法的返回值設為 START_STICKY或START_REDELIVER_INTENT ,該值表示服務在內存資源緊張時被殺死后,在內存資源足夠時再恢復。也可將Service設置為前臺服務,這樣就有比較高的優先級,在內存資源緊張時也不會被殺掉。

  • 用戶通過 settings -> Apps -> Running -> Stop 方式殺死Service

這種情況是用戶手動干預的,不過幸運的是這個過程會執行Service的生命周期,也就是onDestory方法會被調用,這時便可以在 onDestory() 中發送廣播重新啟動。這樣殺死服務后會立即啟動。這種方案是行得通的,但為程序更健全,我們可開啟兩個服務,相互監聽,相互啟動。服務A監聽B的廣播來啟動B,服務B監聽A的廣播來啟動A。?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/40000.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/40000.shtml
英文地址,請注明出處:http://en.pswp.cn/web/40000.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

萬字長文|關于 OpenAI 接口開發你應該知道的一切

這篇文章中個人結合自己的實踐經驗把 OpenAI 官方文檔解讀一遍。但是原文檔涉及內容眾多&#xff0c;包括微調&#xff0c;嵌入&#xff08;Embeddings&#xff09;等眾多主題&#xff0c;我這里重點挑選自己開發高頻使用到的&#xff0c;需要詳細了解的可以自行前往官網閱讀。…

Java中的文本搜索與全文檢索引擎

Java中的文本搜索與全文檢索引擎 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 在現代應用程序中&#xff0c;處理和搜索大量文本數據是一項關鍵任務。傳統的…

2024科技文化節程序設計競賽

補題鏈接 https://www.luogu.com.cn/contest/178895#problems A. 簽到題 忽略掉大小為1的環&#xff0c;答案是剩下環的大小和減環的數量 #include<bits/stdc.h> #include<iostream> #include<cstdio> #include<vector> #include<map> #incl…

c進階篇(四):內存函數

內存函數以字節為單位更改 1.memcpy memcpy 是 C/C 中的一個標準庫函數&#xff0c;用于內存拷貝操作。它的原型通常定義在 <cstring> 頭文件中&#xff0c;其作用是將一塊內存中的數據復制到另一塊內存中。 函數原型&#xff1a;void *memcpy(void *dest, const void…

多模態融合算法應用:CT + 臨床文本數據 + pyradiomics提取到的圖像特征

多模態融合算法應用 CT 臨床文本數據 pyradiomics提取圖像特征 單模態建模臨床數據建模pyradiomics提取圖像特征建模CT建模 多模態建模前融合為什么能直接合并在一起&#xff1f; 后融合Med-CLIP&#xff1a;深度學習 可解釋性 單模態建模 臨床數據建模 臨床文本數據&…

WPF Menu實現快捷鍵操作

很多小伙伴說&#xff0c;在Menu中&#xff0c;實現單個快捷鍵操作很簡單&#xff0c;怎么實現多個快捷鍵操作和&#xff0c;組合快捷鍵呢&#xff0c;今天他來了。 上代碼和效果圖 一、Ctrl Shift 任意子母鍵實現快捷鍵組合 <Window x:Class"XH.TemplateLesson.M…

【測試開發】【postman】按順序循環執行接口

postman按順序循環執行接口 新建接口接口排序執行請求集合 新建接口 Request 001 Request 002 Request 003 接口排序 在Request 001的Tests中添加代碼 postman.setNextRequest("Request 002");在Request 002的Tests中添加代碼 postman.setNextRequest("Requ…

Redis 7.x 系列【17】四種持久化策略

有道無術&#xff0c;術尚可求&#xff0c;有術無道&#xff0c;止于術。 本系列Redis 版本 7.2.5 源碼地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目錄 1. 概述2. 案例演示2.1 無持久化2.2 RDB2.3 AOF2.4 混合模式2.4.1 方式一&#xff1a;…

線性代數|機器學習-P21概率定義和Markov不等式

文章目錄 1. 樣本期望和方差1.1 樣本期望 E ( X ) \mathrm{E}(X) E(X)1.2 樣本期望 D ( X ) \mathrm{D}(X) D(X) 2. Markov 不等式&Chebyshev不等式2.1 Markov不等式公式 概述2.2 Markov不等式公式 證明&#xff1a;2.3 Markov不等式公式 舉例&#xff1a;2.4 Chebyshev不…

AI繪畫 Stable Diffusion圖像的臉部細節控制——采樣器全解析

大家好&#xff0c;我是畫畫的小強 我們在運用AI繪畫 Stable Diffusion 這一功能強大的AI繪圖工具時&#xff0c;我們往往會發現自己對提示詞的使用還不夠充分。在這種情形下&#xff0c;我們應當如何調整自己的策略&#xff0c;以便更加精確、全面地塑造出理想的人物形象呢&a…

域環境提權

域內提權漏洞(1) Netlogon域權限提升 1.查看域控主機名稱 net group "domain controllers" /domain 2.檢測漏洞是否存在 https://github.com/SecuraBV/CVE-2020-1472.git python zerologon_tester.py OWA 192.168.52.138 3.漏洞利用&#xff0c;對域賬號重置 ht…

《簡歷寶典》01 - 一文帶你學會如何寫一份糟糕透頂的簡歷

我們每個人幾乎都會面對找工作這件事&#xff0c;而找工作或者說求職首先就是要寫一份簡歷。今天狗哥將以一個不同的視角帶你寫一份無與倫比&#xff0c;糟糕透頂的求職簡歷&#xff0c;說實話&#xff0c;其實幾年前&#xff0c;我就是這么寫的。 目錄 1. 文件名 2. 基本信…

【項目管理】項目風險管理(Word原件)

風險和機會管理就是在一個項目開發過程中對風險進行識別、跟蹤、控制的手段。風險和機會管理提供了對可能出現的風險進行持續評估&#xff0c;確定重要的風險機會以及實施處理的策略的一種規范化的環境。包括識別、分析、制定處理和減緩行動、跟蹤 。合理的風險和機會管理應盡力…

白騎士的Python教學進階篇 2.4 高級數據結構

系列目錄 上一篇&#xff1a;白騎士的Python教學進階篇 2.3 文件操作??????? 在Python中&#xff0c;掌握高級數據結構可以顯著提升你的編程效率和代碼可讀性。高級數據結構包括列表推導式、生成器與迭代器以及裝飾器。本文將詳細介紹這些高級數據結構&#xff0c;幫助…

算法刷題1-10大排序算法匯總

十種常見排序算法可以分為兩大類&#xff1a; 比較類排序&#xff1a;通過比較來決定元素間的相對次序&#xff0c;由于其時間復雜度不能突破O(nlogn)&#xff0c;因此也稱為非線性時間比較類排序。非比較類排序&#xff1a;不通過比較來決定元素間的相對次序&#xff0c;它可…

服務器安裝Nginx教程

1、安裝所需依賴 yum -y install gcc gcc-c make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel 2、創建nginx目錄并下載Nginx安裝包 //進入/usr/local cd /usr/local//創建nginx目錄 mkdir nginx//進入nginx目錄 cd nginx//下載nginx tar包 wget http://…

Lesson 47 A cup of coffee

Lesson 47 A cup of coffee 詞匯 like v. 喜歡&#xff0c;想要 用法&#xff1a;like 物品 / 人 喜歡……    like 動詞ing 喜歡做……&#xff08;習慣性&#xff09;    like to 動詞原形 喜歡做……&#xff08;一次性&#xff09; 例句&#xff1a;我喜歡小狗…

[leetcode hot 150]第五百三十題,二叉搜索樹的最小絕對差

題目&#xff1a; 給你一個二叉搜索樹的根節點 root &#xff0c;返回 樹中任意兩不同節點值之間的最小差值 。 差值是一個正數&#xff0c;其數值等于兩值之差的絕對值。 解析&#xff1a; minDiffInBST 方法是主要方法。創建一個 ArrayList 來存儲樹的節點值。inorderTrave…

前端日常掃盲

一、js標簽語句 直接上代碼 for(let i 0; i < 10; i){console.log("頂層循環");for(let j 0; j < 10; j){console.log("內層循環",i,j);if(i * j > 30){console.log("退出頂層循環");break;}} }如上面的代碼&#xff0c;雙層循環&a…

opencv-yolo-tiny車輛檢測 ----20240705

opencv-yolo-tiny 實現車輛檢測 opencv.dnn模塊已經支持大部分格式的深度學習模型推理,該模塊可以直接加載tensorflow、darknet、pytorch等常見深度學習框架訓練出來的模型,并運行推理得到模型輸出結果。opecnv.dnn模塊已經作為一種模型部署方式,應用在工業落地實際場景中。…