脑子里记了太多,最近感觉不太够用了,还是决定来CSDN给存起来,方便自己以后翻阅也便利别人,一点知识总结,如有错误各位过客可在评论区指出。
Service内容基本会在这章总结到,总结顺序如下:
- Service概述
- Service生命周期概述
- Service分类:
* 按启动方式分类:通过Context.startService()或Context.bindService启动(顺带讲到Service与Thread区别)
* 按寄存方式分类:本地服务,远程服务(讲到使用AIDL进行进程间通信)
* 按运行方式分类:前台服务,后台服务
4.IntentService使用和源码分析
Service概述
什么是Service,相信大家应该不会陌生了,Service是Android四大组件之一,是一个可以在后台长时间运行操作而没有用户界面的应用组件,服务可由其他应用组件启动(如Activity),服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行,即使我们的APP已经退出桌面了。Service没有界面与用户交互,但是也可以为Service添加界面,后面娓娓道来。
Service知识总结(相关代码请移步https://github.com/Mangosir/ServiceReview/tree/master)
Service生命周期概述:
onCreate:当用Context.startService,Context.bindService启动服务时,会回调onCreate,在整个生命周期中,只会回调一次。也是生命周期方法中第一个被回调的,系统将调用次方法来执行一次性设置程序
onStartCommand:用Context.startService启动Service会回调这个方法,每次启动都会回调,Context.bindService启动Service不会回调这个方法。
onBind:Context.bindService启动Service回调这个方法,启动者可通过该方法返回对象来对Service对象进行操控。
onReBind:当使用startService启动Service,又调用bindService启动Service,且 onUnbind 返回值为 true 时,下次再次调用 Context.bindService 将触发方法。
onUnBind:用 Context.unbindService 触发此方法,默认返回 false, 当返回值 true 后,再次调用 Context.bindService 时将触发 onRebind 方法。
onDestory:1.以Context.startService启动service,调用Context.stopService停止服务回调此方法;2.以Context.bindService启动service,以Context.unbindService停止服务回调此方法;3.先以Context.startService 启动服务,再用Context.bindService绑定服务,结束时必须先调用Context.unbindService解绑再使用Context.stopService停止service才会回调此方法,否则会报错。这里做一些资源释放操作
(onStart方法已经过时,这里就不再赘述)
Service的分类:
1.按启动方式分两类:
第一类:通过Context.startService()启动,调用Context.stopService()停止服务。
第二类:通过Context.bindService启动service,以Context.unbindService停止服务。
第一类:
新建一个ServiceDemo类继承Service类,如下
public class ServiceDemo extends Service{
private String TAG = "ServiceDemo";
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG,"onBind");
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy");
}
}
然后在MainActivity的布局中添加两个按钮,一个启动服务,一个停止服务
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/start"
android:layout_width="100dp"
android:layout_height="50dp"
android:gravity="center"
android:background="@color/colorAccent"
android:text="启动服务"/>
<TextView
android:id="@+id/stop"
android:layout_marginTop="20dp"
android:layout_width="100dp"
android:layout_height="50dp"
android:gravity="center"
android:background="@color/colorAccent"
android:text="停止服务"/>
</LinearLayout>
然后在监听方法中启动服务:
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
@Bind(R.id.start)TextView start;
@Bind(R.id.stop)TextView stop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.start)
public void start() {
Intent intent = new Intent(this, ServiceDemo.class);
startService(intent);
}
@OnClick(R.id.stop)
public void stop() {
Intent intent = new Intent(this, ServiceDemo.class);
stopService(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
ButterKnife.unbind(this);
}
}
Intent intent = new Intent(this, ServiceDemo.class);
startService(intent);
这里用到了ButterKnife注解框架,这里先不做过多解释,以后再出一篇使用详述。
这里可以看到当点击按钮启动Service时,log日子打印结果是
11-06 11:03:58.192 6941-6941 E/ServiceDemo: onCreate
11-06 11:03:58.192 6941-6941 E/ServiceDemo: onStartCommand
当多次点击启动按钮,日志打印结果是
11-06 11:03:58.192 6941-6941 E/ServiceDemo: onCreate
11-06 11:03:58.192 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:20.231 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:20.690 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:20.958 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:21.262 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:21.529 6941-6941 E/ServiceDemo: onStartCommand
启动总结:
从这可以看出,一个Service是可以多次启动的,但是onCreate只会回调一次,而onStartCommand是启动一次就回调一次。所以如果你的Service有多次启动的可能性的话,有一些初始化的操作就放在onCreate里,避免无故开辟内存。但是onStartCommand方法却可以回调多次,我们先看onStartCommand这个方法是有返回值的并且是我们开发者可以控制返回什么值。我们知道当系统内存是有限的,当系统内存资源不足,Service是会被销毁的,如果你在Service里做了什么重要事情,那被销毁显然是你不愿意看到的,所以要有一种方法让系统帮我们重启该服务,那要不要重启就由这个返回值决定了,看下方:
- START_STICKY:如果service进程被kill掉,系统会尝试重新创建Service,如果在此期间没有任何启动命令被传递到Service,那么参数intent将为null。
- START_NOT_STICKY:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统不会自动重启该服务。
- START_REDELIVER_INTENT:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统会自动重启该服务,并将intent的值传入。
- START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
如果把返回值设置成START_STICKY和START_REDELIVER_INTENT,这样就可以处于不死状态了(这样做其实是不好的)还有一种情况是用户主动在设置界面把你的Service给杀死,但是这会回调onDestroy,你可以在这里发送广播重新启动。
onStartCommand()第二个输入参数flags正是代表此次onStartCommand()方法的启动方式,正常启动时,flags默认为0,被kill后重新启动,参数分为以下两种:
- START_FLAG_RETRY:代表service被kill后重新启动,由于上次返回值为START_STICKY,所以参数 intent 为null
- START_FLAG_REDELIVERY:代表service被kill后重新启动,由于上次返回值为START_REDELIVER_INTENT,所以带输入参数intent
好,我们继续往下说,当我们点击按钮停止服务时
Intent intent = new Intent(this, ServiceDemo.class);
stopService(intent);
此时log打印为
11-06 11:20:48.120 6941-6941 E/ServiceDemo: onDestroy
也就是回调了onDestroy,在这里做一些释放内存的事情。
注意了,服务可以多次启动,但是停止只能停一次,还有服务必须在AndroidManifest.xml里注册:
<service android:name="com.mangoer.servicereview.Service.ServiceDemo"></service>
如果以这种方式启动服务,服务就会在后台无限期运行,即使启动的Activity被销毁了,也不会对Service有任何影响;除非手动调用stopService,服务才会停止
总结使用步骤:
- 定义一个类继承Service
- 在Manifest.xml中注册该Service
- 使用Context.startService启动服务
- 不使用时使用stopService停止服务
第二类:
新建ServiceDemo2类继承Service类
public class ServiceDemo2 extends Service{
private String TAG = "ServiceDemo";
private MyBind myBind;
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate");
myBind = new MyBind();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG,"onBind");
return myBind;
}
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
Log.e(TAG,"onRebind");
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG,"onUnbind");
return super.onUnbind(intent);
}
public class MyBind extends Binder {
public void beginDown() {
Log.e(TAG,"begin");
down();
}
}
private void down() {
Log.e(TAG,"down");
}
}
然后在布局里加两个按钮一个绑定服务,一个解绑服务,然后在MainActivity里进行操作
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
@Bind(R.id.bind)TextView bind;
@Bind(R.id.unBind)TextView unBind;
private ServiceDemo2.MyBind myBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
// thread id = " + Thread.currentThread().getId() + ",thread name = " + Thread.currentThread().getName()
Log.e(TAG,"onCreate");
}
@OnClick(R.id.bind)
public void bind() {
bind.setText("绑定成功");
unBind.setText("解绑服务");
Intent intent = new Intent(this, ServiceDemo2.class);
bindService(intent,connection,BIND_AUTO_CREATE);
}
@OnClick(R.id.unBind)
public void unBind() {
bind.setText("绑定服务");
unBind.setText("解绑成功");
unbindService(connection);
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBind = (ServiceDemo2.MyBind) service;
//这里就可以调用Service类的方法了
myBind.beginDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
ButterKnife.unbind(this);
}
}
看启动方式:
Intent intent = new Intent(this, ServiceDemo2.class);
bindService(intent,connection,BIND_AUTO_CREATE);
我们先介绍下这三个参数:
- Intent:构建的连接Activity和Service的对象
- ServiceConnection:在MainActivity创建的匿名内部类connection,重写两个方法,就是通过这个类的onServiceconnected方法获得MyBind实例,从而让Activity可以调用Service类的公共方法;onServiceDisconnected是服务意外中断的时候调用的,不是开发者主动停止服务调用的
- flags:这是一个int型数,一般填入Context.BIND_AUTO_CREATE,表示Activity和Service建立关联后自动创建Service,可以让Service的onCreate执行,但是onStartCommand不会执行
就这样Service就运行起来了,
看下log日志:
11-06 14:24:55.982 7123-7123 E/ServiceDemo: onCreate
11-06 14:24:55.985 7123-7123 E/ServiceDemo: onBind
11-06 14:24:55.987 7123-7123 E/MainActivity: onServiceConnected
11-06 14:24:55.987 7123-7123 E/ServiceDemo: begin
11-06 14:24:55.987 7123-7123 E/ServiceDemo: down
也就是服务一绑定,会执行onCreate,再回调onBind,最后会走到ServiceConnection类的onServiceConnected中。
再调用解绑服务:
11-06 14:37:55.575 14517-14517 E/ServiceDemo: onUnbind
11-06 14:37:55.576 14517-14517 E/ServiceDemo: onDestroy
总结使用步骤:
- 自定义Service类,继承自Service,创建一个继承Binder类的内部类
- 在Service类的onBind方法中返回上述Binder实例
- 在客户端中,使用bindService绑定服务端,从onServiceConnected方法中接收Binder对象并调用该对象的方法
- 使用unbindService使客户端与服务端解除绑定
注意:
- 不管是startService还是bindService,最后都要stopService和unBindService与之对应,要不然容易造成内存泄漏。
- bindService后调用unBindService成功后,如果再调用unBindService将会报异常,可以加一个boolean变量判断。
- 这里有一种特殊情况是先startService,此时回调onCreate->onStartCommand,然后再bindService,回调onBind->onServiceConnected;这时候要想销毁Service必须调用unBindService,再调用stopService。
- bindService成功后,Service就与当前Activity绑定了,它就跟随Activity生命周期走了,如果服务没有销毁,而与之绑定的Activity销毁了,那这个绑定的Service也会被销毁,但是Service里启动的子线程不会被销毁。
- 服务是运行在后台,没错,但是不能进行耗时操作,否则会ANR,页面卡死情况;如果有耗时操作一定要新建一个子线程,放在里进行,这里也说明Service和Thread不是同一种东西,就跟猫和鱼一样,是两个不同物种。但是通常情况下这两个是组合起来使用。
- 有人会问了,既然Service不能做耗时操作,就是做也得放在子线程里去做,那为啥不直接在Activity里开一个子线程去做呢,反而弄Service这么麻烦。有道理,但是Activity是很难对线程去操作的,当线程所在的Activity销毁了,那这个线程就是脱缰的野马,没人能管的了了,你没办法再获取这个线程的实例了,除非把进程给杀了;但是如果线程放在Service里,就算Activity销毁了,以后只要重新与这个Service进行绑定,在onServiceConnected获取binder实例,这样就可以继续操作线程了,要它GG还是要它继续与你作伴,就看你高兴了。
- 通过startService启动服务后,服务就与启动它的组件没有联系了,组件销毁了,服务还是继续运行;通过bindService启动服务,与之绑定的组件就可以控制Service的具体执行逻辑了。
2.按Service寄存方式分两类:
1.本地服务 (Local Service): 寄存于当前的进程当中,当前进程结束后 Service 也会随之结束;因为处于同一个进程当中,所以Service 可以随时与 Activity 等多个部件进行通信,不需要IPC和AIDL;Service服务不会自动启动线程,如果没有人工调用多线程方式进行启动,Service将寄存于主线程当中。
2.远程服务 (Remote Service ): 独立寄存于另一进程中, 服务常驻后台,通过 AIDL (Android Interface Definition Language)接口定义语言,实现Android设备上的两个进程间通信(IPC)。
本地服务:
我们先打开MainActivity,在onCreate里打一个显示当前线程的log,
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Log.e(TAG,"onCreate thread id = " + Thread.currentThread().getId() + ",thread name = " + Thread.currentThread().getName() );
}
再看日志,表明当前Activity运行在主线程
11-06 15:31:22.269 32438-32438 E/MainActivity: onCreate thread id = 1,thread name = main
再启动Service,在onCreate里加个log
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate thread id = " + Thread.currentThread().getId() + ",thread name = " + Thread.currentThread().getName());
}
再在MainActivity里启动服务(用bindService是一样的)
Intent intent = new Intent(this, ServiceDemo2.class);
startService(intent);
看日志
11-06 15:31:23.645 32438-32438 E/ServiceDemo2: onCreate thread id = 1,thread name = main
11-06 15:31:23.647 32438-32438 E/ServiceDemo2: onStartCommand
这就可以看出来Service与Activity运行在同一个main线程,这就是本地服务了。
远程服务:
讲远程服务的时候先讲一下Service在Androidmanifest里的android:process配置属性,这个是表示这个Service是否在另一个进程中运行,不设置默认为本地服务,设置为:remote就为远程服务。那如何将远程服务用起来呢,请看下方:
由于远程服务涉及到AIDL进程间通信,以及两个进程间简单数据和复杂类型数据传递,篇幅较大,就不在这叙述了,请看博主另一篇文章http://blog.csdn.net/qq_30993595/article/details/78481716
3.按Service运行方式分两类:前台服务和后台服务。
前台Service和后台Service(普通)最大的区别就在于:
- 前台Service在下拉通知栏有一条显示通知(主要做一些需要用户知道的事,但退出APP还能继续做,像音乐播放器类似的应用,在下拉栏有一些当前播放歌曲的信息和进行一些简单操作;有些下载功能放在Service里也会在下拉栏显示当前下载进度),但后台Service没有(主要是默默的做一些用户不知道的事,像收集定位数据,更新天气数据)
- 前台Service优先级较高,不会由于系统内存不足而被回收;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收
来看看前台Service什么样,新建一个类ServiceDemo3继承Service:
public class ServiceDemo3 extends Service{
private String TAG = "ServiceDemo3";
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG,"onBind");
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate ");
//添加下列代码将后台Service变成前台Service
//构建点击通知栏后打开MainActivity的Intent对象
// 在API11之后构建Notification的方式
Intent nfIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = getActivity(this,0,nfIntent,0);
Notification.Builder builder = new Notification.Builder(getApplicationContext()); //获取一个Notification构造器
builder .setContentIntent(pendingIntent) // 设置PendingIntent
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 设置下拉列表中的图标(大图标)
.setContentTitle("前台服务") // 设置下拉列表里的标题
.setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
.setContentText("显示的内容") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
Notification notification = builder.build(); // 获取构建好的Notification
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
// 参数一:唯一的通知标识;参数二:通知消息。
startForeground(110, notification);// 让Service变成前台Service,并在系统的状态栏显示出来
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
// 停止前台服务--参数:表示是否移除之前的通知
stopForeground(true);
super.onDestroy();
Log.e(TAG,"onDestroy");
}
}
在onCreate方法中构建一个通知Notification,这样在MainActivity里就可以启动服务了
Intent intent = new Intent(this, ServiceDemo3.class);
startService(intent);
这样就是一个前台服务了,当然这个显示通知这个代码你可以放在你自己具体的逻辑里。
后台服务就不多叙述了,把通知这段代码去掉再启动这个服务就是一个后台服务了。
IntentService:
IntentService是Service的一个子类,它可以自己处理异步请求,在它内部有一个工作线程来处理耗时请求,可以启动IntentService多次,每个耗时操作会以队列的方式在onHandlerIntent方法中回调处理,每次只会执行一个工作线程,全部处理完,IntentService会自动结束,不需要开发者去结束。
先来个IntentService的简单使用,然后再分析源码:
先新建一个类ServiceDemo4
public class ServiceDemo4 extends IntentService{
private String TAG = "ServiceDemo4";
public ServiceDemo4() {
//调用父类的构造函数 构造函数参数=工作线程的名字
super("ServiceDemo4");
Log.e(TAG,"ServiceDemo4");
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate");
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.e(TAG,"onHandleIntent intent = " + intent.getStringExtra("params") + " thread id = " + Thread.currentThread().getId() + ",name = "+Thread.currentThread().getName());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy");
}
}
在配置文件里注册服务
<service android:name=".Service.ServiceDemo4"></service>
在MainActivity里启动服务
@OnClick(R.id.start)
public void start() {
Intent intent = new Intent(this, ServiceDemo4.class);
intent.putExtra("params","thread");
startService(intent);
}
我们先来看下日志:
11-07 11:17:12.657 18996-18996 E/MainActivity2: onCreate thread id = 1,name = main
11-07 11:17:35.215 18996-18996 E/ServiceDemo4: ServiceDemo4
11-07 11:17:35.217 18996-18996 E/ServiceDemo4: onCreate
11-07 11:17:35.217 18996-18996 E/ServiceDemo4: onStartCommand
11-07 11:17:35.218 18996-19347 E/ServiceDemo4: onHandleIntent intent = thread thread id = 613,name = IntentService[ServiceDemo4]
11-07 11:17:35.222 18996-18996 E/ServiceDemo4: onDestroy
当我再次点击启动任务按钮,日志如下:
11-07 11:21:50.631 18996-18996 E/ServiceDemo4: ServiceDemo4
11-07 11:21:50.632 18996-18996 E/ServiceDemo4: onCreate
11-07 11:21:50.633 18996-18996 E/ServiceDemo4: onStartCommand
11-07 11:21:50.633 18996-21742 E/ServiceDemo4: onHandleIntent intent = thread thread id = 614,name = IntentService[ServiceDemo4]
11-07 11:21:50.634 18996-18996 E/ServiceDemo4: onDestroy
可以得出
- IntentService的工作线程(613/614)与MainActivity(1)不在同一个线程
- 我这时候只在onHandleIntent打印了一个log,所以很快就结束任务,然后做完就执行onDestory;但我并没有手动调用stopService,所以IntentService是任务结束就自动销毁
- onHandleIntent里如何判断每次过来的请求要做什么事?答案在于startService时构建的intent里的putExtra()参数
- 每次IntentService结束后再startService都会开启一个与上次不同的线程去处理请求(613/614)
再来写个模拟耗时操作例子,第一次startService,IntentService还在onHandleIntent异步处理请求,没处理完再次startService,再次发一个耗时请求过去:
public class ServiceDemo4 extends IntentService{
private String TAG = "ServiceDemo4";
private LocalBroadcastManager mBroadcastManager;
private boolean isRunning = true;
private boolean isRunning_again = true;
private int progress = 0;
private int progress_again = 0;
public ServiceDemo4() {
//调用父类的构造函数 构造函数参数=工作线程的名字
super("ServiceDemo4");
Log.e(TAG,"ServiceDemo4");
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate");
//切记不要在构造方法里实例化这个本地广播,因为获取不到context作为参数
mBroadcastManager = LocalBroadcastManager.getInstance(this);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.e(TAG,"onHandleIntent intent = " + intent.getStringExtra("params") + " thread id = " + Thread.currentThread().getId() + ",name = "+Thread.currentThread().getName());
String parma = intent.getStringExtra("params");
if (!TextUtils.isEmpty(parma)) {
switch (parma) {
case "thread" :
//当请求为 thread 类型时,模拟下载进度回调,每隔2s加1,然后通过本地广播发出去;
isRunning = true;
while (isRunning) {
progress++;
if (progress>=5) {
isRunning = false;
}
sendThreadStatus(ACTION_THREAD,progress);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case "thread_again" :
//当请求为 thread_again 类型时,模拟下载进度回调,每隔2s加1,然后通过本地广播发出去;
isRunning_again = true;
while (isRunning_again) {
progress_again++;
if (progress_again>=5) {
isRunning_again = false;
}
sendThreadStatus(ACTION_THREAD_AGAIN,progress_again);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
// 发送线程结果
private void sendThreadStatus(String action, int progress) {
Log.e(TAG,"action="+action+",progress="+progress);
Intent intent = new Intent(action);
intent.putExtra("progress", progress);
mBroadcastManager.sendBroadcast(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy");
}
}
这里是在onHandleIntent里模拟耗时请求,获得结果后通过本地广播发出去,然后在MainActivity接受广播进行更新UI
public class MainActivity2 extends AppCompatActivity {
private static String TAG = "MainActivity2";
public final static String ACTION_THREAD = "com.mango.thread";
public final static String ACTION_THREAD_AGAIN = "com.mango.thread_again";
@Bind(R.id.msg)TextView msg;
@Bind(R.id.msg_again)TextView msg_again;
private LocalBroadcastManager mBroadcastManager;
private MyBoradCast myBoradCast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
ButterKnife.bind(this);
Log.e(TAG,"onCreate thread id = " + Thread.currentThread().getId()+ ",name = "+Thread.currentThread().getName());
mBroadcastManager = LocalBroadcastManager.getInstance(this);
myBoradCast = new MyBoradCast();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_THREAD);
intentFilter.addAction(ACTION_THREAD_AGAIN);
mBroadcastManager.registerReceiver(myBoradCast,intentFilter);
}
@OnClick(R.id.start)
public void start() {
Intent intent = new Intent(this, ServiceDemo4.class);
intent.putExtra("params","thread");
startService(intent);
}
@OnClick(R.id.start_again)
public void startAgain() {
Intent intent = new Intent(this, ServiceDemo4.class);
intent.putExtra("params","thread_again");
startService(intent);
}
@OnClick(R.id.stop)
public void stop() {
Intent intent = new Intent(this, ServiceDemo4.class);
stopService(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
ButterKnife.unbind(this);
mBroadcastManager.unregisterReceiver(myBoradCast);
}
public class MyBoradCast extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action != null) {
switch (action) {
case ACTION_THREAD :
int progress = intent.getIntExtra("progress",0);
msg.setText("ACTION_THREAD 当前进度 = "+progress);
break;
case ACTION_THREAD_AGAIN :
int progress_again = intent.getIntExtra("progress",0);
msg_again.setText("ACTION_THREAD_AGAIN 当前进度 = "+progress_again);
break;
}
}
}
}
}
这里先在onCreate里实例化广播MyBoradcast,然后通过本地广播注册(广播这个组件在Service讲完后再写一遍文章具体分析),然后在第一个按钮里启动服务,记得传一个params参数,看日志:
11-07 15:31:29.488 13558-13558 E/MainActivity2: onCreate thread id = 1,name = main
11-07 15:31:31.861 13558-13558 E/ServiceDemo4: ServiceDemo4
11-07 15:31:31.863 13558-13558 E/ServiceDemo4: onCreate
11-07 15:31:31.863 13558-13558 E/ServiceDemo4: onStartCommand
11-07 15:31:31.864 13558-13703 E/ServiceDemo4: onHandleIntent intent = thread thread id = 868,name = IntentService[ServiceDemo4]
11-07 15:31:31.865 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=1
11-07 15:31:33.867 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=2
当耗时操作计数到2时,再点击第二个按钮再次启动服务,看日志:
11-07 15:31:34.638 13558-13558 E/ServiceDemo4: onStartCommand
11-07 15:31:35.869 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=3
11-07 15:31:37.870 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=4
这里只回调了onStartCommad方法,并没有进入onHandleIntent方法,继续看日志:
11-07 15:31:39.872 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=5
11-07 15:31:41.873 13558-13703 E/ServiceDemo4: onHandleIntent intent = thread_again thread id = 868,name = IntentService[ServiceDemo4]
11-07 15:31:41.874 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=1
11-07 15:31:43.874 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=2
11-07 15:31:45.875 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=3
这里可以看到第一个请求计数到5结束后,就回调了onHandleIntent方法处理第二个请求,此时点击停止按钮停止服务,看日志:
11-07 15:31:46.696 13558-13558 E/ServiceDemo4: onDestroy
11-07 15:31:47.876 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=4
11-07 15:31:49.877 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=5
虽然服务停止了,但是服务里开启的子线程并没有结束,依然在计数,依然在发广播。
IntentService使用步骤:
- 自定义类继承IntentService,并重写构造方法和onHandleIntent两个方法
- 在Activity里通过startService方法启动它,可以通过Intent携带参数
总结:
- 如果第一次启动后,onhandleIntent没处理完,继续startService,不会再重新实例化这个Service了,而是将请求放到请求队列里,等待第一个处理完再处理第二个。这种情况下,只有一个线程在运行,看日志的thread id 都是868就明白了。
- 假如现在有A,B,C三个请求,那就startService3次,如果在处理A请求调用了stopService,那A请求会继续执行完,但是B和C请求就不会执行;如果在执行C请求时候调用了stopService,C请求还是会执行完。具体原因待会看源码解释。
- IntentService处理任务时是按请求顺序处理的,也就是一个接一个处理,优点是使用方便代码简洁,比较适用于单线程操作不耗时任务,但是并不符合我们大多数使用情况,没法进行并发操作。
IntentService源码解析
Service有的特性IntentService都有,但是有一些自身具有的属性,它在内部封装了一个消息队列和HnadlerThread。我们进到IntentService的源码中
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;//这是一个消息队列,存放着发过来的请求Intent
private volatile ServiceHandler mServiceHandler;//就是自定义一个Handler,用来从消息队
列取消息和放消息到消息队列中
private String mName;//工作线程名字
private boolean mRedelivery;//是否重启服务的标志,默认flase
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
//从onCreate里可以知道ServiceHandler的创建是依附于HandlerThread线程的,也就是在线程里创建一个Handler
//所以在这里处理请求是在HandlerThread进行,不会阻塞主线程
//每处理一个请求就调用一次stopSelf(msg.arg1),这个参数是onStartCommand方法第三个参数,并不是真的销毁自己,会判断
//如果mServiceLooper的队列里还有消息,就不会销毁自己
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
/**
*创建一个IntentService。 由你的子类的构造函数调用。
* 参数name是工作线程名字
*/
public IntentService(String name) {
super();
mName = name;
}
/**
*
* 设置true,如果onStartCommand在返回之前IntentService被系统销毁了,系统会重启该服务
* 如果有多个Intent已经被发送,则只有最近的一个保证被重新递交。
* 设置flase 如果onStartCommand在返回之前IntentService被系统销毁了,系统不会重启该服务
*/
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
super.onCreate();
//构建一个线程,HandlerThread是继承自Thread
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//将HandlerThread的消息队列拿出来,传给ServiceHandler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
/**
*可以看到每startService一次,就会调用一次onStartCommand,在onStartCommand会调用onStart,
* 在onStart里将发过来的请求Intent放到Message里,
* 这个startId是叠加的,表示请求顺序
*/
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
/**
* 建议不用重写这个方法,没有用,因为有onHandleIntent方法被回调
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
//只要调用stopService后,后续请求就不会得到执行,因为消息队列清空了mServiceLooper.quit()
@Override
public void onDestroy() {
mServiceLooper.quit();
}
/**
* 除非您为您的服务提供绑定,否则不需要实现此方法,因为默认实现返回null。
* 所以建议用startService启动服务,不用bindService启动服务。
*/
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
/**
* 这是一个抽象方法,在工作线程去处理请求,不会阻塞主线程,一次只处理一个请求,如果一个请求耗时较长,会将接下来的请求处理往后延,
* 所有请求结束后,IntentService会自动销毁,开发者不要调用stopSelf
*/
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
整体流程来说就是:
- startService后,IntentService内部调用onCreate方法,在这里创建了一个HandleThread,也就是创建了一个线程,并取出HandleThread中的消息队列Looper,接着创建一个ServiceHandler(本质就是一个Handler),并将获取到的Looper的引用传给ServiceHandler;这个用法其实就跟我们平时使用HandleThread的过程是一样的,只不过google把这个使用放到Service里去了就变成了一个IntentService。
- 然后就会调用onStartCommand,将发过来的请求Intent和请求id传过去调用onStart方法,
- 在onstart方法里将这两个参数构造成Message,通过mServiceHandler发到消息队列。
- 然后在mServiceHandler类的handleMessage方法里调用onHandleIntent方法,然后调用stopSelf,如果消息队列没有消息了,那就真的停止自己了。
- onHandleIntent如果没执行完,再次startService,就不会再走onCreate,而是继续走第二步,第三步,第四步,以此循环。
- stopSelf执行完了,开发者再次startService,就继续从第一步开始继续往下走
Service与IntentService
- Service是依附于主线程的(远程服务除外),直接做耗时操作会阻塞主线程;而IntentService是工作在工作线程,对主线程没影响
- 使用IntentService不需要像在Service里自己手动开启线程去处理耗时请求
- Service是需要手动调用stopService来销毁,而IntentService是自动销毁
- IntentService内部采用了HandleThread和Handler的实现,IntentService又比线程优先级高,相对而言不容易被系统杀死来保证服务运行。