Android O新增的一个特性,系统会在通知栏显示当前在后台运行的应用,其实际是显示启动了前台服务的应用,并且当前应用的Activity不在前台。具体我们看下源码是怎么实现的。

1 APP调用startService或startForegroundService启动一个service.

startService和startForegroundService在Android O上主要有两个区别:

一个是后台应用无法通过startService启动一个服务,而无论前台应用还是后台应用,都可以通过startForegroundService启动一个服务。

此外Android规定,在调用startForegroundService启动一个服务后,需要在服务被启动后5秒内调用startForeground方法,

否则会结束掉该service并且抛出一个ANR异常。关于前台应用和后台应用的规范见官网

2 Service被启动后,需要调用startForeground方法,将service置为前台服务。其中有一个地方要注意,第一个参数id不能等于0。

    public final void startForeground(int id, Notification notification) {
try {
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, id,
notification, 0);
} catch (RemoteException ex) {
}
}

接着调用了AMS的setServiceForeground方法,该方法会调用ActiveServices的setServiceForegroundLocked方法。

ActiveServices是用来辅助AMS管理应用service的一个类。

    public void setServiceForegroundLocked(ComponentName className, IBinder token,
int id, Notification notification, int flags) {
final int userId = UserHandle.getCallingUserId();
final long origId = Binder.clearCallingIdentity();
try {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
setServiceForegroundInnerLocked(r, id, notification, flags);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}

3 调用了setServiceForegroundInnerLocked方法

   private void setServiceForegroundInnerLocked(ServiceRecord r, int id,
Notification notification, int flags) {
if (id != 0) {
if (notification == null) {
throw new IllegalArgumentException("null notification");
}
// Instant apps need permission to create foreground services.
// ...A lot of code is omitted
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
r.foregroundNoti = notification;
if (!r.isForeground) {
final ServiceMap smap = getServiceMapLocked(r.userId);
if (smap != null) {
ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName);
if (active == null) {
active = new ActiveForegroundApp();
active.mPackageName = r.packageName;
active.mUid = r.appInfo.uid;
active.mShownWhileScreenOn = mScreenOn;
if (r.app != null) {
active.mAppOnTop = active.mShownWhileTop =
r.app.uidRecord.curProcState <= ActivityManager.PROCESS_STATE_TOP;
}
active.mStartTime = active.mStartVisibleTime
= SystemClock.elapsedRealtime();
smap.mActiveForegroundApps.put(r.packageName, active);
requestUpdateActiveForegroundAppsLocked(smap, 0);
}
active.mNumActive++;
}
r.isForeground = true;
}
r.postNotification();
if (r.app != null) {
updateServiceForegroundLocked(r.app, true);
}
getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
}
}

该方法主要做的事情,创建一个ActiveForegroundApp实例,并把实例加入到smap.mActiveForegroundApps,

调用requestUpdateActiveForegroundAppsLocked,设置ServiceRecord的isForeground = true.

由此可见,所有的前台服务都会在smap.mActiveForegroundApps列表中对应一个实例。

requestUpdateActiveForegroundAppsLocked方法又调用了updateForegroundApps方法,见下面代码。

这里有个关键代码是

active.mAppOnTop = active.mShownWhileTop =
r.app.uidRecord.curProcState <= ActivityManager.PROCESS_STATE_TOP;

下面会再次提到这段代码。

4 updateForegroundApps方法。通知栏上面的“running in the background”就是在这个方法里面去更新的。

    void updateForegroundApps(ServiceMap smap) {
// This is called from the handler without the lock held.
ArrayList<ActiveForegroundApp> active = null;
synchronized (mAm) {
final long now = SystemClock.elapsedRealtime();
long nextUpdateTime = Long.MAX_VALUE;
if (smap != null) {
for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) {
ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i);
// ...A lot of code is omitted
if (!aa.mAppOnTop) {
if (active == null) {
active = new ArrayList<>();
}
active.add(aa);
}
}
}
} final NotificationManager nm = (NotificationManager) mAm.mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
final Context context = mAm.mContext; if (active != null) {
// ...A lot of code is omitted
//这里是更新通知的地方,具体代码太长,省略掉了。
} else {
//如果active为空,取消掉通知。
nm.cancelAsUser(null, SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
new UserHandle(smap.mUserId));
}
}
  • 遍历smap.mActiveForegroundApps列表,判断列表中的元素,如果其mAppOnTop成员属性为false,则加入active列表中。

  • 根据active列表,更新notification。

可见,只有在smap.mActiveForegroundApps列表中,并且mAppOnTop为false的前台服务才会显示在通知栏中的“running in the background”中。

以上是一个应用启动一个前台服务到被显示在通知栏中的“running in the background”中的代码上的流程。此外我们再了解些相关的逻辑。

mAppOnTop的状态

从上面的分析看出,mAppOnTop的值决定了一个前台服务是否会被显示在通知栏的“running in the background”中。mAppOnTop的状态除了会在创建的时候赋值,还会在另一个方法中被更新。

每次更新后,如果值有变化,就会调用requestUpdateActiveForegroundAppsLocked,该方法上面分析过了。

    void foregroundServiceProcStateChangedLocked(UidRecord uidRec) {
ServiceMap smap = mServiceMap.get(UserHandle.getUserId(uidRec.uid));
if (smap != null) {
boolean changed = false;
for (int j = smap.mActiveForegroundApps.size()-1; j >= 0; j--) {
ActiveForegroundApp active = smap.mActiveForegroundApps.valueAt(j);
if (active.mUid == uidRec.uid) {
if (uidRec.curProcState <= ActivityManager.PROCESS_STATE_TOP) {
if (!active.mAppOnTop) {
active.mAppOnTop = true;
changed = true;
}
active.mShownWhileTop = true;
} else if (active.mAppOnTop) {
active.mAppOnTop = false;
changed = true;
}
}
}
if (changed) {
requestUpdateActiveForegroundAppsLocked(smap, 0);
}
}
}

该方法传入一个uidrecord参数,指定具体的uid及相关状态。判断逻辑跟前面setServiceForegroundInnerLocked方法的逻辑一致。

其中ActivityManager.PROCESS_STATE_TOP的官方解释是

Process is hosting the current top activities. Note that this covers all activities that are visible to the user.

意思就是,当前进程的一个activity在栈顶,覆盖了所有其它activity,用户可以真正看到的。

换句话,如果用户不能直接看到该应用的activity,并且该应用启动了一个前台服务,那么就会被显示在“running in the background”中。

foregroundServiceProcStateChangedLocked方法只有一处调用,AMS的updateOomAdjLocked,该方法的调用地方太多,无法一一分析。

"running in the background"通知的更新。

除了以上两种情况(应用在service中调用startForeground和mAppOnTop的状态变更)会触发该通知的更新外,还有一些其它情况会触发更新。

从上面代码的分析中,我们知道,触发更新的地方必须要调用requestUpdateActiveForegroundAppsLocked方法。

该方法会在ActiveSercices类中的如下几个方法中调用。

setServiceForegroundInnerLocked (这个我们前面分析过)

decActiveForegroundAppLocked (减小前台应用)

updateScreenStateLocked (屏幕状态变化)

foregroundServiceProcStateChangedLocked (进程状态变化)

forceStopPackageLocked (强制停止应用)

我们看下其中的decActiveForegroundAppLocked方法

decActiveForegroundAppLocked

    private void decActiveForegroundAppLocked(ServiceMap smap, ServiceRecord r) {
ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName);
if (active != null) {
active.mNumActive--;
if (active.mNumActive <= 0) {
active.mEndTime = SystemClock.elapsedRealtime();
if (foregroundAppShownEnoughLocked(active, active.mEndTime)) {
// Have been active for long enough that we will remove it immediately.
smap.mActiveForegroundApps.remove(r.packageName);
smap.mActiveForegroundAppsChanged = true;
requestUpdateActiveForegroundAppsLocked(smap, 0);
} else if (active.mHideTime < Long.MAX_VALUE){
requestUpdateActiveForegroundAppsLocked(smap, active.mHideTime);
}
}
}
}

该方法主要是移除前台service,根据foregroundAppShownEnoughLocked判断,是否马上移除还是过一段时间移除。

该方法主要在两个地方调用。一个是在setServiceForegroundInnerLocked中调用,当应用调用startForeground,第一个参数设为0时,会走到这个路径。

另一个bringDownServiceLocked,也就是当绑定到该service的数量减小时,会调用该方法。

前台服务是否一定会在通知栏显示应用自己的通知

如果是一定的话,我想系统也没必要再额外显示一条“running in the background”的通知,列出所有后台运行的应用了。

所以答案是不一定,虽然在调用startForeground方法时,必须要传一个notification作为参数,但依然会有两种情况会导致不会在通知栏显示应用发的通知。

  • 用户主动屏蔽应用通知,可以通过长按通知,点击“ALL CATEGORIES”进入通知管理,关闭通知。关闭后前台服务依然生效。
  • NotificationManager在显示应用通知的时候,因为某些原因显示失败,失败原因可能是应用创建了不规范的通知,比如Android O新增了NotificationChannel,应用在创建通知的时候,必须指定一个NotificationChannel,但是如果应用创建通知的时候,指定的NotificationChannel是无效的,或者直接传null作为参数值,那么在NotificationManager就没办法显示该通知。这种情况下,前台服务还是会生效,但是却不会在通知栏显示应用的通知,不过NotificationManager发现不规范的通知时,一般会弹出一个toast提醒用户。

Android O 通知栏的"running in the background"的更多相关文章

  1. 解决Android Studio Gradle Build Running慢的问题

    Android Studio方便好用,但是Android Studio Gradle Build Running很慢 解决方法: C:\Users\你的用户名\.gradle 目录下新建一个文件名为 ...

  2. android 沉浸通知栏

    IOS的沉浸式通知栏很高大上,通知栏和app统一颜色或样式,很美观.android上面也早就人实现这种效果了. 我在这边也写一个实现通知栏沉浸式的方法,目前只实现了相同颜色. 先要改布局文件xml & ...

  3. Android:通知栏的使用

    非常久没有使用Android的通知功能了,今天把两年前的代码搬出来一看.发现非常多方法都废弃了,代码中各种删除线看的十分不爽.于是乎,打开Google,查看官方文档.学习最新的发送通知栏消息的方法. ...

  4. android 自定义通知栏

    package com.example.mvp; import cn.ljuns.temperature.view.TemperatureView;import presenter.ILoginPre ...

  5. Android Studio Gradle Build Running 特别慢的问题探讨

    本文的本本win7 64bit 6G android studio2.1 在运行程序的时候Gradle Build Running 特别慢,一个helloworld都快2min了 1.开启gradle ...

  6. Android编译报Errors running builder 'Android Pre Compiler' on project 'XXX' java.lang.NullPointerException

    编译android时,遇到报错:Errors occurred during the build.Errors running builder 'Android Pre Compiler' on pr ...

  7. Android notifications通知栏的使用

    app发送通知消息到通知栏中的关键代码和点击事件: package com.example.notifications; import android.os.Bundle; import androi ...

  8. Android——状态栏通知栏Notification

    1.AndroidManifest.xml注意要同时注册Notification02Activity <!-- 状态通知栏 Notification -->        <acti ...

  9. android显示通知栏Notification以及自定义Notification的View

    遇到的最大的问题是监听不到用户清除通知栏的广播.所以是不能监听到的. 自定义通知栏的View,然后service运行时更改notification的信息. /** * Show a notificat ...

随机推荐

  1. kkpager的改进,Ajax数据变化但是页码不变的问题

    原文:http://blog.csdn.net/xiaojian1018/article/details/45564051 kkpager 是一个简单分页展示插件,需要依赖jquery.下载地址:ht ...

  2. Swift可选类型(Optional)之星耀

    首先我们先看下Objective-C与Swift语言对于可选nil的不同理解: Objective-C中的nil:表示缺少一个合法的对象,是指向不存在对象的指针,对结构体.枚举等类型不起作用(会返回N ...

  3. WIN10中使用Hyper-V 配置虚拟机宿主机互ping

    在Windows10  Hyper-V 中安装 Linux (Ubuntu16.04)虚拟机无法 ping 通宿主机,宿主机可以ping通虚拟机. 这种情况下关闭 Windows 防火墙就能ping通 ...

  4. Codeforces Round #337 (Div. 2) 610B Vika and Squares(脑洞)

    B. Vika and Squares time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  5. Python爬虫开发【第1篇】【Json与JsonPath】

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写.同时也方便了机器进行解析和生成.适用于进行数据交互的场景,比如网站前台与 ...

  6. 安装ubuntu后启动黑屏

    我是在windows7上的一个空暇盘上安装ubuntu 14.安装后重新启动没有ubuntu的启动项,然后用easybcd生成启动项,重新启动发现果然有,可是选择之后黑屏. 百度半天无果.后来无意发现 ...

  7. iOS开发——常见BUG——window决定程序的状态栏管理问题

    Xcode7升级之后遇到的问题   问题一: 老项目在Xcode6上运行没有任何问题,但在Xcode7上运行直接崩了! 经过一波分析: 发现是因为我顶部状态栏处添加了topWindow,用于处理Tab ...

  8. regulator_get 调用过程【转】

    本文转载自:http://blog.csdn.net/u012719256/article/details/52083961 Touch panel DTS 分析(MSM8994平台,Atmel 芯片 ...

  9. python中如何理解if __name__ == '__main__'

    __name__ 是当前模块名,当模块被直接运行时模块名为 __main__ .这句话的意思就是,当模块被直接运行时,以下代码块将被运行,当模块是被导入时,代码块不被运行.我们通过一个简单的例子来理解 ...

  10. BZOJ_1150_[CTSC2007]数据备份Backup_堆+贪心

    BZOJ_1150_[CTSC2007]数据备份Backup_堆+贪心 Description 你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份.然而数据备份的工作是枯燥乏 ...