Android查缺补漏--Service和IntentService

Service的运行不依赖界面,即使程序被切换到后台,Service仍然能够保持正常运行。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行。

Service 分为启动状态和绑定状态。当处于仅启动状态时,通过 stopService或 stopSelf 即可停止 Service。当处于绑定状态时需要通过 unBindService 和 stopService 结合使用才能完全停止 Service。

一、Service的生命周期(onCreate()-onStartCommand()-onDestory()):

  • 无论在项目的任何地方调用Context.startService(),相应的Service就会启动,首次创建时会调用onCreate(),接着回调onStartCommand()。服务启动后就一直保持运行状态,直到stopService()或stopSelf()被调用。
  • onCreate:只会在创建Service时调用一次。
  • onStartCommand:每调用一次startService(),就会调用一次此方法。
  • onDestory:调用stopService或stopSelf时,会调用此方法。
  • onBind:调用bindService时回调。

二、如何创建一个运行在前台的Service

Service默认运行在前台,优先级较低,当系统内存不足时就面临会被回收的危险。如果不希望被回收或者处于某种需要,我们就可以通过startForeground将Service运行在前台。 下面是一个将Service设置为前台服务的示例,同时在启动Service时添加了通知栏可以更直观的看到前台服务的运行情况,点击通知栏消息可以跳转到MainActivity。

/**
 * 前台Service
 */
public class ForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        showNotification();
    }

    private void showNotification() {
        final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("我是ContentTitle")
                .setContentText("我是ContentText");
        // 创建通知被点击时出发的Intent
        Intent intent = new Intent(this, MainActivity.class);
        // 创建任务栈
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(MainActivity.class);
        stackBuilder.addNextIntent(intent);
        PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(pendingIntent);
        final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        // 构建通知
        final Notification notification = builder.build();
        // 显示通知
        notificationManager.notify(0, notification);
        // 启动为前台服务
        startForeground(0, notification);
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

三、IntentService和用Service执行耗时任务。

普通的Service虽然运行在后台,但这并不代表它运行在子线程中,它仍然运行在UI线程中。可以试验一下看看Service到底有没有运行在UI线程里面,创建一个MyService,然后在其onCreate方法里面添加一个耗时任务:

public class MyService extends Service {

    private static final String TAG = MyService.class.getSimpleName();
    
    // 省略其他代码...

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: 线程:" + Thread.currentThread());
        int n = 10;
        while (n-- > 0) {
            try {
                Thread.sleep(1000);
                Log.i(TAG, "我是一个耗时任务,执行剩余时间:" + n);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 输出:
12-07 14:50:54.683 5506-5506/cn.codingblock.androidadvancestudy I/MyService: onCreate: 线程:Thread[main,5,main]
12-07 14:50:55.684 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:9
12-07 14:50:56.684 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:8
12-07 14:50:57.685 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:7
12-07 14:50:58.685 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:6
12-07 14:50:59.686 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:5
12-07 14:51:00.686 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:4
12-07 14:51:01.687 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:3
12-07 14:51:02.687 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:2
12-07 14:51:03.688 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:1
12-07 14:51:04.688 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:0

从log可以看到线程的名字是Thread[main,5,main]即主线程,并且程序在10s只能执行这个耗时任务,点击其他操作均无反应,成功的“卡住了”。这个示例证明了普通Service运行在UI线程中。

那么如果我们需要用Service做一些耗时任务该怎么办:

  • 1、在普通的Service中开启一个子线程,让子线程去执行耗时任务。
public class MyService extends Service {

    private static final String TAG = MyService.class.getSimpleName();

    // 省略其他代码...

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: 线程:" + Thread.currentThread());
        new Thread(new Runnable() {
            @Override
            public void run() {
                int n = 10;
                while (n-- > 0) {
                    try {
                        Thread.sleep(1000);
                        Log.i(TAG, "我是一个耗时任务,执行剩余时间:" + n);
                        Thread.yield();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}
  • 2、使用IntentService,在onHandleIntent()方法中执行耗时任务。
public class MyIntentService extends IntentService {
    
    String TAG = MyIntentService.class.getSimpleName();

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 这里非主线程,可在这里做一些耗时任务
        Log.i(TAG, "onHandleIntent: 线程:" + Thread.currentThread());
        int n = 10;
        while (n-- > 0) {
            try {
                Thread.sleep(1000);
                Log.i(TAG, "我是一个耗时任务,执行剩余时间:" + n);
                Thread.yield();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

接下来在Activity中我们也创建一个耗时任务(mainTask),并让这个任务跑在UI线程,然后同时 start MyService、MyIntentService 和 mainTask 并观察Log。

public class ServiceTestActivity extends AppCompatActivity implements View.OnClickListener {
    String TAG = ServiceTestActivity.class.getSimpleName();
    
    Context context;
    private Button btn_start_3_thread;

    // ignore other code...

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_start_3_thread:
                // 同时启动 MyService 、MyIntentService 和 mainTask 并观察log
                startService(new Intent(context, MyService.class));
                startService(new Intent(context, MyIntentService.class));
                mainTask();
                break;
        }
    }

    private void mainTask() {
        int n = 10;
        while (n-- > 0) {
            try {
                Thread.sleep(1000);
                Log.i(TAG, "我是一个耗时任务,执行剩余时间:" + n);
                Thread.yield();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 输入如下:
12-07 15:08:50.074 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:9
12-07 15:08:51.075 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:8
12-07 15:08:52.076 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:7
12-07 15:08:53.076 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:6
12-07 15:08:54.077 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:5
12-07 15:08:55.077 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:4
12-07 15:08:56.078 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:3
12-07 15:08:57.078 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:2
12-07 15:08:58.079 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:1
12-07 15:08:59.079 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:0
12-07 15:08:59.095 22693-22693/cn.codingblock.androidadvancestudy I/MyService: onCreate: 线程:Thread[main,5,main]
12-07 15:08:59.096 22693-22693/cn.codingblock.androidadvancestudy I/MyService: onStartCommand: 
12-07 15:08:59.102 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: onHandleIntent: 线程:Thread[IntentService[MyIntentService],5,main]
12-07 15:09:00.099 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:9
12-07 15:09:00.103 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:9
12-07 15:09:01.100 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:8
12-07 15:09:01.104 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:8
12-07 15:09:02.100 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:7
12-07 15:09:02.104 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:7
12-07 15:09:03.101 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:6
12-07 15:09:03.105 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:6
12-07 15:09:04.101 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:5
12-07 15:09:04.105 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:5
12-07 15:09:05.101 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:4
12-07 15:09:05.106 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:4
12-07 15:09:06.102 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:3
12-07 15:09:06.106 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:3
12-07 15:09:07.103 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:2
12-07 15:09:07.107 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:2
12-07 15:09:08.103 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:1
12-07 15:09:08.107 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:1
12-07 15:09:09.103 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:0
12-07 15:09:09.107 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:0

从log中可以看到,先执行完了 ServiceTestActivity 中的 mainTask 耗时任务才启动两个 Service,试验姿势貌似有点不对。我们先分析一下这三行代码的执行顺序:

// 标记1
startService(new Intent(context, MyService.class));
startService(new Intent(context, MyIntentService.class));
mainTask();

按理说应该是先启动MyService,并开始执行MyService的异步任务,再启动MyIntentService并开始执行其异步任务,然后再开始执行mainTask任务,由于前两者都是异步的,所以输入结果应该三个异步任务交叉执行。

但是,从真正的输入结果中看到执行完了mainTask任务后才开始启动了两个Service,这是怎么一回事呢?

其实这跟 Service 的启动流程有关系,Service的启动比较复杂,这里说一下大体过程,大体总结起来 Service 的启动流程经历了三个阶段:

  1. 先从App进程调用startSesrvice;
  2. 然后通过IPC调用进入系统进程(system_service)在AMS(ActvitiyManagerService)中完成生命周期管理及其他一系列的工作;
  3. 最后再回到App进程中完成Service对象的创建。

其中第1步和第3步都是同步执行的,第2步在AMS中对Service的相关操作是异步执行的,所以导致了实验的这个结果。

整理一下,再重新对这三行代码(标记1)分析一下:

  1. 调用第一行startService来启动MyService,这一步是同步执行,直到都到AMS阶段开始异步执行,而此时就有机会执行主线程中的第二行startService代码了。转2
  2. 与1同理,在启动MyIntentService时执行到AMS阶段开始异步,而此时就有机会执行第三行代码。转3
  3. 调用mainTask()方法,开始执行主线程的耗时任务,而此时是同步的,所以在此任务执行完之前线程不会被切换。即使上面两个启动的Service的方法在AMS已经执行完毕,转入到主线程时发现主线程正在执行mainTask的耗时任务,此时也只能等待其执行完毕,然后创建Service,再开启两个Service中的异步任务。

好了,原因查明了。真是处处有惊喜,处处有收获!

解决上面问题的方法很简单,就是等Service启动完成之后我们再调用Activity中的mainTask()方法即可,比如把他们用两个按钮来控制,修改后输出的log如下:

12-07 17:53:10.905 29898-29898/cn.codingblock.androidadvancestudy I/MyService: onCreate: 线程:Thread[main,5,main]
12-07 17:53:10.910 29898-29898/cn.codingblock.androidadvancestudy I/MyService: onStartCommand: 
12-07 17:53:10.916 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: onHandleIntent: 线程:Thread[IntentService[MyIntentService],5,main]
12-07 17:53:11.907 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:9
12-07 17:53:11.916 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:9
12-07 17:53:12.824 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:9
12-07 17:53:12.907 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:8
12-07 17:53:12.916 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:8
12-07 17:53:13.825 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:8
12-07 17:53:13.908 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:7
12-07 17:53:13.917 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:7
12-07 17:53:14.826 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:7
12-07 17:53:14.910 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:6
12-07 17:53:14.918 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:6
12-07 17:53:15.827 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:6
12-07 17:53:15.911 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:5
12-07 17:53:15.919 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:5
12-07 17:53:16.827 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:5
12-07 17:53:16.912 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:4
12-07 17:53:16.920 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:4
12-07 17:53:17.828 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:4
12-07 17:53:17.913 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:3
12-07 17:53:17.922 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:3
12-07 17:53:18.829 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:3
12-07 17:53:18.914 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:2
12-07 17:53:18.923 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:2
12-07 17:53:19.829 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:2
12-07 17:53:19.915 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:1
12-07 17:53:19.924 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:1
12-07 17:53:20.830 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:1
12-07 17:53:20.916 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一个耗时任务,执行剩余时间:0
12-07 17:53:20.924 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一个耗时任务,执行剩余时间:0
12-07 17:53:21.830 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一个耗时任务,执行剩余时间:0

从log来看,普通Service中开始子线程执行的耗时任务和IntentService中的任务还有主线程中的任务确实在交叉执行。

最后想说的是,本系列文章为博主对Android知识进行再次梳理,查缺补漏的学习过程,一方面是对自己遗忘的东西加以复习重新掌握,另一方面相信在重新学习的过程中定会有巨大的新收获,如果你也有跟我同样的想法,不妨关注我一起学习,互相探讨,共同进步!

参考文献:

  • 《Android开发艺术探索》
  • 《Android开发进阶从小工到专家》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏中国Android研究院

正确使用IntentService的姿势

在开发安卓应用的过程中,我们经常需要这样的场景:希望APP在不影响当前用户的操作的前提下,在后台默默的做一些任务。比如:静默的下载文件或者上传数据。我们一般会考...

662
来自专栏everhad

笔记:Activity的启动过程

Activity的创建特点 作为四大组件之一的Activity,它不像普通java对像那样,可以new出来,然后去使用。而是调用 startActivity(...

2018
来自专栏拂晓风起

cocos2d-js 3.0 RC0 监听返回键、菜单键、进入后台(home键)、恢复显示等事件

902
来自专栏Android干货园

Android中线程的使用

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/47...

601
来自专栏Android干货

关于Activity销毁,而绘制UI的子线程未销毁出现的问题

3496
来自专栏吴小龙同學

Android之BroadcastReceiver

什么是BroadcastReceiver?   BroadcastReceiver,广播接收者,它是一个系统全局的监听器,用于监听系统全局的Broadcast消...

3325
来自专栏向治洪

android多线程下载3

今天跟大家一起分享下android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2...

1775
来自专栏Android开发指南

7.数据库、Contentobserver

31711
来自专栏项勇

[Android学习整理]之监控并控制SystemUi(状态栏)的显示与隐藏

1513
来自专栏上善若水

009android初级篇之APP中使用系统相机相册等集成应用

这是第一种方式 在启动相机前先指定好图片的文件位置,通知intent,同时也保留在成员变量中。然后在函数中,可以直接打开该文件

824

扫码关注云+社区