Android IntentService 简析

IntentService的使用背景

在讨论 IntentService 之前,我们先来看一段普通 Service 的生命周期函数回调的输出,这里我在相关的回调函数中,打印出当前回调函数所在的线程。
Android IntentService 简析_第1张图片
从上面我们可以看到,不论是用 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:
界面:
Android IntentService 简析_第2张图片
界面很简单,就是两个按钮一个进度条。

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。

Android IntentService 简析_第3张图片
当我点击 task1后进度条开始往前跑,然后 Logcat 中打印出 onHandleIntent task_1,这时候再点击 task2按钮,发现并没有打印 onHandleIntent task_2,说明并没有执行 task2,等进度条跑完了才打印出onHandleIntent task_2,第二次点击 task2按钮也是这样的情况。当 task 都执行完了之后发现调用了 onDestroy 回调方法。
此时如果我再点击 task1或者 task2按钮,会发现 IntentService 会重新走构造函数:
Android IntentService 简析_第4张图片
从上面的执行结果来看,我们可以得出以下结论:
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并没有继续执行。

接下来我们再看另一种情况:

Android IntentService 简析_第5张图片
当我点击 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关于消息机制的相关知识,以后我也会在后面的博文进行讨论的。

你可能感兴趣的:(android)