Service和后台任务

启动Service有如下两种方式。

  • 通过Context的startService()方法:通过该方法启动Service,访问者与Service之间没有关联,即使访问者退出了,Service也仍然运行。
  • 通过Context的bindService()方法:使用该方法启动Service,访问者与Service绑定在一起,访问者一旦退出,Service也就终止了。

从Android5.0开始,Google要求必须使用显式Intent启动Service组件。

每当Service被创建时会回调onCreate()方法,每次Service被startService()启动时都会回调onStartCommand()方法--多次启动一个已有的Service组件将不会再回调onCreate()方法,但每次启动时都会回调onStartCommand()方法。

当程序通过startService()和stopService()启动、关闭Service时,Service与访问者之间基本上不存在太多的关联,因此Service和访问者之间也无法进行通信信、交换数据。

如果Service和访问者之间需要进行方法调用或交换数据,则应该使用bindService()和unbindService()方法启动、关闭Service。

bindService方法的参数ServiceConnection对象的onServiceConnected()方法中有一个IBinder对象,该对象即可实现与被绑定Service之间的通信。
开发Service类时,该Service类必须提供一个IBinder onBind(Intent intent)方法,在绑定本地Service的情况下,onBind(Intent intent)方法所返回的IBinder对象将会传给ServiceConnection对象里onServiceConnected(ComponentName name, IBinder bind)方法的bind参数,这样访问者就可通过该IBinder对象与Service进行通信了。

IBinder对象相当于Service组件的内部钩子关联到绑定的Service组件,当其他程序组件绑定该Service时,Service将会把IBinder对象返回给其他程序组件,其他程序组件通过该IBinder对象即可与Service组件进行实时通信。

Service本身存在两个问题:

  • Service不会专门启动一个单独的进程,Service与它所在应用位于同一个进程中。
  • Service不是一条新的线程,它依赖于主线程,不应该在Service中直接处理耗时的任务。

IntentService

IntentService用来方便的处理一些耗时任务。

    如果需要在Service中处理耗时任务,建议在Service中另外启动一条新线程来处理该耗时任务。如果直接在其他程序组件中启动子线程来处理耗时任务并不可靠,由于Activity可能会被用户退出,而BroadcastReceiver的生命周期本身就很短。可能出现的情况是:在子线程还没有结束的情况下,Activity已经被用户退出了,或者BroadcastReceiver已经结束了。在Activity已经退出、BroadcastReceiver已经结束的情况下,此时它们所在的进程就变成了空进程(没有任何活动组件的进程),系统需要内存时可能会优先终止该进程。如果宿主进程被终止,那么该进程内的所有子线程也会被中止,这样就可能导致子线程无法执行完成。

    IntentService使用队列来管理请求Intent,每当客户端代码通过Intent请求启动IntentService时,IntentService会将该Intent加入队列中,然后开启一条新的worker线程来处理该Intent。对于异步的startService()请求,IntentService会按次序依次处理队列中的Intent,该线程保证同一时刻只处理一个Intent。由于IntentService使用新的worker线程处理Intent请求,因此IntentService不会阻塞主线程,所以IntentService自己就可以处理耗时任务。

    IntentService具有如下特征:

    • IntentService会创建单独的worker线程来处理所有的Intent请求。
    • IntentService会创建单独的worker线程来处理onHandleIntent方法实现的代码,因此开发者无须处理多线程问题。
    • 当所有请求处理完成后,IntentService会自动停止,开发者无须调用stopSelf()方法来停止该Service。
    • 为Service的onBind()方法提供了默认实现,默认实现的onBind()方法返回null
    • 为Service的onStartCommand()方法提供了默认实现,该实现会将请求Intent添加到队列中。

    Foreground Service

    前台服务是用户可以在Android系统的通知栏中看到的服务,即使它在后台执行,也会有一个通知提醒用户。

    跨进程调用Service (AIDL Service)

    在Android系统中,各应用程序都运行在自己的进程中,进程之间一般无法直接进行数据交换。为了实现跨进程通信(Interprocess Communication,简称IPC),Android提供了AIDL Service。AIDLService与传统技术Corba、Java中的RMI(远程方法调用)之间存在一定的相似之处。Android的远程Service调用与Java的RMI基本相似,都是先定义一个远程调用接口,然后为该接口提供一个实现类。

    系统Service

    Android系统本身提供了大量的系统Service,开发者只要在程序中调用Context的如下方法即可获
    取这些系统Service。getSystemService(String name):根据Service名称来获取系统Service。

    电话管理器(TelephonyManager)

    TelephonyManager是一个管理手机通话状态、电话网络信息的的服务类,该类提供了大量的
    getXxx()方法来获取电话网络的相关信息。在程序中获取TelephonyManager十分简单,只要调用如下代码即可:TelephonyManager tManager = (TelephonyManager)getSystemService (Context.TELEPHONY SERVICE);接下来就可以通过TelephonyManager获取相关信息或者进行相关操作了。通过TelephonyManager提供的一系列方法即可获取手机网络、SIM卡的相关信息。TelephonyManager除提供一系列的getXxx()方法来获取网络状态和sSIM卡信息之外,还提供了一个listen(PhoneStateListener listener,int events)方法来监听通话状态。

    短信管理器(SmsManager)

    SmsManager是Android提供的另一个非常常见的服务,SmsManager提供了一系列sendXxxMessage()方法用于发送短信,不过就现在实际应用来看,短信通常都是普通的文本内容,也就是调用sendTextMessage()方法进行发送即可。

    PendingIntent是对Intent的包装,一般通过调用PendingIntent 的getActivity()、getService()、getBroadcastReceiver()静态方法来获取PendingIntent对象。与Intent对象不同的是,PendingIntent通常会传给其他应用组件从而由其他应用程序来执行PendingIntent所包装的"Intent"。

    音频管理器(AudioManager)

    在某些时候,程序需要管理系统音量,或者直接让系统静音,这就可借助于Android提供的AudioManager来实现。

    震动器 Vibrate

    振动是除视频、声音之外的另一种"多媒体",充分利用系统的振动器会带给用户更好的体验。系统获取Vibrator也是调用Context的getSystemService()方法即可,接下来就可调用Vibrator的方法来控制手机振动了。

    手机闹钟服务(AlarmManager)

    AlarmManager是一个全局定时器,允许在指定的时间执行任务。它可以精确到毫秒,适用于需要精确时间控制的任务,如闹钟应用。

    从Android 6.0(API 23)开始,AlarmManager的精确度受到限制,例如在设备处于深度休眠状态时。对于需要高精度时间控制的任务,可以考虑使用setExact()方法(仅限API 23及以上),但这仍然不保证在所有情况下都能达到预期的精确度。

    JobScheduler

    JobScheduler是Android 5.0 (API 21) 引入的,仅适用于 Android 5.0 及以上版本,它用于在满足特定条件时执行后台任务,处理不需要精确到秒的任务(例如,当设备空闲时执行清理任务)。

    WorkManager

    WorkManager是jetpack库中后台任务管理组件,用于替代传统的Service进行后台任务处理。

    WorkManager‌ 使用 ‌工作线程‌ 来执行任务,不是在主线程(UI 线程)上执行。工作线程是专门用于执行后台任务的线程池,使用Executor执行。

    主要特点:

    • 可脱离应用执行‌:任务可以在应用退出后继续执行。
    • 灵活触发:可以根据需要设置不同的触发器,如定时任务、网络状态变化等。
    • 自动重试‌:WorkManager会自动管理任务的执行和重试,减少了开发者的工作量。
    • ‌全版本兼容‌:根据设备 API 级别自动选择最佳执行方式(JobScheduler、AlarmManager 等),并适配 Doze 模式等电源管理特性。

    安卓应用开发中 WorkManager 周期性任务未按预期执行问题详解_jobinfo.builder的setrequiresdeviceidle(true)不起作用-CSDN博客

    Android Jetpack 系列(六)WorkManager 任务调度_android workmanager-CSDN博客

    墓碑机制

    Android 系统并无官方称为“墓碑机制”的功能;该术语源自 iOS 的后台管理策略,Android 对应的是 LMK(Low Memory Killer)与“暂停执行已缓存的应用”(开发者选项)。

    “墓碑机制”通俗的讲就是伪多任务,当一个应用被切换到后台时,系统就会自动杀死,并把该应用切换后台时的状态自动记录,当再切换回来时呈现的是杀死应用时的瞬间状态,系统会把应用还原成被杀死前的状态,就好像它从来没被杀死过一样,通过系统动画进行视觉弥补,让察觉不到。

    Doze模式

    Doze模式可以称作“活埋机制”,Android(6.0、7.0)对于一些非社交的APP采用无视的方式,屏蔽网络,唤醒和定时任务也被忽略或推迟。这些APP可以继续在后台运行,但是啥也干不了。而且Doze模式启动条件也比较苛刻,首先需要一个小时的等待期,在屏幕关闭半小时后开始进行『大幅度运动监测』,接下来半小时内无大幅度运动才会进入Doze模式。Doze模式并不是墓碑机制,不会强制杀死应用,而是加强了后台唤醒的管制,在长时间待机状态的晚上才是Doze模式发挥作用的时候。

    前台服务(ForegroundService)的线程在后台不执行

    主要因系统省电策略(如 OriginOS)主动冻结后台进程,而非 Android 原生限制;需结合系统设置与开发适配双重解决。‌‌例如 vivo(OriginOS)默认“暂停未使用应用的活动”并限制后台高耗电行为,即使有前台服务,也可能被杀或线程休眠。需手动在 ‌设置 → 电池 → 后台高耗电管理‌ 中为应用开启“允许后台高耗电”,并在 ‌自启动管理‌ 中开启自启动,同时在多任务界面 ‌锁定应用卡片‌,关闭“暂停未使用应用的活动”对该应用的限制。

    Handler 和 Timer

    • 简单的前台实时任务(如倒计时、动画控制)。
    • 不需要持久化的短期任务。
    优点
    • 轻量化:适用于简单、短期、实时性较高的任务。
    • 实时性好:可以精确控制任务的触发时间。
    缺点
    • 不适合后台任务:应用进程被杀后任务会终止。
    • 不支持持久化:无法跨设备重启或进程重启保存任务状态。
    • 电量优化不足:频繁执行任务可能增加电量消耗。

    Timer

    Android 中的 java.util.Timer 运行在‌后台子线程‌,非 UI 线程(主线程)。‌‌

    • 线程机制‌:每个 Timer 实例创建时会启动一个独立的后台线程(默认命名为 "Timer-0"),所有调度的 TimerTask 均在此线程中‌串行执行‌。
    • UI 操作限制‌:严禁在 TimerTask 的 run() 方法中直接更新 UI,否则会抛出异常或导致崩溃。
    • 正确用法‌:若需更新界面,必须通过 HandlerrunOnUiThread() 或 LiveData 等机制将结果切换回主线程处理。
    • 替代建议‌:由于 Timer 存在单线程阻塞和异常终止风险,现代 Android 开发更推荐替代Timer使用 Handler.postDelayed()Coroutine 或 ScheduledThreadPoolExecutor。‌‌

    监听网络状态

    使用ConnectivityManager来监听网络状态的变化

    AsyncTask

    3.0 之前的 AsyncTask 可以同时有 5 个任务在执行,而 3.0 之后的 AsyncTask 同时只能有 1 个任务在执行。为什么升级之后可以同时执行的任务数反而变少了呢?这是因为更新后的AsyncTask变得更加灵活,如果不想使用默认的线程池,还可以自由地进行配置。比如使用如下的代码来启动任务:

    Executor exec = new ThreadPoolExecutor(5, 20, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

    new DownloadTask().executeOnExecutor(exec);

    这样就可以使用自定义 Executor 来执行任务,而不是使用 SerialExecutor。上述代码的效果允许在同一时刻有 5 个Task同时执行,并且最多能够存储 20 个任务。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值