九、服务、启动器和组件

长期运行的服务

在 Android 中,有几种方法可以在当前活动结束后一次性或周期性地运行“作业”。这里讨论的技术将在以下两种类型之间变化:

  • 强绑定到当前活动,比如AsyncTask,意思是如果活动结束,那么任务被垃圾回收。

  • 没有将强绑定到当前活动,例如JobScheduler,其中任务甚至在父活动本身被垃圾收集后仍继续。

扳机

触发器是长期运行的服务如何分配任务和运行的机制,它们是服务的初始入口点。大多数触发器将为服务提供某种形式的持久性。这些触发可以在表 9-1 中看到。

表 9-1

长期运行的服务触发器

|

引发

|

描述

| | --- | --- | | 目的 | 从代码中直接触发,通常来自用户交互。 | | 手机闹钟服务 | 在未来的特定时间触发,一次性或重复发生。 | | 广播接收器 | 收到特定广播消息时触发。例如,BootCompletePowerConnected或自定义接收器。 | | 传感器回调 | 收到特定传感器值时触发。 | | 工作管理器 | 根据 API 等级使用JobSchedulerAlarmManagerBootComplete。 | | 作业调度程序 | 从 API level 21 (Android L)开始,AlarmManager的更智能实现允许根据网络、空闲和充电状态运行。也瞌睡顺从。 |

服务

有几种类型的“服务”可以运行,为应用提供长期运行的后台工作。这些可以在表 9-2 中看到。

表 9-2

长期运行的服务类型

|

服务

|

描述

| | --- | --- | | 工作管理器 | 封装启动器和服务元素(考虑到底层实现被抽象时的向后兼容性)。并发工作之间的最小间隔为 15 分钟,每个工人最多只能运行 10 分钟。WorkManagers重启后也会自动保持(如果许可的话,使用BootComplete广播接收器)。 | | 作业调度程序 | 封装了启动器和服务元素。这些都是高度可定制的,可以根据网络、空闲和电池状态等环境因素运行工作。除此之外,它们还可以被定义为以特定的时间间隔或特定的时间段运行,并在重启后持续运行(如果许可可用,使用BootComplete广播接收器)。工作之间的最小间隔是 15 分钟。 | | 服务 | 有许多类型的服务;然而,最常见的一种是意向服务,其中工作请求按顺序运行。后续请求(对服务的意图)会一直等到第一个操作完成。 | | 线 | 主要用于不想在 UI 线程上工作的作业(例如网络),但是,只要它们的父线程没有被杀死,就可以用于长期运行的后台工作。线程被绑定到父应用。 | | 异步任务 | 绑定到父Activity的生命,意味着如果活动结束或者被杀死,那么AsyncTask也是。这样做的好处是更容易将工作从AsyncTask推回到 UI 线程。 | | 前台服务 | 从 API 26 开始,后台服务(如意向服务)被限制为仅在应用处于前台时运行。取而代之的是前台服务,在前台服务运行时,它们必须向用户显示一个持续的通知(例如,一个音乐应用在播放时显示一个音乐播放器)。 |

IntentService、AlarmManager 和 BootComplete

如前所述,IntentService 1 是一种不能直接与 UI 交互的服务。IntentService中的工作请求按顺序运行,请求将一直等待,直到当前操作完成。在IntentService上运行的操作不能被中断。

一个AlarmManager 2 是 Android 中的一种机制,允许代码在后台线程中延迟和继续运行。一个AlarmManager可以被配置为在未来的特定时间以预先配置的时间间隔运行。AlarmManager还有一个setAlarmClock选项,允许它在设备处于低功耗空闲或打盹模式时触发。

可以设置一个BroadcastReceiver来监听引导完成意图 3 ,如第 4 章所述。该意图在设备重启后启动时发送。反过来,在接收到BootComplete意图后启动AlarmManager将意味着设备重启后后台服务继续运行。

从 Android Oreo 8(API 26 级)开始,Android 服务不再能从后台进程启动。 4 这意味着在 Android 8+中,要使用JobSchedulers或前台服务。请记住,虽然该功能仅适用于针对 Android 8+的应用,但它可以由用户在设置 5 页面中启用,这也对服务如何在后台运行提出了许多额外的限制。

下面的函数将设置一个 报警管理器 按照 waitBeforeRepeatInMinutes 参数的定义每 x 分钟重复一次:

public void startPeriodicWork(long waitBeforeRepeatInMinutes){

  // Construct an intent that will execute the AlarmReceiver
  Intent intent = new Intent(context, AlarmReceiver.class);

  // Create a PendingIntent to be triggered when the alarm goes off
  final PendingIntent pIntent = PendingIntent.getBroadcast(context, AlarmReceiver.REQUEST_CODE,
          intent, PendingIntent.FLAG_UPDATE_CURRENT);

  // Setup periodic alarm every every half hour from this point onwards
  long firstMillis = System.currentTimeMillis(); // alarm is set right away
  AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

  // First parameter is the type: ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC_WAKEUP
  // Interval can be INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_DAY

  if (alarm != null) {
      alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstMillis,
              waitBeforeRepeatInMinutes * 60 * 1000, pIntent);
  }

}

接下来,创建 AlarmManager BroadcastReceiver 类。在 Android manifest 中设置 process 属性,这样如果应用关闭了 6 ,它将继续保持活动状态。作为其中的一部分,将 BroadcastReceiver 添加到 AndroidManifest.xml 文件中。

<receiver android:name=".receivers.AlarmReceiver"
    android:process=":remote" />

警报接收者 中增加以下内容。java:

public class AlarmReceiver extends BroadcastReceiver {
    public static final int REQUEST_CODE = 12345;

    // Triggered by the Alarm periodically (starts the service to run task)
    @Override
    public void onReceive(Context context, Intent intent) {

        int tid = Process.myTid();
        Log.v("TaskScheduler", "Started Alarm Receiver with tid "+ tid);

        TaskManager taskManager = new TaskManager(context);
        taskManager.oneOffTask();
    }
}

接下来,为 BootComplete 添加 BroadcastReceiver。将以下内容添加到 AndroidManifest.xml 文件中:

<receiver android:name=".receivers.BootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

然后创建BootReceiver.java类:

public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        int tid = Process.myTid();
        Log.v("TaskScheduler", "Started Boot Complete Receiver with tid "+ tid);

        TaskManager taskManager = new TaskManager(context);
        taskManager.startPeriodicWork(5);
    }
}

最后创造出IntentService;为此,创建一个名为 ServiceManager.java: 的文件

public class ServiceManager extends IntentService {

    public ServiceManager() {
        super("ServiceTest"); //Used to name the worker thread, important only for debugging.
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        int tid = Process.myTid();
        Log.v("TaskScheduler", "Started Service with tid "+ tid);

        String val = intent.getStringExtra("foo");
        //todo Add the work to be performed here.
    }
}

将此服务添加到 AndroidManifest.xml 文件:

<service android:name=".managers.ServiceManager"
    android:exported="false"/>

前台服务

Android 中有一系列不同类型的服务 7 ,从启动的服务(运行在 UI 线程中)到IntentService(运行在自己的线程中)再到绑定的服务(只要有一个活动绑定到它就运行)。

截至 Android 8 Oreo (API 26),Android 应用运行后台服务有限制,除非应用本身在前台。在这种情况下,应该使用startForegroundService()方法,而不是使用context.startService()方法。在此之后,服务有 5 秒钟的时间向用户显示通知并调用startForeground(1, notification)方法,该方法在服务期间一直存在,直到stopForeground(true)stopSelf()方法被调用。前台服务像任何其他服务一样扩展了Service类,并且除了遵循前面的规则和限制之外,以相同的方式进行操作。

应该使用如下代码来标识应该使用后台服务还是前台服务:

Intent intent = new Intent(context, ServiceManager.class); //replace with an appropriate intent
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(intent);
}else{
    context.startService(intent);
}

由于通知是前台服务启动的一部分,这意味着它的副产品是建立一个通知通道 8 (针对 Android 8.0 - API 级别 26 及以上)。添加通知通道是为最终用户提供细粒度访问的一种方式,允许他们更改通知设置并决定应用中的哪些通知通道应该可见。

以下显示了设置通知通道的示例:

public static void createNotificationChannel(Context context) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        int importance = NotificationManager.IMPORTANCE_DEFAULT;
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance);
        channel.setDescription(CHANNEL_DESC);

        NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
        if (notificationManager != null) {
            notificationManager.createNotificationChannel(channel);
        }
    }
}

发送通知:

Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
        0, notificationIntent, 0);

Notification notification = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
        .setContentTitle("Notification Title")
        .setContentText("Notification Text")
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentIntent(pendingIntent)
        .build();

startForeground(1, notification);
}

从 Android 9 (API level 28)开始,除了如下将您的服务添加到 Android 清单中,您还需要添加 FOREGROUND_SERVICE 权限:

<service android:name=".managers.ForegroundServiceManager"
    android:exported="false"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

作业调度程序

从 Android 5 L (API 21)开始,当设备有更多可用资源时,作业调度器作为一种任务批处理作业的方式被引入。作为一个整体,多项工作可以由JobSchedulers ,来完成,他们会将这些任务分成几批。这意味着所分配的工作可能不会按预期执行;然而,它将在该时间前后发生(例如,被指示每 15 分钟执行一次的任务可能在一次运行的 14 分钟后执行,而在另一次运行的 16 分钟后执行)。

JobSchedulers 最强大的功能之一是,如果设置或未设置特定的标准,例如,没有网络连接、电池正在充电或设备处于空闲状态,它们允许工作延期。还有一个选项是用setPeriodicsetPersisted来运行周期性的工作,并在重启后继续运行(如果应用拥有 RECEIVE_BOOT_COMPLETED 权限)。setOverrideDeadline选项还允许在运行一次性工作的最大时间内,允许在强制运行工作之前等待一段时间。

添加以下内容。如果不想要周期工作器,则删除 setPeriodic 和 setPersisted 标记:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void startJobScheduler(){
    ComponentName serviceComponent = new ComponentName(context, JobSchedulerManager.class);
    JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
    //builder.setMinimumLatency(1 * 1000); // wait at least /Can't call setMinimumLatency() on a periodic job/
    //builder.setOverrideDeadline(3 * 1000); // maximum delay //Can't call setOverrideDeadline() on a periodic job.
    builder.setPeriodic(1000); //runs over time
    builder.setPersisted(true); // persists over reboot
    //builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); // require unmetered network
    //builder.setRequiresDeviceIdle(true); // device should be idle
    //builder.setRequiresCharging(false); // we don't care if the device is charging or not
    JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);

    if (jobScheduler != null) {
        jobScheduler.schedule(builder.build());
    }
}

然后做一个名为 JobSchedulerManager.java 的类:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class JobSchedulerManager extends JobService {

    @Override
    public boolean onStartJob(JobParameters jobParameters) {

        int tid = Process.myTid();
        Log.v("TaskScheduler", "Started Job Scheduler with tid "+ tid);

        //todo perform work here

        // returning false means the work has been done, return true if the job is being run asynchronously
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

将下面的类添加到 AndroidManifest.xml 文件中:

<service android:name=".managers.JobSchedulerManager"
    android:permission="android.permission.BIND_JOB_SERVICE"/>

一个JobScheduler服务必须用先前的权限来保护,这样只有具有该权限的应用才能给该服务分配任务。如果在清单中声明了作业服务,但未使用此权限进行保护,则该服务将被系统忽略。

工作经理

如 Android 文档所述, 9 WorkManagers从 API 14 开始向后兼容。API 14-22 上使用了BroadcastReceiverAlarmManager的组合,API 23+上使用了JobScheduler。使用WorkManager而不是AlarmManager的一个主要缺点是对它们的运行时间有限制(这是从在幕后使用JobScheduler继承而来的);这包括WorkManager运行时间不得超过 10 分钟,并且在当前工作开始至少 15 分钟后才能执行另一项连续工作。这样做的原因是为了遵守瞌睡限制。

使用工作管理器时,将以下依赖项添加到 gradle.build 文件中:

def work_version = "2.3.3"

  // (Java only)
  implementation "androidx.work:work-runtime:$work_version"

  // Kotlin + coroutines
  implementation "androidx.work:work-runtime-ktx:$work_version"

下面的代码执行任务并启动一个工作管理器:

PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(
        com.example.taskscheduler.managers.WorkManager.class, 15, TimeUnit.MINUTES)
        .build(); //update path to match your created WorkManager.java class

WorkManager.getInstance().cancelAllWork();
WorkManager.getInstance().enqueue(work);

最后创建一个名为 WorkManager.java 的类:

public class WorkManager extends Worker {

    Context context;

    public WorkManager(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);

        this.context = context;
    }

    @Override
    public Result doWork() {
        int tid = Process.myTid();
        Log.v("TaskScheduler", "Worker started with tid "+ tid);
        // Todo run your work here.
        return Result.success();
    }
}

穿线

main应用线程(由系统为每个应用创建)之外,一个应用可以有多个额外的执行线程。然而,线程的设置相当简单,因为AsyncTasks被绑定到它们的父线程(通常是一个Activity)的生命中。在这种情况下,如果线程的父线程被破坏(即,被用户从任务堆栈中移除),则该线程受到垃圾收集(其中移除未使用的资源以为其他组件回收内存)。

启动线程:

public void startThread(){
    Thread thread = new ThreadManager();
    thread.start();
}

制作一个名为 ThreadManager.java 的 java 类:

public class ThreadManager extends Thread{
    public ThreadManager() {
        super();
    }

    @Override
    public void run() {
        long tid = getId();

        // Todo do work here.
        Log.v("TaskScheduler", "Starting a new thread "+ tid);

        while (true){
            Log.v("TaskScheduler", "In a thread: " + tid);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

异步任务

在 Android R–API level 30 中不推荐使用(建议使用标准的java.util.concurrent或 Kotlin 并发实用程序) AsyncTasks 允许在单独的线程上短期运行(一次仅运行几秒钟)代码,同时还可以访问 UI 线程。当构造一个AsyncTask对象时,需要提供三种类型;这些是:

  • 传递到 AsyncTask 执行中的参数的类型

  • 后台计算期间使用的进度单位的类型

  • 后台方法的结果的类型

必须在主线程上加载、创建和执行——从 API 级开始,这是自动完成的。

调用异步任务:

AsyncTask<String, Void, Void> task = new myAsyncTask(getApplicationContext()).execute("example string");

取消异步任务:

task.cancel(true);

创建一个 AsyncTask 类(作为活动类的私有或包私有子类):

class myAsyncTask extends AsyncTask<String, Void, Void> {
    private Context mContext;

    public myAsyncTask(Context context) {
        this.mContext = context;
    }

    @Override
    protected Void doInBackground(final String... strings) {
        final Context context = this.mContext;

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                for (String text:strings) {

                    Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
                }
            }
        });

        return null;
    }
}

电池和安全对长期运行服务的影响

长期运行的服务可以为用户提供无数有用的功能;然而,由于电池和安全问题,有无数种方法可以提前终止长期运行的服务。这随服务类型的不同而不同;但是,这通常是由电源管理限制引起的。 10 这些包括以下运行时的限制:

  • 十二模式【11】

  • App 待机桶 12

  • 应用背景限制

  • 应用电池优化

瞌睡

Doze 是在 Android 6 (API level 23)中引入的,当设备空闲时(意味着设备最近一段时间没有接收用户交互),Doze 通过将应用运行时划分到维护窗口中来充当电池节省工具。这些应用可以在其中运行后台任务的窗口开始时很频繁,但是,随着时间的推移,设备空闲的时间越长,这些窗口就会变得越来越不同。这意味着虽然一个AlarmManager可能被分配每 5 分钟运行一次的任务,但是瞌睡限制会阻止它定期运行。

报警管理器的限制:

  • setExact()setWindow()报警管理器报警被推迟到下一个维护窗口。

  • setAndAllowWhileIdle()setExactAndAllowWhileIdle()将在打盹维护窗口期间正常启动。setAlarmClock()也将在维护窗口期间启动,系统在警报触发前不久退出休眠。

作业计划程序的限制:

  • 作业调度程序或工作管理器被挂起。

其他限制:

  • 网络访问、唤醒锁、Wi-Fi 扫描和同步适配器会被忽略和暂停。

您可以使用下面的命令测试一个应用如何在 Doze 模式下运行(在 API 级别 23 以上:

adb shell dumpsys deviceidle force-idle

通过运行以下命令可以退出空闲模式:

adb shell dumpsys deviceidle unforce
应用备用桶

Android 9 (API 级别 28)增加了另一个省电功能。此功能将所有应用分配到四个存储桶之一。每个制造商可以为如何将应用放入每个桶中设置自己的标准(Android 文档强调“机器学习”技术可以用于支持这一决策过程)。反过来,为什么应用被分配特定的存储桶的确切原理是未知的。

这些桶是

  • 活动 -应用当前正在使用或最近使用过,包括应用是否已启动活动或正在运行前台服务,或者用户已点击应用的通知:

    • 工作 -无限制

    • 警报 -无限制

  • 工作集-app 正常使用:

    • 作业 -最多延迟 2 小时

    • 警报 -最多延迟 6 分钟

  • 频繁 -经常使用该应用,但不是每天都使用:

    • 作业 -最多延迟 8 小时

    • 警报 -最多延迟 30 分钟

  • 稀有 -不常用的应用:

    • 作业 -最多延迟 24 小时

    • 警报 -最多延迟 2 小时

    • 联网 -最多延迟 24 小时

  • Never -应用已安装,但从未运行:

    • 如果应用从未运行过,组件将被禁用。

修改组件的状态

应用组件是用前面提到的AndroidManifest.xml file. A编写的,有四种主要类型的组件,它们是

  • 活动

  • 服务

  • 广播接收机

  • 内容供应器

静态修改组件状态

可以通过在 Android 清单中编辑组件条目的android:enabled="false"标签来静态修改组件。以下活动SecondaryActivity已经通过其在 Android 清单中的条目被默认禁用。

设置组件的启用属性:

<activity android:name=".SecondaryActivity"
    android:enabled="false"
 />

动态修改组件

组件可以通过编程设置为三种主要状态,它们是

  • 组件 _ 启用 _ 状态 _ 默认

    • 将组件设置为清单中定义的默认状态。
  • 组件已启用状态已启用

    • 显式启用组件。
  • 组件 _ 启用 _ 状态 _ 禁用

    • 显式禁用组件。禁用的组件不能使用或启动。

有两种其他的组成状态;然而,这些不能用setComponentEnabledSetting方法设置。这些是

  • 组件 _ 启用 _ 状态 _ 禁用 _ 用户

    • 显式禁用该组件,并且可以由用户在适当的系统用户界面中重新启用。
  • 组件 _ 启用 _ 状态 _ 禁用 _ 直到 _ 使用

    • 这种状态意味着组件应该被识别为禁用的(即,在启动器中不显示活动),直到用户明确地试图在它应该被设置为启用的地方使用它。

启用组件 :

PackageManager packageManager = getApplicationContext().getPackageManager();
ComponentName componentName = new ComponentName(getApplicationContext(), SecondaryActivity.class);

packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,PackageManager.DONT_KILL_APP);

返回组件的状态:

PackageManager packageManager = getApplicationContext().getPackageManager();
ComponentName componentName = new ComponentName(getApplicationContext(), SecondaryActivity.class);

int componentState = packageManager.getComponentEnabledSetting(componentName);

禁用一个组件 :

PackageManager packageManager = getApplicationContext().getPackageManager();
ComponentName componentName = new ComponentName(getApplicationContext(), SecondaryActivity.class);
packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,PackageManager.DONT_KILL_APP);

创建 Android 启动器

Android Launcher 在 API 级别 1 中实现,是 Android 的一个组件,它允许 Android 应用作为 Android 设备主屏幕上的基本活动(如图 9-2 所示)。这些主屏幕可以由各个原始设备制造商设置;然而,其他著名的发射器包括 Facebook Home。必须在 Android 设备的设置菜单中设置一个启动器,如图 9-1 所示。

img/509502_1_En_9_Fig2_HTML.jpg

图 9-2

示例启动器

img/509502_1_En_9_Fig1_HTML.jpg

图 9-1

启动器设置

创建启动器应用

首先将以下属性添加到 AndroidManifest.xml 文件中的 activities 活动标记:

android:launchMode="singleTask"

然后给同一个活动标签的意图过滤器添加两个类别:

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.HOME" />

在这个阶段,该应用将作为一个启动器,并且可以从 Android 设备的设置中选择作为主屏幕。下面详细介绍了几个在创建启动器时有用的附加技术。

附加功能

检索应用列表:

private List<ResolveInfo> getListOfApplications(Context context){
    Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
    mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
    List<ResolveInfo> pkgAppsList = context.getPackageManager().queryIntentActivities( mainIntent, 0);
    return pkgAppsList;
}

检索应用的图标:

public static Drawable getActivityIcon(Context context, String packageName, String activityName) {
    PackageManager pm = context.getPackageManager();
    Intent intent = new Intent();
    intent.setComponent(new ComponentName(packageName, activityName));
    ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);

    return resolveInfo.loadIcon(pm);
}

设置图像视图。将一个 ImageView 对象添加到您的活动中,名称为 imageView:

<ImageView
    android:id="@+id/imageView"
    android:layout_width="129dp"
    android:layout_height="129dp"
    android:foregroundGravity="center_vertical"
    app:srcCompat="@android:drawable/ic_dialog_alert"
    android:layout_gravity="center"
    />

为 ImageView : 创建一个点击监听器

ImageView chromeIcon = (ImageView) findViewById(R.id.imageView);
chromeIcon.setImageDrawable(getActivityIcon(getApplicationContext(),"com.android.chrome", "com.google.android.apps.chrome.Main"));

ImageView img = findViewById(R.id.imageView);
img.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {

        Intent launchIntent = getPackageManager().getLaunchIntentForPackage("com.android.chrome");
        startActivity(launchIntent);
    }
});

设置壁纸。将下面的代码添加到 styles.xml 的名称为 AppTheme 的样式标记中:

<item name="android:windowShowWallpaper">true</item>
<item name="android:windowBackground">@android:color/transparent</item>

隐藏系统界面 :

private void hideSystemUI() {
    View decorView = getWindow().getDecorView();
    decorView.setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_IMMERSIVE
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_FULLSCREEN);
Footnotes [1](#Fn1_source) “创建后台服务| Android 开发者。” [`https://developer.android.com/training/run-background-service/create-service`](https://developer.android.com/training/run-background-service/create-service) 。5 月 11 日访问。2020.   [2](#Fn2_source) " Android . app . alarm manager-Android 开发者。"2019 年 12 月 27 日, [`https://developer.android.com/reference/android/app/AlarmManager`](https://developer.android.com/reference/android/app/AlarmManager) 。5 月 11 日访问。2020.   [3](#Fn3_source) “广播概述| Android 开发人员。”2019 年 6 月 3 日, [`https://developer.android.com/guide/components/broadcasts`](https://developer.android.com/guide/components/broadcasts) 。5 月 11 日访问。2020.   [4](#Fn4_source) “Android 8.0:Java . lang . illegalstateexception:不允许....” [`https://stackoverflow.com/questions/46445265/android-8-0-java-lang-illegalstateexception-not-allowed-to-start-service-inten`](https://stackoverflow.com/questions/46445265/android-8-0-java-lang-illegalstateexception-not-allowed-to-start-service-inten) 。5 月 21 日访问。2020.   [5](#Fn5_source) “后台执行限制|安卓开发者。”2020 年 2 月 13 日, [`https://developer.android.com/about/versions/oreo/background`](https://developer.android.com/about/versions/oreo/background) 。5 月 11 日访问。2020.   [6](#Fn6_source) “我是否应该在我的接收器中使用 Android:process = ":remote….” [`https://stackoverflow.com/questions/4311069/should-i-use-android-process-remote-in-my-receiver`](https://stackoverflow.com/questions/4311069/should-i-use-android-process-remote-in-my-receiver) 。5 月 21 日访问。2020.   [7](#Fn7_source) “服务概述| Android 开发人员。”2019 年 6 月 3 日, [`https://developer.android.com/guide/components/services`](https://developer.android.com/guide/components/services) 。5 月 11 日访问。2020.   [8](#Fn8_source) "创建和管理通知渠道…" [`https://developer.android.com/training/notify-user/channels`](https://developer.android.com/training/notify-user/channels) 。5 月 11 日访问。2020.   [9](#Fn9_source) “使用 WorkManager | Android 开发人员安排任务。” [`https://developer.android.com/topic/libraries/architecture/workmanager`](https://developer.android.com/topic/libraries/architecture/workmanager) 。5 月 11 日访问。2020.   [10](#Fn10_source) “电源管理限制| Android 开发人员。”2018 年 6 月 3 日, [`https://developer.android.com/topic/performance/power/power-details`](https://developer.android.com/topic/performance/power/power-details) 。5 月 11 日访问。2020.   [11](#Fn11_source) “针对瞌睡和应用待机进行优化……” [`https://developer.android.com/training/monitoring-device-state/doze-standby`](https://developer.android.com/training/monitoring-device-state/doze-standby) 。5 月 11 日访问。2020.   [12](#Fn12_source) “应用待机桶|安卓开发者。”2018 年 6 月 3 日, [`https://developer.android.com/topic/performance/appstandby`](https://developer.android.com/topic/performance/appstandby) 。5 月 11 日访问。2020.