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
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind: " );
return new JiaBinder();
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: " );
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: " );
new Thread(new Runnable() {
@Override
public 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() {
@Override
public 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() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.e(TAG, "onServiceConnected: " );
jiaBinder = (ServiceJia.JiaBinder) iBinder;
//连接成功,调用绑定的service中的方法
jiaBinder.doSomething("start conncetion");
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.e(TAG, "onServiceDisconnected: " );
}
@Override
public void onBindingDied(ComponentName name) {
Log.e(TAG, "onBindingDied: " );
}
@Override
public 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
*/
@Override
protected 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)
@Override
public 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
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public 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)
@Override
public 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");
}
}
}
});
本文主要是记录了四种使用服务经常遇到的问题,后面再分析生命周期和源码。
才疏学浅,如有错误,欢迎指正,多谢。