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");}}}});
本文主要是记录了四种使用服务经常遇到的问题,后面再分析生命周期和源码。
才疏学浅,如有错误,欢迎指正,多谢。