Service一定要開啟子線程才可以執行耗時任務嗎?不完全是吧。
Service是Android系統中的四大組件之一,它是一種沒有可視化界面,運行于后臺的一種服務程序。屬于計算型組件,用來在后臺執行持續性的計算任務,重要性僅次于Activity活動。
本文分四塊來記錄,分別是普通Service、IntentService、ForegroundService,其中普通service根據運行狀態不同分為startService啟動的Service和bindService綁定的Service。
先定義一個Service子實現類,普通Service的啟動的Service和bindService綁定的Service共用這一個Service類。
public class ServiceJia extends Service {private static final String TAG = ServiceJia.class.getName();@Nullable@Overridepublic IBinder onBind(Intent intent) {Log.e(TAG, "onBind: " );return new JiaBinder();}@Overridepublic void onCreate() {super.onCreate();Log.e(TAG, "onCreate: " );}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.e(TAG, "onStartCommand: " );new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(30000);Log.e(TAG, "onStartCommand: 等了半分鐘" );} catch (InterruptedException e) {e.printStackTrace();}}}).start();Log.e(TAG, "onStartCommand: -end" );return super.onStartCommand(intent, flags, startId);}//用于bindService時和activity交互public static class JiaBinder extends Binder {public void doSomething(String something){Log.e(TAG, "JiaBinder --doSomething: "+something );}public void onClick(String something){Log.e(TAG, "JiaBinder --onClick: "+something );}}}
1、startService啟動Service和停止
通過startService()創建啟動的service可以一直運行下去,必須自己調用stopSelf()方法或者其他組件調用stopService()方法來停止。適用于不和其他組件通訊的業務。
?//不可以阻塞線程做耗時操作,比如這樣:
try {
?????Thread.sleep(30000);
?????} catch (InterruptedException e) {
???????????e.printStackTrace();
??????}
?程序閃退報錯:
2023-12-02 09:46:39.584 1592-1795/? E/ActivityManager: ANR in com.example.testdemo3?PID: 29467
Reason: executing service com.example.testdemo3/.service.ServiceJia
Load: 46.58 / 46.1 / 46.01
CPU usage from 70164ms to 0ms ago (2023-12-02 09:45:28.698 to 2023-12-02 09:46:38.862) with 99% awake:
啟動和關閉服務:
startService.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {String text = startService.getText().toString();if ("startService".equals(text)){startService.setText("stopService");serviceIntent = new Intent(ServiceActivity.this, ServiceJia.class);startService(serviceIntent);}else if ("stopService".equals(text)){if (stopService(serviceIntent)){ //真停了再修改按鈕的文字顯示startService.setText("startService");}}}
});
2、bindService綁定Service和解綁
調用bindService()來創建,調用方可以通過一個IBinder接口和service進行通信,需要通過ServiceConnection建立連接。多用于有交互的場景。
只能調用方通過unbindService()方法來斷開連接。調用方可以和Service通訊,并且一個service可以同時和多個調用方存在綁定關系,解除綁定也需要所有調用全部解除綁定之后系統才會銷毀service。
綁定和解綁服務:
String text = bindService.getText().toString();
if ("bindService".equals(text)){
if (serviceIntent == null)serviceIntent = new Intent(ServiceActivity.this, ServiceJia.class);if (bindService(serviceIntent,serviceConnection, Context.BIND_AUTO_CREATE)){bindService.setText("unBindService");}
}else if ("unBindService".equals(text)){unbindService(serviceConnection);bindService.setText("bindService");
}
//連接成功,可以通過Binder調用綁定的service中的方法,比如jiaBinder.doSomething("start conncetion");
serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {Log.e(TAG, "onServiceConnected: " );jiaBinder = (ServiceJia.JiaBinder) iBinder;//連接成功,調用綁定的service中的方法jiaBinder.doSomething("start conncetion");}@Overridepublic void onServiceDisconnected(ComponentName componentName) {Log.e(TAG, "onServiceDisconnected: " );}@Overridepublic void onBindingDied(ComponentName name) {Log.e(TAG, "onBindingDied: " );}@Overridepublic void onNullBinding(ComponentName name) {Log.e(TAG, "onNullBinding: " );}
};
3、IntentService
它可以解決第一句中“Service一定要開啟子線程才可以執行耗時任務嗎?”的問題。
IntentService 也是 Android 中的一個服務,繼承自 Service 類,并在單獨的工作線程中執行任務,避免了多線程處理異步任務。我們可以在onHandleIntent生命周期方法中執行耗時任務,不用開啟子線程:
注意這里是繼承IntentService ;
還有就是繼承IntentService必須聲明一個空參構造方法否則會報錯刪除?。
public class MyIntentService extends IntentService {public static final String TAG = "MyIntentService";/*** Creates an IntentService. Invoked by your subclass's constructor.* @param name Used to name the worker thread, important only for debugging.* 如果name字段為空,字符串就是worker thread的名字*/public MyIntentService(String name) {super(name);}public MyIntentService() {super(TAG);}/*** 可以做耗時任務* @param intent*/@Overrideprotected void onHandleIntent(@Nullable Intent intent) {String type = intent.getStringExtra("type");try {Log.e(TAG, "onHandleIntent: -start" );Thread.sleep(10000);Log.e(TAG, "onHandleIntent: -end" );} catch (InterruptedException e) {e.printStackTrace();}}
啟動和關閉IntentService:
intentService.setOnClickListener(new View.OnClickListener() {@RequiresApi(api = Build.VERSION_CODES.O)@Overridepublic void onClick(View v) {String text = intentService.getText().toString();if ("startIntentService".equals(text)){intentService.setText("stopIntentService");intentServiceIntent = new Intent(ServiceActivity.this, MyIntentService.class);startService(intentServiceIntent);}else if ("stopIntentService".equals(text)){boolean b = stopService(intentServiceIntent);Log.e(TAG, "onClick: stopIntentService= "+b );if (b){ //真停了再修改按鈕的文字顯示intentService.setText("startIntentService");}}}
});
這里有一個比較坑的地方就是,必須指定一個空參構造函數,否則運行就會報錯。
4、前臺服務
前臺服務用于執行用戶可察覺的操作。前臺服務會顯示狀態欄通知,讓用戶知道當前應用正在前臺執行任務并消耗系統資源。用戶可以通過點擊通知來與應用進行交互。
值得注意的兩個點:
- 除了在AndroidManifest.xml注冊服務之外,還需要申請前臺服務的權限,否則會閃退。
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- Build.VERSION_CODES.O及以上的系統,需要單獨NotificationManager需要設置NotificationChannel否則會閃退,具體見下面代碼。
閃退報錯信息:“android.app.RemoteServiceException: Bad notification for startForeground”
使用上和普通Service類似同樣繼承Service,但是要設置前臺服務通知
startForeground(1000,notification);定義前臺服務:
public class MyForegroundService extends Service {@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Notification.Builder builder = null;if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {builder = new Notification.Builder(this,getChannelId("com.example.testdemo.service","MyForegroundService"));Notification notification = builder.setContentTitle("我是前臺服務").setContentText("目前運行平穩").setSmallIcon(R.mipmap.ic_launcher).build();//設置前臺服務startForeground(1000,notification);}else {//設置前臺服務startForeground(1000,new Notification());}//被殺以后還會再次創建return START_STICKY;}@RequiresApi(api = Build.VERSION_CODES.O)private String getChannelId(String channelId, String channelName) {NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE);notificationChannel.setLightColor(Color.BLUE);notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);notificationManager.createNotificationChannel(notificationChannel);return channelId;}
}
啟動和關閉前臺服務:
foregroundService.setOnClickListener(new View.OnClickListener() {@RequiresApi(api = Build.VERSION_CODES.O)@Overridepublic void onClick(View v) {String text = foregroundService.getText().toString();if ("startForegroundService".equals(text)){foregroundService.setText("stopForegroundService");foregroundServiceIntent = new Intent(ServiceActivity.this, MyForegroundService.class);
// startService(intentServiceIntent);startForegroundService(foregroundServiceIntent);}else if ("stopForegroundService".equals(text)){boolean b = stopService(foregroundServiceIntent);Log.e(TAG, "onClick: stopIntentService= "+b );if (b){ //真停了再修改按鈕的文字顯示foregroundService.setText("startForegroundService");}}}});
本文主要是記錄了四種使用服務經常遇到的問題,后面再分析生命周期和源碼。
才疏學淺,如有錯誤,歡迎指正,多謝。