在讨论 IntentService 之前,我们先来看一段普通 Service 的生命周期函数回调的输出,这里我在相关的回调函数中,打印出当前回调函数所在的线程。
从上面我们可以看到,不论是用 startService 还是 bindService 启动的 Service(前提是 Service不是运行在另外的进程中),它的各个生命周期回调函数都是运行在主线程中的。那么当我们在启动 Service 之后,我们需要在对应的生命周期回调函数中进行耗时的操作的话(例如I/O 操作、网络操作等)可能会导致程序 ANR。为了解决这个问题,最好的办法就是在 service 中开新的线程进行操作。而 IntentService 就是 Android 用来简化带有线程操作的Service 类。
那么如何使用 IntentService 呢,话不多说,上代码:
public class IntentDemoService extends IntentService {
public static final String ACTION_PROGRESS = "action_progress";
public IntentDemoService() {
super("IntentDemoService");
}
public IntentDemoService(String name) {
super(name);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String task = intent.getStringExtra("task");
int progress = 0;
//次循环用来模拟耗时操作,例如下载文件等。
while (progress <= 100) {
try {
Thread.sleep(70);
} catch (InterruptedException e) {
e.printStackTrace();
}
progress++;
//刷新UI
setStateAndProgress(task, progress);
}
}
private void setStateAndProgress(String state, int progress) {
Intent intent = new Intent();
intent.setAction(ACTION_PROGRESS);
intent.putExtra("STATE", state);
intent.putExtra("PROGRESS", progress);
sendBroadcast(intent);
}
}
我们再看 activity:
界面:
界面很简单,就是两个按钮一个进度条。
public class IntentServiceActivity extends AppCompatActivity {
ProgressBar mProgressBar;
AppCompatTextView mTextView;
BroadcastReceiver broadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_intent_service);
findViewById(R.id.btn_task_1).setOnClickListener(view->{
Intent intent = new Intent(this, IntentDemoService.class);
intent.putExtra("task", "task_1");
startService(intent);
});
findViewById(R.id.btn_task_2).setOnClickListener(view->{
Intent intent = new Intent(this, IntentDemoService.class);
intent.putExtra("task", "task_2");
startService(intent);
});
mProgressBar = findViewById(R.id.pb);
mProgressBar.setMax(100);
mTextView = findViewById(R.id.tv_task);
registerBroadCastReceiver();
}
private void registerBroadCastReceiver() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int progress = intent.getIntExtra("PROGRESS", 0);
String state = intent.getStringExtra("STATE");
mTextView.setText("当前任务" + state);
mProgressBar.setProgress(progress);
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(IntentDemoService.ACTION_PROGRESS);
registerReceiver(broadcastReceiver, intentFilter);
}
@Override
protected void onDestroy() {
if (broadcastReceiver != null) {
unregisterReceiver(broadcastReceiver);
}
super.onDestroy();
}
}
这里我先是点击了 task1 然后点击了 task2,然后又点击了一次 task2。
当我点击 task1后进度条开始往前跑,然后 Logcat 中打印出 onHandleIntent task_1,这时候再点击 task2按钮,发现并没有打印 onHandleIntent task_2,说明并没有执行 task2,等进度条跑完了才打印出onHandleIntent task_2,第二次点击 task2按钮也是这样的情况。当 task 都执行完了之后发现调用了 onDestroy 回调方法。
此时如果我再点击 task1或者 task2按钮,会发现 IntentService 会重新走构造函数:
从上面的执行结果来看,我们可以得出以下结论:
1、IntentService的 onHandleIntent 是可以进行耗时操作,而不需要我们自己手动去开个线程。这是因为会自动创建一个线程。
2、由于一个 IntentService 只有一个工作线程,如果多次通过 startService 启动 IntentService 产生的多个任务是按照先后顺序一个一个进行工作的。也就是说先将 task_1放到 onHandleIntent 中去执行,等 task_1执行完了再将 task_2放到 onHandleIntent 中去执行,依次类推。
3、当所有任务都执行完毕后,IntentService 会自己销毁, 回调 onDestroy 方法。
接下来我们再来看下在IntentService 正在执行任务的时候调用 stopService 会有什么情况。
首先点击 task1,再点击 task2,再点击 stop task 的时候(此时 task1都没有执行完),会发现点击 stopService 时,会回调 onDestroy 方法,service 销毁,但是其还在继续执行直至 task1执行完,但是之后 task2并没有继续执行。
接下来我们再看另一种情况:
当我点击 task1 再点击 stopService,紧接着再次点击 task2,发现进度条更新错乱,说明有两个线程在把数据传给主线程去更新进度。
也就是说当 task1尚未结束去点击 stopService 时 IntentService 销毁,此时其线程继续执行,这时候再次点击 task2(task1尚未结束),会重新开启 IntentService,IntentService 会继续创建一个线程执行 task2,也就是说此时 task1和 task2是在两个线程中进行的,所以执行顺序和完成顺序都是不确定的。
IntentService 源码简析
@Override
public void onCreate() {
super.onCreate();
//创建HandlerThread,HandlerThread是 IntentService 的工作线程
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//获取到HandlerThread 线程的Looper 对象
//将 Looper 对象传递给 Servicehandler,这样ServiceHandler 就和 HandlerThread 的消息队列绑在一起了
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
//创建Message 对象,并将Intent 当做 Message的 obj 参数
//这样 Intent 就和 Message 关联起来了
Message msg = mServiceHandler.obtainMessage();
//将 startId 传给参数 arg1,后面销毁 service 会用到
msg.arg1 = startId;
msg.obj = intent;
//将关联了 Intent 的 Message 对象发送给 Handler 处理。
//这也说明了任务是一条条执行的
mServiceHandler.sendMessage(msg);
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//ServiceHandler 的消息队列是在 HandlerThread 这个工作线程中的
//所以 onHandleIntent 是在工作线程中调用的,所以不会阻塞主线程(ANR)
onHandleIntent((Intent)msg.obj);
//当 onHandeIntent调用结束之后,会调用 stopSelf(sartId)去尝试销毁 Service
//如果当前的 startId 是最近的一个 startId 才会真正销毁 service
//也就是当所有任务执行完毕后才会销毁 service(手动调用 stopService 除外)
stopSelf(msg.arg1);
}
}
结语
IntentService给我们需要 Service 做一些耗时操作提供了便捷,但是IntentService不能并行处理多个任务,只能按照先后顺序一个接一个的进行处理。同时当我们调用 stopService 如果此时有任务正在进行的话,是无法结束正在执行的任务的。
另:文中提到的 Looper 、ThreadHandler、Message 等知识如果大家不熟悉的话,可以自己去看下 Android关于消息机制的相关知识,以后我也会在后面的博文进行讨论的。