服务(Service)是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
服务可以与应用的生命周期良好集成,可以在应用进入后台或被销毁后继续运行。子进程通常会随着应用的关闭而终止,不适合长期后台运行。所以不能用子进程代替服务
另外,也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
当我们需要执行一些耗时操作,比如说发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,如果不将这类操作放在子线程里去运行,就会导致主线程被阻塞住,从而影响用户对软件的正常使用。
Android多线程编程其实并不比Java多线程编程特殊,基本都是使用相同的语法。
和许多其他的GUI库一样,Android的UI也是线程不安全的。也就是说,如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。Android是不允许在子线程中进行UI操作的。但是有些时候,我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件,对于这种情况,Android提供了一套异步消息处理机制,完美地解决了在子线程中进行UI操作的问题。
MainActivity中的代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final int UPDATE_TEXT =1;
private TextView text;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
//这里可以进行UI操作
text.setText("Nice to meet you!");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);//将Message对象发送出去
}
}).start();
break;
default:
break;
}
}
}
Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。其中Message和Handler在上一小节中我们已经接触过了,而MessageQueue和Looper对于你来说还是全新的概念,下面我就对这4个部分进行一下简要的介绍。
...
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Overridepublic void run() {
//在这里进行Ui操作,将结果显示到界面
responseText.setText(response);
}
});
}
其实就是一个异步消息处理机制的接口封装,它虽然表面上看起来用法更为简单,但其实背后的实现原理和上图的描述是一模一样的。
为了更加方便我们在子线程中对UI进行操作,Android还提供了另外一些好用的工具,比如AsyncTask。
借助AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程。当然,AsyncTask背后的实现原理也是基于异步消息处理机制的,只是Android帮我们做了很好的封装而已。
首先来看一下AsyncTask的基本用法,由于AsyncTask是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们可以为AsyncTask类指定3个泛型参数,这3个参数的用途如下:
❑ Params。在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
❑ Progress。后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
❑ Result。当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
因此,一个最简单的自定义AsyncTask就可以写成如下方式:
class DownloadTask extends AsyncTask {
...
}
这里我们把AsyncTask的第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传入参数给后台任务。第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位。第三个泛型参数指定为Boolean,则表示使用布尔型数据来反馈执行结果。
当然,目前我们自定义的DownloadTask还是一个空任务,并不能进行任何实际的操作,我们还需要去重写AsyncTask中的几个方法才能完成对任务的定制。经常需要去重写的方法有以下4个。
class DownloadTask extends AsyncTask {
@Override
protected void onPreExecute() {
progressDialog.show();//显示进度对话框
}
@Override
protected Boolean doInBackground(Void...params) {
try {
while (true) {
int downloadPercent = doDownload();//这是一个虚构的方法
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer...values) {
//在这里更新下载进度
progressDialog.setMessage("Downloaded " + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();//关闭进度对话框
//这里显示下载结果
if (result) {
Toast.makeText(context,"Download succeded",Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
}
}
}
定义一个服务。
新建一个ServiceTest项目,然后右击com.example. servicetest → New → Service → Service → MyService,并重写Service中的一些方法
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException(“Not yet implemented”);
}
@Override
public void onCreate() {
super.onCreate();
Log.d(“MyService”,“onCreate executed”);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(“MyService”,“onStartCommand executed”);
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d(“MyService”,“on Destroy executed”);
super.onDestroy();
}
}
MyService是继承自Service类的,说明这是一个服务。
加入了两个按钮,分别是用于启动服务和停止服务的。 3. 为按钮添加事件 public class MainActivity extends AppCompatActivity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button startService = (Button) findViewById(R.id.start_service); Button stopService = (Button) findViewById(R.id.stop_service); startService.setOnClickListener(this); stopService.setOnClickListener(this); }
@Override
public void onClick(View view) {
if (view.getId() == R.id.start_service) {
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务
Log.d("MainActivity","start_service");
} else if (view.getId() == R.id.stop_service) {
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);
Log.d("MainActivity","stop_service");
}
}
}
这里在onCreate()方法中分别获取到了Start Service按钮和StopService按钮的实例,并给它们注册了点击事件。然后在Start Service按钮的点击事件里,我们构建出了一个Intent对象,并调用startService()方法来启动MyService这个服务。在Stop Serivce按钮的点击事件里,我们同样构建出了一个Intent对象,并调用stopService()方法来停止MyService这个服务。
startService()和stopService()方法都是定义在Context类中的,所以在活动里可以直接调用这两个方法。注意,这里完全是由活动来决定服务何时停止的,如果没有点击Stop Service按钮,服务就会一直处于运行状态。那服务有没有什么办法让自己停止下来呢?当然可以,只需要在MyService的任何一个位置调用stopSelf()方法就能让这个服务停止下来了。
3. 活动和服务进行通信
现在我们希望活动和服务的关系更紧密一些呢?例如在活动中指挥服务去干什么,服务就去干什么。当然可以,这就需要借助我们刚刚忽略的onBind()方法了。
比如说,目前我们希望在MyService里提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。
public class MyService extends Service {
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
public void startDownload() {
Log.d("MyService","startDownload executed");
}
public int getProgress() {
Log.d("MyService","getProgress executed");
return 0;
}
}
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
//throw new UnsupportedOperationException("Not yet implemented");
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService","onCreate executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService","onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d("MyService","on Destroy executed");
super.onDestroy();
}
}
这两个按钮分别是用于绑定服务和取消绑定服务的,那到底谁需要去和服务绑定呢?当然就是活动了。当一个活动和服务绑定了之后,就可以调用该服务里的Binder提供的方法了。
3. 修改MainActivity中的代码,绑定服务
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (MyService.DownloadBinder) iBinder;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button) findViewById(R.id.start_service);
Button stopService = (Button) findViewById(R.id.stop_service);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
Button bindService = (Button) findViewById(R.id.bind_service);
Button unbindService = (Button) findViewById(R.id.unbind_service);
bindService.setOnClickListener(this);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.start_service) {
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent);//启动服务
} else if (view.getId() == R.id.stop_service) {
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);
} else if (view.getId() == R.id.bind_service) {
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);//绑定服务
} else if (view.getId() == R.id.unbind_service) {
unbindService(connection);//解绑服务
} else {
// 默认情况下的代码
}
}
}
服务也有自己的生命周期,前面我们使用到的onCreate()、onStartCommand()、onBind()和onDestroy()等方法都是在服务的生命周期内可能回调的方法。
一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand()方法。如果这个服务之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。服务启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用。
注意,虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了。
另外,还可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。
当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。
需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。
服务几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是,服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。
前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止服务被回收掉才使用前台服务的,有些项目由于特殊的需求会要求必须使用前台服务,比如说彩云天气这款天气预报应用,它的服务在后台更新天气数据的同时,还会在系统状态栏一直显示当前的天气信息。
public void onCreate() {
super.onCreate();
Log.d("MyService","onCreate executed");
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId = "default";
String channelName = "默认通知";
//new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
manager.createNotificationChannel(new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH));
}
Notification notification = new NotificationCompat.Builder(MyService.this,"default")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher_background)).build();
manager.notify(1,notification);
}
在本章一开始的时候我们就已经知道,服务中的代码都是默认运行在主线程当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。
所以,这个时候就需要用到Android多线程编程的技术,我们应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停止下来。所以,如果想要实现让一个服务在执行完毕后自动停止的功能,就可以这样写:
public class MyService extends Service {
...
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
//处理具体逻辑
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
...
}