每次在后台运行时,应用都会消耗一部分有限的设备资源,例如 RAM。 这可能会影响用户体验,如果用户正在使用占用大量资源的应用(例如玩游戏或观看视频),影响会尤为明显。 为了提升用户体验,Android 8.0(API 级别 26)对应用在后台运行时可以执行的操作施加了限制。
应用在两个方面受到限制:
- 后台 Service 限制:处于空闲状态时,应用可以使用的后台 Service 存在限制。 这些限制不适用于前台 Service,因为前台 Service 更容易引起用户注意。
- 广播限制:除了有限的例外情况,应用无法使用清单注册隐式广播。 它们仍然可以在运行时注册这些广播,并且可以使用清单注册专门针对它们的显式广播。
在 Android 8.0 之前,创建前台 Service 的方式通常是先创建一个后台 Service,然后将该 Service 推到前台。 Android 8.0 有一项复杂功能:系统不允许后台应用创建后台 Service。 因此,Android 8.0 引入了一种全新的方法,即 startForegroundService(),以在前台启动新 Service。 在系统创建 Service 后,应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示新 Service 的用户可见通知。 如果应用在此时间限制内_未_调用 startForeground(),则系统将停止此 Service 并声明此应用为 ANR。
如果满足以下任意条件,应用将被视为处于前台:
- 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
- 具有前台 Service。
- 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的 Service,那么该应用处于前台:
- IME
- 壁纸 Service
- 通知侦听器
- 语音或文本 Service
如果 UID 现在在后台(不在临时白名单上), 它之前是在前台(或在临时白名单上);那么1min后当前UID将会处于idle状态,见Uid是否处于白名单作用
查找最近不活动的应用程序,并在宽限期后将它们标记为空闲。 如果空闲,停止任何后台服务并通知听众。
@GuardedBy("mService")
void idleUidsLocked() {
......
final long nowElapsed = SystemClock.elapsedRealtime();
final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
......
// 1min后执行消息
for (int i = N - 1; i >= 0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
final long bgTime = uidRec.getLastBackgroundTime();
// 距离设置后台时间超过1min且当前uid不是idle则设置idle
if (bgTime > 0 && !uidRec.isIdle()) {
if (bgTime <= maxBgTime) {
EventLogTags.writeAmUidIdle(uidRec.getUid());
synchronized (mProcLock) {
uidRec.setIdle(true);
uidRec.setSetIdle(true);
}
// 停止当前uid下的后台Services
mService.doStopUidLocked(uidRec.getUid(), uidRec);
} .......
}
}
处于前台时,应用可以自由创建和运行前台与后台 Service。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用 Service。 在该时间窗结束后,应用将被视为处于_空闲_状态。 此时,系统将停止应用的后台 Service,就像应用已经调用 Service 的 Service.stopSelf() 方法一样。
因为当前uid进入后台1min后处于idle状态,停止与此 uid 关联的所有服务;所以我们会经常在bugreport中看到类似如下log。
08-27 20:08:12.038 1588 5729 I am_uid_active: 10135
08-27 20:09:13.517 1588 2114 I am_uid_idle: 10135
08-27 20:09:13.517 1588 2114 I am_stop_idle_service: [10135,com.android.htmlviewer/com.android.settings.services.MemoryOptimizationService]
03-22 01:49:59.872 3463 3521 W ActivityManager: Stopping service due to app idle: u0a153 -1m7s689ms com.qiyi.video/com.iqiyi.im.service.PPMessageService
void stopInBackgroundLocked(int uid) {
ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
ArrayList<ServiceRecord> stopping = null;
if (services != null) {
for (int i = services.mServicesByInstanceName.size() - 1; i >= 0; i--) {
ServiceRecord service = services.mServicesByInstanceName.valueAt(i);
if (service.appInfo.uid == uid && service.startRequested) {
if (mAm.getAppStartModeLOSP(service.appInfo.uid, service.packageName,
service.appInfo.targetSdkVersion, -1, false, false, false)
service.mRecentCallingPackage)
!= ActivityManager.APP_START_MODE_NORMAL) {
if (stopping == null) {
stopping = new ArrayList<>();
}
String compName = service.shortInstanceName;
EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
StringBuilder sb = new StringBuilder(64);
sb.append("Stopping service due to app idle: ");
UserHandle.formatUid(sb, service.appInfo.uid);
sb.append(" ");
TimeUtils.formatDuration(service.createRealTime
- SystemClock.elapsedRealtime(), sb);
sb.append(" ");
sb.append(compName);
Slog.w(TAG, sb.toString());
stopping.add(service);
// 如果应用程序受到后台限制,还要确保取消任何通知
if (appRestrictedAnyInBackground(
service.appInfo.uid, service.packageName)) {
cancelForegroundNotificationLocked(service);
}
}
}
}
if (stopping != null) {
final int size = stopping.size();
for (int i = size - 1; i >= 0; i--) {
ServiceRecord service = stopping.get(i);
service.delayed = false;
services.ensureNotStartingBackgroundLocked(service);
stopServiceLocked(service, true);
}
if (size > 0) {
mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
}
}
}
}
注意:stopInBackgroundLocked回调后,service不一定会被stop
private final void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn,
boolean hasConn) {
if (isServiceNeededLocked(r, knowConn, hasConn)) {
return;
}
// 如果有新拉起service的需求,本次不会stop该service
if (mPendingServices.contains(r)) {
return;
}
bringDownServiceLocked(r);
}
如果Service是间接启动的(例如从 PendingIntent),弄清楚是否正在后台状态下启动一个应用程序。
当Service所在App处于后台(uid为idle)时,会限制启动Service,限制分两种情况:
ActivityManager: Background start not allowed: service Intent { act=geofence_trigered cmp=com.xiaomi.smarthome/.scene.activity.GeoActionService (has extras) } to com.xiaomi.smarthome/.scene.activity.GeoActionService from pid=9233 uid=10270 pkg=com.xiaomi.smarthome startFg?=true
无限制下android.app.AppOpsManager#OP_RUN_ANY_IN_BACKGROUND 为ALLOWED
09-13 10:30:40.633 1581 2603 W ActivityManager: Background start not allowed: service Intent { cmp=com.android.deskclock/.addition.resource.ResourceLoadService (has extras) } to com.android.deskclock/.addition.resource.ResourceLoadService from pid=14625 uid=10216 pkg=com.android.deskclock startFg?=false
// 当前uid不处于空闲状态
final boolean bgLaunch = !mAm.isUidActiveLOSP(r.appInfo.uid);
boolean forcedStandby = false;
// 如果应用程序有严格的后台限制,我们将任何 bg 服务启动类似于旧版应用程序强制限制情况,
// 无论其目标 SDK 版本如何。
if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
+ " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
}
forcedStandby = true;
}
.......
if (forcedStandby || (!r.startRequested && !fgRequired)) {
// 在继续之前——如果这个应用程序不允许在后台启动服务,那么在这一点上我们不会让它运行。
final int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,
r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service "
+ service + " to " + r.shortInstanceName
+ " from pid=" + callingPid + " uid=" + callingUid
+ " pkg=" + callingPackage + " startFg?=" + fgRequired);
避免当前App进入空闲状态,可从以下两个方向:
mDeviceIdleTempAllowlist或mPendingTempAllowlist名单中的uid的会设置当前UidRecord的mCurAllowList为true。
@GuardedBy(anyOf = {"this", "mProcLock"})
// alwaysRestrict 为false,disabledOnly 为false
int getAppStartModeLOSP(int uid, String packageName, int packageTargetSdk,
int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby)
// 当前app在前台
if (mInternal.isPendingTopUid(uid)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
// 避免当前Uid进入空闲状态
if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.isIdle()) {
......
} else {
......
// alwaysRestrict为false,走的appServicesRestrictedInBackgroundLOSP
final int startMode = (alwaysRestrict)
? appRestrictedInBackgroundLOSP(uid, packageName, packageTargetSdk)
: appServicesRestrictedInBackgroundLOSP(uid, packageName,
packageTargetSdk);
// android o 以下,暂不考虑了
if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
// 这是一个旧应用程序,已被迫进入“尽可能兼容”的背景检查模式。
// 为了增加兼容性,我们将允许其他前台应用启动其服务。
if (callingPid >= 0) {
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(callingPid);
}
if (proc != null && !ActivityManager.isProcStateBackground(
proc.mState.getCurProcState())) {
// 启动的caller在前台,所以我们将允许它通过。
return ActivityManager.APP_START_MODE_NORMAL;
}
}
}
return startMode;
}
}
return ActivityManager.APP_START_MODE_NORMAL;
}
服务启动适用于具有后台运行豁免的应用程序,但某些其他后台操作则不可用。 如果我们正在检查服务启动策略,请允许那些调用者不受限制地继续。
@GuardedBy(anyOf = {"this", "mProcLock"})
int appServicesRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {
// Persistent app?
if (mPackageManagerInt.isPackagePersistent(packageName)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
// Non-persistent but background whitelisted? bluetooth uid
if (uidOnBackgroundAllowlistLOSP(uid)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
// UID 是否在系统、用户或临时休眠许可名单中
if (isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ false)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
// None of the service-policy criteria apply, so we apply the common criteria
return appRestrictedInBackgroundLOSP(uid, packageName, packageTargetSdk);
}
在这些情况下,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动 Service,并且其后台 Service 也可以运行。 处理对用户可见的任务时,应用将被置于白名单中,例如:
- 处理一条高优先级 Firebase 云消息传递 (FCM)消息。 见 应用是推送App
- 接收广播,处理短信/彩信消息。见应用在广播接收器中接收特殊广播 短信/彩信
- 从通知执行 PendingIntent。 见应用收到系统的 PendingIntent 通知
- 在 VPN 应用将自己提升为前台进程前开启 VpnService。
豁免的情况下在idle白名单(mDeviceIdleAllowlist)或临时白名单(mDeviceIdleTempAllowlist或mPendingTempAllowlist)中的app可不受 此限制。临时白名单添加情况见临时白名单
// UID 是否在系统、用户或临时休眠许可名单中
@GuardedBy(anyOf = {"this", "mProcLock"})
boolean isOnDeviceIdleAllowlistLOSP(int uid, boolean allowExceptIdleToo) {
final int appId = UserHandle.getAppId(uid);
// allowExceptIdleToo为false
final int[] allowlist = allowExceptIdleToo
? mDeviceIdleExceptIdleAllowlist
: mDeviceIdleAllowlist;
return Arrays.binarySearch(allowlist, appId) >= 0
// 由于高优先级消息而暂时允许逃避后台检查的一组应用程序 ID,短信/彩信
|| Arrays.binarySearch(mDeviceIdleTempAllowlist, appId) >= 0
// 暂时绕过省电模式的临时白名单,通知等
|| mPendingTempAllowlist.get(uid) != null;
}
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
我是ruby的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visitthehelpcenter.关闭9年前。我需要从基于ruby的应用程序使用AmazonSimpleNotificationService,但不知道从哪里开始。您对从哪里开始有什么建议吗?
因此,在使用Sphinx时,搜索限制为1000个结果。但是,如果will_paginate生成的结果分页链接超过1000个,请不要考虑这一点,并提供指向超过1000/per_page的页面的链接。设置最大页数或类似内容的明显方法是什么?干杯。 最佳答案 我认为最好将参数:total_entries提交给方法paginate:@posts=Post.paginate(:page=>params[:page],:per_page=>30,:total_entries=>1000)will_paginate将仅为显示1000个结果所需的页
在ruby1.9中,放宽了行结束位置的条件,因此我们现在可以用句号开始一行来显示方法调用。当我们混淆了链式和非链式方法,并希望显示下一个非链式方法的开始位置时,这很方便。如果没有这个新功能,我们能做的最好的可能就是使用缩进:method1(args1).method2(args2).method3(args3)method4(args4).method5(args5).method6(args6)或插入一个空行。但这很不方便,因为我们必须注意缩进,同时,不要忘记在每个方法调用之后加上链中最后一个方法调用之后的句点。正因为如此,我制造了很多错误,要么有一个额外的周期,要么有一个缺失的
我想知道使用fork{}从Rails应用程序“后台”处理是否是个好主意...从我收集到的fork{my_method;Process#setsid}实际上做了它应该做的事情。1)创建另一个具有不同PID的进程2)不中断调用过程(例如它继续w/o等待fork完成)3)执行子进程直到它完成..这很酷,但这是个好主意吗?fork到底在做什么?它会在内存中创建我的整个railsmongrel/passenger实例的重复实例吗?如果是这样那就太糟糕了。或者,它是否以某种方式在不消耗大量内存的情况下完成。我的最终目标是取消我的后台守护进程/队列系统,转而支持这些进程的fork(主要是发送电子邮件
我正在按照我一直在研究的研讨会实现“服务对象”,我正在构建一个redditAPI应用程序。我需要对象返回一些东西,所以我不能只执行初始化程序中的所有内容。我有这两个选择:选项1:类需要实例化classSubListFromUserdefuser_subscribed_subs(client)@client=client@subreddits=sort_subs_by_name(user_subs_from_reddit)endprivatedefsort_subs_by_name(subreddits)subreddits.sort_by{|sr|sr[:name].downcase}
我知道我们可以做到:sidekiq_optionsqueue:"Foo"但在这种情况下,Worker只分配给一个队列:“Foo”。我需要在特定队列中分配作业(而不是worker)。使用Resque很容易:Resque.enqueue_to(queue_name,my_job)另外,为了并发问题,我需要限制每个队列的Worker数量为1。我该怎么做? 最佳答案 您可能会使用https://github.com/brainopia/sidekiq-limit_fetch然后:Sidekiq::Client.push({'class'=>
业务逻辑:用户每天只能为日记创建一个条目。在创建条目之前,它必须查询记录以确定是否已经为今天创建了条目。我正在寻找解决此问题的最佳方法的建议。我对如何在客户端实现它有一些想法,但我真的很想在模型层进行验证。任何帮助将不胜感激。 最佳答案 在日志表上创建唯一索引:add_index:journal_entries,[:user_id,:created_on],unique:true然后只能创建一条具有给定user_id和日期的记录,如果违反,数据库将引发异常。请注意,created_on必须是date列,而不是datetime。这是唯
我的迁移看起来像这样classCreateQuestionings现在,当我运行$rakedb:migrate:reset时,在我的db/schema.rb中看不到限制:create_table"questionings",force::cascadedo|t|t.text"body",null:falseend我做错了吗还是这是一个错误?顺便说一下,我使用的是rails5.0.0.beta3和ruby2.3.0p0。 最佳答案 t.text在PostgreSQL和textdoesn'tallowforsizelimits中生成