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

1 APP调用startServicestartForegroundService启动一个service.

startServicestartForegroundService在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方法,该方法会调用ActiveServicessetServiceForegroundLocked方法。

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中调用startForegroundmAppOnTop的状态变更)会触发该通知的更新外,还有一些其它情况会触发更新。

从上面代码的分析中,我们知道,触发更新的地方必须要调用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. jquery校验框架

    http://www.validform.club/ http://craftpip.github.io/jquery-confirm/

  2. myloader恢复mysql数据库演示样例

     mydumper是针对mysql数据库备份的一个轻量级第三方的开源工具.备份方式为逻辑备份.它支持多线程.备份速度远高于原生态的mysqldump以及众多优异特性.与其相配套的恢复工具则是mylo ...

  3. Django学习系列之Form验证

    django表单基础 django表单分类 基于django.forms.Form:所有表单类的父类 基于django.forms.ModelForm:可以和模型类绑定的Form Form验证流程 定 ...

  4. label显示不同颜色的字体

    NSString *contentSrt = [NSString stringWithFormat:@"%@ (%@)",categoryModel.categoryName, c ...

  5. Mesos, Marathon, Docker 平台部署记录

    Mesos, Marathon, Docker 平台部署记录 所有组件部署基于Ubuntu 14.04 x64 主机 IP 角色 master 192.168.1.3 Mesos Master, Ma ...

  6. Tcl脚本调用高层API实现仪表使用和主机创建配置的自己主动化測试用例

    #设置Chassis的基本參数,包含IP地址.port的数量等等 set chassisAddr 10.132.238.190 set islot 1 set portList {11 12} ;#端 ...

  7. firewalld 防火墙 nat 网络地址转换

    目的:实现以下效果 一. 准备环境 @1 三台虚拟机 @2  client 端 ip  192.168.1.2      server端   两块网卡 , ip 分别是 192.168.1.1   和 ...

  8. u

    Atrrible /s/d –s –h  u盘修复

  9. HDUOJ Number Sequence 题目1005

     /*Number Sequence Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Oth ...

  10. luogu 2622 关灯问题II

    题目大意: 有一些灯,有些开关可以控制这些灯,给出矩阵表示控制 对于矩阵中的a i j 表示第i个开关控制第j个灯的情况 若元素为1 表示当灯开着的时候,关掉灯 若元素为0 表示无操作 若元素为-1 ...