Android O 通知栏的"running in the background"
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"的更多相关文章
- 解决Android Studio Gradle Build Running慢的问题
Android Studio方便好用,但是Android Studio Gradle Build Running很慢 解决方法: C:\Users\你的用户名\.gradle 目录下新建一个文件名为 ...
- android 沉浸通知栏
IOS的沉浸式通知栏很高大上,通知栏和app统一颜色或样式,很美观.android上面也早就人实现这种效果了. 我在这边也写一个实现通知栏沉浸式的方法,目前只实现了相同颜色. 先要改布局文件xml & ...
- Android:通知栏的使用
非常久没有使用Android的通知功能了,今天把两年前的代码搬出来一看.发现非常多方法都废弃了,代码中各种删除线看的十分不爽.于是乎,打开Google,查看官方文档.学习最新的发送通知栏消息的方法. ...
- android 自定义通知栏
package com.example.mvp; import cn.ljuns.temperature.view.TemperatureView;import presenter.ILoginPre ...
- Android Studio Gradle Build Running 特别慢的问题探讨
本文的本本win7 64bit 6G android studio2.1 在运行程序的时候Gradle Build Running 特别慢,一个helloworld都快2min了 1.开启gradle ...
- 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 ...
- Android notifications通知栏的使用
app发送通知消息到通知栏中的关键代码和点击事件: package com.example.notifications; import android.os.Bundle; import androi ...
- Android——状态栏通知栏Notification
1.AndroidManifest.xml注意要同时注册Notification02Activity <!-- 状态通知栏 Notification --> <acti ...
- android显示通知栏Notification以及自定义Notification的View
遇到的最大的问题是监听不到用户清除通知栏的广播.所以是不能监听到的. 自定义通知栏的View,然后service运行时更改notification的信息. /** * Show a notificat ...
随机推荐
- maven bug之Maven:Non-resolvable parent POM: Failure to find错误
使用Maven编译淘宝的TimeTunnel项目时遇到如下错误: [INFO] Scanning for projects...[ERROR] The build could not read 1 p ...
- Chains (链 )
Indy 中的工作队列系统,调度器,和链引擎都被叫做链. 当使用链的时候,一个基于链的 IOHandler 存储工作项目到有关的工作队列中.在一个工作单元被完成以前,执行这个工作单元的纤程是无法做其它 ...
- 用Visual Studio 2010 打开Visual Studio 2013 (C#专用)
1.更改.sln 1)将Microsoft Visual Studio Solution File, Format Version 12.00 改成11.00 2)将 # Visual Studi ...
- man中文手册
Ubuntu安装man手册 sudo apt-get install manpages-zh CentOS安装man手册 yum install man man中文安装包 yum install ma ...
- ubuntu语言设置成汉语
打开设置system setting,进入语言支持,有语言和地区格式.下载须要的语言并应用到整个系统. 按说明来就可以 这样的方法使得部分英语变为汉语.
- linux 下使用genymotion
在官网下载genymotion http://www.genymotion.cn/ 然后进行下面操作 1.假设本机没有virtualbox 下载一个 能够通过指令 sudo apt-get inst ...
- HFile存储格式
Table of Contents HFile存储格式 Block块结构 HFile存储格式 HFile是參照谷歌的SSTable存储格式进行设计的.全部的数据记录都是通过它来完毕持久化,其内部主要採 ...
- js坑爹笔试题目汇总(持续更新中)
把你的面试官问倒,你就是一个合格的面试者了,以下总结一些易错的js笔试题目,会持续更新中.欢迎关注 1,考察this var length = 10 function fn(){ alert(this ...
- Android Jni层 创建 linux socket 出错问题解决
问题: 想在Jni层创建 udp socket 与服务端通信,可是没有成功.最后发现居然是创建socket失败(代码例如以下) // create socket g_sd = socket(AF_IN ...
- linux select poll and epoll
这里以socket文件来阐述它们之间的区别,假设现在服务器端有100 000个连接,即已经创建了100 000个socket. 1 select和poll 在我们的线程中,我们会弄一个死循环,在循环里 ...