Android四大組件之Service
簡述
Service 是Android中的組件之一,與Activity、Broadcast Receiver、Content Provider共稱爲四大組件。從名字也可以看出Service主要是提供服務的,它運行在後臺,甚至可以不依賴組件而單獨存在。
生命週期
下面是官方文檔中的生命週期圖:
從上圖可以看出Service有兩種啓動方式,不同的啓動方式下其生命週期是不同的。比較可以看出兩種啓動方式下onCreate和onDestroy是一致的,不同之處是中間服務執行的部分。
對於startService方式啓動的服務,它執行的是onStartCommond,由於它啓動後就與組件沒有了關聯,因此只涉及到這一個方法。這樣的服務不手動關閉的話會一直運行在後臺。對於onBind方式啓動的服務,它與啓動的組將通過IBinder進行互動,因此它的生命週期中執行的是onBind和onUnbind方法,對應着綁定與解綁的操作。
使用
Service作爲四大組件之一,使用服務的時候也需要在AndroidManifest中進行註冊(四大組件都需要註冊)。
<service android:description="string resource"
android:directBootAware=["true" | "false"]
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>
description:對服務的描述信息,不是必要的。該屬性必須使用引用方式,不能直接添加描述。
derestBootAware:服務是否可以運行在用戶未解鎖前,默認false
enabled:當前服務是否可被實例化,默認true。false下服務無法啓動
export:是否可以被其他應用程序啓動,默認false。
isolatedProcess:true則運行在其他進程中,與系統進程獨立並且沒有權限,僅能通過綁定方式與其他組件通信
label:展示給用戶的服務的名字
name:對應的Service實現類的類名,必須聲明
permission:權限
process:服務運行的進程名字,默認情況下所有的組件都是運行在主進程中,主進程名則是應用包名。設置時使用冒號(:)可以在進程前加上當前包名。例如包名爲com.example.test,設置進程爲 android:process=":new",則服務運行在com.example.test:new 進程。若是不加冒號,則運行在new進程。
創建服務對象
創建服務只需要繼承Service類,然後實現onBind方法即可,並且在AndroidManifest中聲明即可。
public class MyService extends Service {
private static final String TAG = "MyService";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
Log.i(TAG, "onCreate: Service創建");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: Service啓動");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: Service銷燬");
super.onDestroy();
}
}
這裏創建了一個空的服務,並且onBind方法僅僅返回了一個null。這個方法是必須實現的,但是在非綁定方式啓動的服務中,是不涉及到這個onBind方法的,因此採用startService啓動的服務中,onBind直接返回null即可。
直接啓動:
直接啓動是由startService進行啓動,然後通過stopService停止,該方式下,生命週期爲onCreate->onStartCommond->onDestroy
當第一次調用startService的時候,服務會先調用onCreate再調用onStartCommond,並且在已啓動服務的情況下再次startService則只調用onStartCommond方法。
public class MainActivity extends AppCompatActivity {
private Button mButtonStart;
private Button mButtonStop;
private Intent mIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButtonStart = findViewById(R.id.btn_start);
mButtonStop = findViewById(R.id.btn_stop);
mIntent = new Intent(this,MyService.class);
mButtonStart.setOnClickListener(v->{
startService(mIntent);
});
mButtonStop.setOnClickListener(v->{
stopService(mIntent);
});
}
}
多次啓動,最後停止的打印日誌如下:
/com.example.servicedemo I/MyService: onCreate: Service創建
/com.example.servicedemo I/MyService: onStartCommand: Service啓動
/com.example.servicedemo I/MyService: onStartCommand: Service啓動
/com.example.servicedemo I/MyService: onStartCommand: Service啓動
/com.example.servicedemo I/MyService: onDestroy: Service銷燬
值得注意的是,採用startService方式啓動的服務將一直運行在後臺,而不會主動銷燬。因此,當服務的操作結束後,要使用stopService(Intent)來將該服務進行關閉,或者在服務內部通過stopSelf來進行關閉。
int onStartCommand(Intent intent, int flags, int startId)
參數:
intent :這是啓動服務時(startService)所攜帶的intent,可以用來傳遞數據。在系統殺死該服務並重啓後可能爲null。
flags: 值爲0或者START_FLAG_REDELIVERY或者 START_FLAG_RETRY
startId:唯一ID,用於stopSelf
返回值:
START_STICKY:如果服務因爲內存問題被系統殺死,則後面會重建該服務並調用onStartCommond,但這種情況下intent的值爲null。
START_STICKY_COMPATIBILITY:兼容START_STICKY,但是並不保證一定能夠重建服務成功
START_NOT_STICKY:如果服務因爲內存問題被系統殺死,則不重建該服務,因此這種情況下不會接收到值爲null的intent。
START_REDELIVER_INTENT:如果服務因爲內存問題被系統殺死,則重建服務並且傳遞最後一個intent,這種情況下intent也不爲空。
Bind啓動
Bind啓動方式是通過bindService進行綁定的,這種方式要求Service的onBind方法必須返回一個IBinder對象,這樣在其他組件中就能夠獲得該對象,從而進行交互。該方式下生命週期爲onCreate->onBind->onUnBind->onDestroy
public class MyService extends Service {
private static final String TAG = "MyService";
class MyBinder extends Binder {
public MyService getService() {
return MyService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: 綁定服務");
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind: 解除綁定");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
Log.i(TAG, "onCreate: Service創建");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: Service啓動");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: Service銷燬");
super.onDestroy();
}
}
重新修改了MyService,在其內部定義了一個內部類,該內部類是繼承自Binder的,並且定義了一個方法用於獲取Service。這樣,在通過bindServce獲取的IBinder後就可以進而獲取Service對象,從而實現組件與服務通信。
另外,綁定方式下啓動的服務是依賴於啓動組件的,當組件銷燬的時候,若是並未顯式解綁服務,則會自動調用onUnbind和onDestroy進行解綁。這與startService方式是有明顯不同的,startService方式下啓動的服務將一直運行在後臺,即使啓動它的組件已被銷,所以一定要記住手動停止服務。
private MyService mService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButtonBind = findViewById(R.id.btn_bind);
mButtonUnBind = findViewById(R.id.btn_unbind);
mIntent = new Intent(this, MyService.class);
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = ((MyService.MyBinder) service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
mButtonBind.setOnClickListener(v -> {
bindService(mIntent, connection, BIND_AUTO_CREATE);
});
mButtonUnBind.setOnClickListener(v -> {
unbindService(connection);
});
}
通過Bind方式啓動Service還需要一個ServiceConnection對象,在連接到Service的時候,該對象的onServiceConnected方法就可以獲得該Service中onBind返回的IBinder,從而進一步獲得Service對象。
/com.example.servicedemo I/MyService: onCreate: Service創建
/com.example.servicedemo I/MyService: onBind: 綁定服務
/com.example.servicedemo I/MyService: onUnbind: 解除綁定
/com.example.servicedemo I/MyService: onDestroy: Service銷燬
兩種方式同時啓動
同一個服務是隻存在一份實例的,也就是說只有第一次創建服務纔會調用onCreate方法,後面多次啓動只會調用onStartCommond或者onBind方法。並且,當同一個服務既被startService方式啓動又被bindService啓動的時候,只有同時調用停止和解綁兩個方法(stopService和unBindService的順序無關),服務纔會被銷燬。
注意:服務可以同時由多個組件多次以startService方式啓動,並且由其他組件進行關閉;但是綁定方式(bindService)同時只能有一個組件進行綁定,並且只有綁定服務的組件才能進行解綁。
前臺服務
前臺服務是藉助Notification來在通知欄顯示的服務,這種方式下的服務的優先級較高,當系統內部不足時會銷燬後臺服務而幾乎不會銷燬前臺服務。
使用前臺服務可以與用戶進行交互,例如下載文件、後臺播放音樂等,都可以使用前臺服務。前臺服務是在Service中調用startForeground方式,可以在onStartCommond也可以在onBind中調用,代表着兩種啓動方式都可以成爲前臺服務。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: 啓動服務");
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
NotificationCompat.Builder builder;
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// android 8.0以上需要配置Channel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId = "notification_channel_id";
NotificationChannel channel = new NotificationChannel(channelId, "channel_name", NotificationManager.IMPORTANCE_HIGH);
// 設置默認聲音
channel.enableLights(true);
channel.enableVibration(true);
manager.createNotificationChannel(channel);
builder = new NotificationCompat.Builder(getBaseContext(), channelId);
} else {
builder = new NotificationCompat.Builder(getBaseContext(),null);
}
builder.setContentTitle("服務標題")
.setContentText("服務內容")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis());
startForeground(1, builder.build());
return super.onStartCommand(intent, flags, startId);
}
IntentService
在服務中,所有的操作都是運行在主線程中的。因此,在服務中若是有耗時的操作的時候,都必須創建子線程,然後在子線程中進行執行,以避免ANR錯誤。
而Service的子類IntentService,是系統爲我們封裝好的一個服務,就是爲了解決耗時操作的情況的。其內部創建了一個線程,通過Handler來處理請求。使用它只需要重寫onHandleIntent方法即可,另外構造方法接收一個String參數,這個參數是用來命名當前服務所運行的線程的線程名的。
public class MyIntentService extends IntentService {
private static final String TAG = "MyService";
public MyIntentService() {
super("intent");
}
@Override
protected void onHandleIntent(Intent intent) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "onHandleIntent: 處理服務 "+Thread.currentThread().getName());
}
}
使用IntentService不用擔心關閉的問題,因爲IntentService每次被啓動的時候,都會將onHandleIntent中的操作封裝後分發到消息隊列中依次執行,當所有的任務都執行完畢的時候就會銷燬服務。由於這種服務是依次執行任務的,是串行的,不適用於大量的併發任務。
優點是使用簡單,另外,若是要重寫onCreate、onStartCommond、onDestroy,則必須調用其父類實現,以保證IntentService的正常運行。
總結
Serivice分爲前臺服務和後臺服務。後臺服務是運行在後臺並且沒有界面的,用戶是無法感知的;前臺服務通過Notification方式,可以在通知欄顯示,用戶也可以與之交互,它是在Service的內部通過startForeground方法實現的。
Service的啓動方式分爲直接啓動和綁定啓動。直接啓動方式下的服務將不依賴啓動組件,並且啓動後可以一直運行在後臺,直到手動停止。停止的方式有兩種,組件通過stopService停止服務或者服務內部調用stopSelf進行停止。綁定方式啓動的服務依賴於啓動組件,當組件銷燬的時候,服務也會自動銷燬,即使沒有調用解綁方法(unBindService)。
直接啓動的方法可以啓動多次,但只有第一次啓動的時候執行onCreate方法,後面只會執行onStartCommond,並且可以由任意組件停止服務。綁定啓動的服務只能被一個組件綁定,其他組件無法綁定無法解綁,只能由綁定的組件進行解綁。
Service是運行在主線程的,是無法進行耗時操作的。因此若是執行耗時操作需要在服務中開啓子線程進行執行任務,或者使用IntentService。IntentService將操作都放到子線程中進行執行,內部採用Handler方式。因此任務的執行是串行的,不適用於大量併發任務。