今天在集成个推的推送服务的时候,看到个推在接受推送的消息的处理的时候,用的是服务,这个和之前的不一样,,老的个推接受消息用的是广播,根据个推的文档说:
从2.9.5.0版本开始,为了解决小概率发生的Android广播丢失问题,我们推荐应用开发者使用新的IntentService方式来接收推送服务事件(包括CID获取通知、透传消息通知等)
在此之前只是听说过IntentService,,知道他是服务的一种,可以用于处理耗时操作。仅此而已,在项目中也没有用过,既然个推这里用到了,借此机会,了解下IntentService.
疑问:1:IntentService可以处理耗时操作,是开了个新的线程吗?
2:我每次调用 mServiceIntent = new Intent(MainActivity.this, IntentServiceSub.class);都会重新开启一个线程吗?
带着疑问。我写了个demo;
首先是一个继承IntentService的IntentServiceSub:
public class IntentServiceSub extends IntentService {
private static final String TAG = "zmm";
public IntentServiceSub() {
super("IntentServiceSub");
Log.e(TAG, "=>IntentServiceSub");
}
/* (non-Javadoc)
* @see android.app.IntentService#onCreate()
*/
@Override
public void onCreate() {
Log.e(TAG, "=>onCreate");
super.onCreate();
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.e(TAG, "=>onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
/* (non-Javadoc)
* @see android.app.IntentService#onDestroy()
*/
@Override
public void onDestroy() {
Log.e(TAG, "=>onDestroy");
super.onDestroy();
}
@Override
protected void onHandleIntent(Intent arg0) {
try {
Log.e(TAG, "IntentService 线程:" + Thread.currentThread().getId());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
接着在Activity:里面有两个按钮:
一个开启服务,一个关闭服务:
public class MainActivity extends AppCompatActivity {
private Intent mServiceIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("zmm", "主线程ID:" + Thread.currentThread().getId());
// if (mServiceIntent == null) {
// mServiceIntent = new Intent(MainActivity.this, IntentServiceSub.class);
// }
/**
* 看过log,,注释掉的这种写法和下面的写法作用是一样的,IntentServiceSub只会创建一次,
* 即onCreate只会走一次,再次startService,只会重走IntentServiceSub的onStartCommand方法。
* 不同的地方,应该就是下面的方法每次都会重新new一个对象。但是引用也只有一份。
*/
mServiceIntent = new Intent(MainActivity.this, IntentServiceSub.class);
startService(mServiceIntent);
}
});
findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("zmm", "btnStopIntentService");
if (mServiceIntent != null) {
stopService(mServiceIntent);
mServiceIntent = null;
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("zmm","MainActivity--->+onDestroy");
}
}
别忘了在AndroidManifest.xml文件里面注册改服务:
<service android:name=".IntentServiceSub"/>
服务里面很简单,我们就sleep了2秒。模拟耗时操作。
我们运行起来,点击开启服务以后,,看下log…
可以看到走了服务的构造方法,OnCreate–》onStartCommand–》然后回调到onHandleIntent()方法,在这里面我们可以看到。。这里面的线程的id不是主线程,,,所以,我们可以知道,IntentService服务是会开启一个新的线程来进行耗时操作。
然后我先是点击了一下开启服务,然后等2秒即第一次任务执行完毕以后,再次点击开启服务。我们可以看到:
从log,我们可以看到,第一次任务执行完毕以后,服务自动调用了OnDestroy(),即服务执行完毕后,会自动关闭,且线程资源也会回收。再次开始服务。就是一个新的服务,生命周期都会重新走一遍.
但是如果我们连续点击开启服务,我连着点了五次(就是不等上一个服务结束就点击),然后,我们可以看到。
从log我们可以看出,就算调用再多次 mServiceIntent = new Intent(MainActivity.this, IntentServiceSub.class);
startService(mServiceIntent);服务都只有一个,每次startService都只会走onStartCommand方法和onHandleIntent方法,重走onstartCommand这个方法没有什么特殊的和普通的服务一样,但是IntentService重走了onHandleIntent()方法,并且,我们可以看到这些都是在一个线程中,,且是在上一个任务执行完毕,才会执行下一个任务。全部执行完毕以后,调用服务的onDestroy()方法,销毁服务
由此。我又产生了一个疑问,如果我在这五个任务没有执行完毕的时候,按了返回键退出程序,或者,点击了停止服务按钮,可以停止服务吗???
先看第一种点击了停止服务按钮:
可以看到,我们点击了停止服务以后,就直接调用了服务的onDestroy方法,任务全部取消了。
第二种情况,按返回键:
可以看到,按返回键退出程序以后,页面销毁了,但是服务没有,任务还在继续执行,直到执行完所有任务以后,调用了服务的onDestroy,结束服务。如果这个时候我们再次点击开始服务。可想而知,会重新走服务的OnCreate–》onstartCommand–》onHandleIntent方法,并且,这个时候线程id肯定也换了。。。(这些好像是废话。。。哈哈哈)
然后,我们继续探索下,我们知道我们在真正结束一个程序,让虚拟机释放资源的时候,会调用System.exit(0);或者System.exit(1);(Ps:System.exit(0)是正常退出程序,而System.exit(1)或者说非0表示非正常退出程..两者都会退出程序,都会将你的整个虚拟机里的内容都停掉,释放掉内存。。)如果我们在任务没有结束的时候,返回,并且在ondestroy方法里调用System.exit(0)方法。会有什么效果。
看图:
我们可以看到,如果在destroy方法里调用了System.exit(0)方法,即使任务没有执行完,服务也不会继续执行。如果是普通的Service,这里重启服务,,因为,普通服务的onStartCommand返回值默认是START_STICKY。
START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统将会把它置为started状态,系统不会自动重启该服务,直到startService(Intent intent)方法再次被调用;。
START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
Ps:别问我为什么知道。。因为我之前写定位的时候,测试过。。。
我以为IntentService会是一样的。。。所以看到这个结果我是不愿意相信的。心想What???带着疑问。我看了下IntentService的源码。
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
搜嘎。。原来,IntentService默认的返回值是START_NOT_STICKY,故不会重启。。所以,如果我们想让他重启,只需要把mRedelivery 这个设置为true就可以了。。看了下,果真IntentService提供了修改该参数的值的方法setIntentRedelivery(),那么问题来了,,我该在什么时候调用这个方法,我本来想的是直接重写该方法。然后强制传入个true
@Override
public void setIntentRedelivery(boolean enabled) {
Log.e("zmm","setIntentRedelivery--->");
super.setIntentRedelivery(true);
}
结果发现并没有用,,看了下日志,,并没有走该方法。。。然后仔细看了下源码,发现IntentService根本就木有调用该方法(坑不坑)。。。所以我们得手动调用。
我们就可以在构造方法或者onCreate方法里面手动调用下就可以了。
public IntentServiceSub() {
super("IntentServiceSub");
Log.e(TAG, "=>IntentServiceSub");
setIntentRedelivery(true);
}
可以看到,虽然程序退出了,调用了System.exit(0),但是服务重启了。。。重点,,重启了。。不是继续。。。这里虽然返回之前的线程的id和退出重启以后的线程id相等,,但是。。。其实不是同一个了,,这里只是凑巧。。多试几次就会发现了。例如:
虽然重启了,但是任务的个数是对的,假设,你点了3次开始服务,即三个任务,然后在第一个任务完成了,第二个任务刚开始,就调用了System.exit(0),那么重启以后,会继续执行剩下的两个任务。。
看图:
至此IntentService,学习之旅结束。。。
注意:IntentService不应该处理长时间运行的服务(如音乐播放),长时间运行的服务应该由sticky服务完成。(Ps:看好多博客都这么说。。。)
感谢。。。额。。。感谢大家。。因为参考了很多人的博客。。。
每日语录:
这个世界上有很多好人,如果你找不到,就让自己成为一个。。。。
加油!!!(Ps:此博客是在《可惜我是水瓶座》单曲循环下完成的。。。)