本文目录结构:
一、服务简介
二、多线程编程
三、服务的生命周期
四、服务的基本用法
五、前台服务
六、如何保证service不被杀死
一、服务简介
定义:
服务在Android中的运用主要是实现任务在后台的运行,特别是那些不需要和用户交互而且还要求长期运行的任务,比如下载资源。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
注意:
①服务并不是运行在一个独立的进程中的,而是依赖于创建服务时所在的应用进程。所以当该进程被杀掉时,所有依赖于该进程的服务也会停止运行。
②服务并不会自动开启线程,所有代码都是运行在主线程当中的。
二、多线程编程
Android中的多线程主要有Thread与Handler的结合使用、AsyncTask、HandlerThread以及IntentService四种方式
2.1 Thread与Handler的结合使用
2.1.1 创建方式
Thread的创建方法我就不多讲了,在我的文章《那些你所不知道的Java小知识(持续更新)》的第3点中可以看到。因为系统不建议在子线程中更新UI,但是有些时候我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的控件。因此Android为我们提供了一个异步消息处理机制。(至于为什么不建议,大家可以去看一下鸿洋的《Android中子线程真的不能更新UI吗?》,讲解的很详细)
2.1.2 异步消息处理机制
该机制主要由四个部分组成:Message,Handler,MessageQueue和Looper。
- Message 是在线程之间传递的,它可以携带少量的信息,用于在不同线程之间交换数据。每个线程可以有多个Message对象。
Message的创建方式有以下三种:
Message msg = new Message();
Message msg =Message.obtain();
Message msg = handler1.obtainMessage();
后两种方法都是从整个Message池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更推荐后两种方法。
- Handler 主要用于发送和处理Message。发送消息一般是使用Handler的sendMessage方法,消息经过一系列流程过后会传到Handler的handleMessage方法中。每个线程可以有多个Handler对象。
- MessageQueue 主要用于存储所有通过Handler发送的Message。每个线程只有一个MessageQueue对象。
-
Looper 相当于MessageQueue的管家,当调用Looper的loop方法之后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage方法中。每个线程只有一个Looper对象。
所以它们四个的数量关系是:每个线程只能有一个Looper和一个MessageQueue,但是可以有多个Handler和多个Message。
2.1.3 小例子
这里我们实现点击按钮之后在子线程中更新按钮的文字。
2.1.4 注意
① 主线程中Looper的轮询死循环为什么没有阻塞主线程?
Android是依靠事件驱动的,通过Looper.loop()不断进行消息循环,可以说activity的生命周期都是运行在Looper.loop()的控制之下,一旦退出消息循环,应用也就退出了。
② 使用Handler的postDelay()后消息队列会发生什么变化?
postDelay的Message并不是先等待一定时间再放到MessageQueue中,而是直接放入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,更近的则放入队头,保证队头的时间最小,队尾的最大。如果此时队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够的时间再唤醒执行该Message,否则唤醒后直接执行。
2.2 AsyncTask
2.2.1 前言
① AsyncTask的底层原理封装了线程池和Handler,在其之中有两个线程池:SerialExecutor用于任务的排队,默认是串行的线程池,在3.0以前核心线程数为5,线程池大小为128,而3.0以后改为同一时间只能处理一个任务;THREAD_POOL_EXECUTOR用于真正执行任务。
② AsyncTask是一个抽象类,使用前要先继承。(匿名内部类的方式除外)。
2.2.2 参数和方法说明
① 在继承的时候我们需要指定三个参数,它们代表的意义如下:
- Params:在执行AsyncTask时需要传入的参数,它可以在后台任务中使用。
- Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的参数作为单位。
-
Result:当后台任务执行完毕后,如果需要返回结果,则使用这里的参数作为返回类型。
所以,一个自定义的AsyncTask可以如下所示:
public class MyTask extends AsyncTask<Void,Integer,Boolean> {
...
}
② 接着我们需要重写其中的几个方法:
-
onPreExecute()
这个方法会在后台任务开始执行之前调用,我们可以在这里进行一些界面上的初始化操作,比如显示一个对话框。 -
doInBackground(Params... params)
这个方法中的所有代码都会在子线程中执行,我们应该在这里去处理所有的耗时操作。任务完成时就可以通过return语句将执行结果返回,如果指定第三个参数为Void就不需要返回了。因为代码都是在子线程中执行的,所以注意不能再这里更新UI。如果需要反馈当前后台任务的执行进度,可以调用publishProgress(Progress... progress)方法来实现。 -
OnProgressUpdate(Progress... progress)
当后台任务调用publishProgress方法后,该方法很快就会被调用,其中的参数就是publishProgress传过来的。该方法里面可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。 -
onPostExecute(Result result)
当后台任务执行完毕并通过return语句返回时,该方法很快就会被调用。返回的结果就会被作为参数传到该方法中。我们可以利用返回的数据来进行一些UI操作,比如提醒任务执行的结果,以及关闭对话框等。
所以,一个完整的AsyncTask可以如下所示:
public class MyTask extends AsyncTask<Void,Integer,Boolean> {
@Override
protected void onPreExecute() {
dialog.show();//显示对话框
}
@Override
protected Boolean doInBackground(Void... voids) {
try{
while(true){
int downloadProgress = downloadAndGetProgress();//这是一个虚构的方法
publishProgress(downloadProgress);
if (downloadProgress >= 100){
break;
}
}
}catch (Exception e){
return false;//下载失败
}
return true;//下载成功
}
@Override
protected void onProgressUpdate(Integer... values) {
//更新下载进度
dialog.setMessage("Download " + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
dialog.dismiss();//关闭对话框
if (result){
//提示下载成功
}else{
//提示下载失败
}
}
}
如果想要启动这个后台任务,只需要执行new MyTask().execute();即可。
2.2.3 注意
① 不要直接调用onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()和onCancelled()方法。
② 一个异步对象只能调用过一次execute方法。
2.3 HandlerThread
2.3.1 前言
HandlerThread是一种具有消息循环的线程,其内部可使用Handler。通俗来讲,与子线程绑定的handler发送出去的消息,是在子线程当中执行的。
2.3.2 使用方法
整体思路:将子线程与workHandler绑定之后,在要进行耗时操作的地方通过workHandler发送消息,然后在通过mainHandler发送消息进行主线程的UI更新。
//第零步,创建主线程的handler,这里我称之为主handler
mainHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
btn_send.setText("after");
break;
}
}
};
//第一步,创建handlerThread对象
final HandlerThread handlerThread = new HandlerThread("handlerThread");
//第二步,启动handlerTh
handlerThread.start();
//第三步,创建与handlerThread绑定的handler,这里我称之为工作handler
//其发送的消息都是在子线程中执行的
final Handler workHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what){
case 0:
//这里做耗时操作
Log.i(TAG, "我做了耗时操作。");
//做完耗时操作通过mainHandler进行UI的更新
mainHandler.sendEmptyMessage(1);
break;
}
return false;
}
});
//第四步,通过workHan发送消息来做耗时操作
btn_send = findViewById(R.id.btn_send);
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
workHandler.sendEmptyMessage(0);
}
}).start();
}
});
//第五步,退出消息循环
btn_quit = findViewById(R.id.btn_quit);
btn_quit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
handlerThread.quit();
}
}).start();
}
});
2.3.3 注意
① 如何将一个Thread线程变成一个Looper线程?
通过Looper.prepare()可将一个一个Thread线程变成一个Looper线程。
② Looper线程有什么特点?
它通过MessageQueue来存放消息,Looper.loop()进行消息轮询。
③ 可以在子线程直接new一个Handler吗?不行的话要怎么做?
因为子线程的Looper需要手动去创建,所以不可以直接new。
要new一个Handler的话可以这样做:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//为子线程创建Looper
new Handler(){
@Override
public void handleMessage(Message msg) {
//这里的消息处理是在子线程中执行的
}
};
Looper.loop();//开启消息轮询
}
}).start();
在ActivityThread里调用了 Looper.prepareMainLooper()方法创建了主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接在主线程使用 Handler 了
2.4 IntentService
2.4.1 前言
不同于线程,它是服务,优先级比线程高,更不容易被系统杀死,因此较适合执行一些高优先级的后台任务;
不同于普通服务,它可自动创建子线程来执行后台任务,并且任务执行完毕后会自动退出。
2.4.2 使用方法
//第一步,自定义服务
public class MyIntentService extends IntentService {
public MyIntentService(){
//必须调用父类的有参构造函数
super("ha");//这里的ha是随便写的
}
//重写onHandleIntent方法
@Override
protected void onHandleIntent( Intent intent) {
//做耗时操作,因为该方法是在子线程中执行的
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("MyIntentService", "MyIntentService onDestroy");
}
}
//第二步,注册服务(在AndroidManifest文件中)
<service android:name=".MyIntentService"/>
//第三步,启动服务(在activity中)
Intent intent = new Intent(MainActivity.this,MyIntentService.class);
startService(intent);
2.4.3 注意
① IntentService与Activity间通信:
Activity传值给IntentService:Intent、EventBus等
IntentService传值给Activity:广播、EventBus等(回调我试过了,竟然不行!)
② 为何不用bindService方式启动IntentService?
IntentService的工作原理是,在IntentService的onCreate里创建一个HandlerThread,并利用其内部的Looper实例化一个ServiceHandler对象;而这个ServiceHandler用于处理消息的handleMessage方法会去调用IntentService的onHandleIntent方法,这也是为什么可在该方法中去处理后台任务的原因。当有Intent任务请求时会把Intent封装成Message,然后ServiceHandler会把消息发送出去,而发送消息是在onStartCommand中完成的,只能通过startService方法才可走该生命周期方法,所以不能用bindService方式启动IntentService。
三、服务的生命周期
3.1 通过startService方式启动服务
- 生命周期:onCreate -> onStartCommand -> onDestroy
- 通过stopService或者stopSelf方法停止服务。
- 一旦服务开启就跟调用者(开启者)没有任何关系了。
- 开启者退出了,开启者挂了,服务还在后台长期的运行。
- 开启者不能调用服务里面的方法。
3.2 通过bindService方式启动服务
- 生命周期:onCreate -> onBind -> onUnBind -> onDestroy
- 通过unbindService方法停止服务。
- 该方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。
- 绑定者可以调用服务里面的方法。
- 常用于Service和Activity间通信。
3.3 注意
① 当服务在启动之前还没有创建过,onCreate方法会先执行,否则不会再执行。
② 一个Activity先start一个Service之后,再bind时会回调什么方法?此时如何才能回调Service的destroy方法呢?
只会回调onBind方法。这种情况下要同时调用stopService和unbindService方法。
四、服务的基本用法
4.1 startService方式
//第一步,自定义类继承Service,必须重写onBind方法
public class MyService extends Service {
private static final String TAG = "MyService";
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return null;
}
//第二步,选择性的重写onCreate,onStartCommand和onDestroy方法
@Override
public void onCreate() {
Log.i(TAG, "onCreate: ");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
super.onDestroy();
}
}
//第三步,注册服务
<service android:name=".MyService"/>
//第四步,启动或者停止服务
//启动服务
Intent intent = new Intent(Main3Activity.this,MyService.class);
startService(intent);
//停止服务
Intent intent = new Intent(Main3Activity.this,MyService.class);
stopService(intent);
4.2 bindService方式(也是service与activity通信的方式)
整体思路:
首先,自定义类继承Binder,按照自己的业务逻辑写相应的方法供activity调用。
然后,自定义类继承Service,在onBind方法中返回自定义的Binder。
接着,实例化ServiceConnection对象,重写onServiceConnected和onServiceDisconnected方法。
最后,bindService绑定服务即可。
public class MyService extends Service {
private static final String TAG = "MyService";
private DownloadBinder mBinder = new DownloadBinder();
//自定义类继承Binder
class DownloadBinder extends Binder {
public void startDownload(){
Log.i(TAG, "startDownload: ");
}
public int getProgress(){
Log.i(TAG, "getProgress: ");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return mBinder; //返回自定义的Binder对象
}
@Override
public void onCreate() {
Log.i(TAG, "onCreate: ");
super.onCreate();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
super.onDestroy();
}
}
public class Main3Activity extends AppCompatActivity {
private static final String TAG = "Main3Activity";
private Button btn_send,btn_quit;
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
//service与activity绑定成功时调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
//service与activity绑定断开时调用
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
btn_send = findViewById(R.id.btn_send);
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Main3Activity.this,MyService.class);
//第三个参数表示在活动和服务进行绑定后自动创建服务
bindService(intent,connection,BIND_AUTO_CREATE);
}
});
btn_quit = findViewById(R.id.btn_quit);
btn_quit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//解除绑定
unbindService(connection);
}
});
}
}
五、前台服务
前言
服务几乎都是在后台运行的,系统优先级还是比较低的,当系统出现内存不足时就有可能会回收掉正在后台运行的服务。此时为了防止服务被回收掉,就可以使用前台服务。前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的图标在系统状态栏显示,下拉状态栏可以看到更加详细的信息,非常类似于通知的效果。
使用方法
public class MyService extends Service {
...
@Override
public void onCreate() {
super.onCreate();
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
Notification notification = new Notification.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1,notification);
}
注意
现在Android对于通知栏已经做了修改,我们还需要去做版本适配,在这里我就不展开来讲了,贴一个郭神的文章吧Android通知栏微技巧,8.0系统中通知栏的适配
六、保证service不被杀死的方法
- 在Service的onStartCommand方法中设置flages值为START_STICKY,使得Service被杀死后尝试再次启动Service。
- 提升Service优先级,比如设置为一个前台服务。
- 在Activity的onDestroy通过发送广播,并在广播接收器的onReceive中启动service。