blob: c8719514e67605e9c2e0d265fd3792b96e257e29 [file] [log] [blame]
<html devsite><head>
<title>应用电耗管理</title>
<meta name="project_path" value="/_project.yaml"/>
<meta name="book_path" value="/_book.yaml"/>
</head>
<body>
<!--
Copyright 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<p>
在 Android 9 及更高版本中,平台可以对应用进行监控,以了解应用是否存在对设备的电池续航时间造成负面影响的行为。平台会使用和评估设置规则以提供用户体验流程,让用户可以选择限制违反规则的应用。
</p>
<p>
在 Android 8.0 及更低版本中,系统通过一些功能(例如低电耗模式、应用待机模式、后台限制和后台位置限制)对这类应用进行了限制。不过,一些应用仍存在不良行为(<a href="https://developer.android.com/topic/performance/vitals/" class="external">Android Vitals</a> 中对部分这类行为进行了说明)。Android 9 引入了一个操作系统基础架构,此基础架构可以根据设置规则(随时间不断更新)检测和限制应用。
</p>
<h2 id="app-restrictions">后台限制</h2>
<p>
用户可以选择限制应用,或者系统可能会提示用户其检测到对设备运行状况有不利影响的应用。
</p>
<p>
受限应用:
</p>
<ul>
<li>仍然可以由用户启动。</li>
<li>无法在后台运行作业/闹铃或使用网络。</li>
<li>无法运行前台服务。</li>
<li>可以由用户更改为不受限应用。</li>
</ul>
<p>
设备实现者可以向应用添加额外限制以:</p>
<ul>
<li>限制应用自动重启。</li>
<li>限制绑定服务(风险极高)。</li>
</ul>
<p>
在后台运行的受限应用不应该消耗任何设备资源,例如内存、CPU 和电量。如果用户未主动使用后台受限应用,则这些应用不应该影响设备的运行状况。不过,如果用户启动后台受限应用,则这类应用应全功能运行。
</p>
<h3 id="using-customg-restrictions">使用自定义实现</h3>
<p>
设备实现者可以继续使用他们的自定义方法对应用设置限制。
</p>
<aside class="caution"><strong>注意</strong>:后续版本可能会破坏设备实现者的自定义设置。我们建议采用 AOSP 中的 Android 9 应用限制架构。
</aside>
<h3 id="integrating-app-restrictions">集成应用限制</h3>
<p>
下面的部分概述了如何针对您的设备定义和集成应用限制。如果您使用的是 Android 8.x 或更低版本中的应用限制方法,请仔细阅读下面的部分,了解 Android 9 中的变更。
</p>
<h4 id="set-appopsmanager-flag">设置 AppOpsManager 标记</h4>
<p>
限制应用时,应在 <code>AppOpsManager</code> 中设置适当的标记。下面是 <code>packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryUtils.java</code> 中的一个示例代码段:
</p>
<pre class="prettyprint"> public void setForceAppStandby(int uid, String packageName,
int mode) {
final boolean isPreOApp = isPreOApp(packageName);
if (isPreOApp) {
// Control whether app could run in the background if it is pre O app
mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName, mode);
}
// Control whether app could run jobs in the background
mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName, mode);
}
</pre>
<h4 id="ensure-isbackgroundrestricted-returns-true">
确保 <code>isBackgroundRestricted</code> 返回 <code>true</code>
</h4>
<p>
限制应用时,应确保 <code>ActivityManager.isBackgroundRestricted()</code> 返回 <code>true</code>
</p>
<h4>记录限制原因</h4>
<p>
限制应用时,应记录限制原因。下面是 <code>packages/apps/Settings/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppAction.java</code> 中日志记录的一个示例代码段:
</p>
<pre class="prettyprint">mBatteryUtils.setForceAppStandby(mBatteryUtils.getPackageUid(packageName), packageName,AppOpsManager.MODE_IGNORED);
if (CollectionUtils.isEmpty(appInfo.anomalyTypes)) {
// Only log context if there is no anomaly type
mMetricsFeatureProvider.action(mContext,
MetricsProto.MetricsEvent.ACTION_TIP_RESTRICT_APP, packageName,
Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT,metricsKey));
} else {
// Log ALL the anomaly types
for (int type : appInfo.anomalyTypes) {
mMetricsFeatureProvider.action(mContext,
MetricsProto.MetricsEvent.ACTION_TIP_RESTRICT_APP, packageName,
Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT, metricsKey),
Pair.create(MetricsProto.MetricsEvent.FIELD_ANOMALY_TYPE, type));
}
</pre>
<p>
应将 <code>type</code> 替换成 <code>AnomalyType</code> 中的值。
</p>
<p>
设备实现者可以使用 <code>src/com/android/settings/fuelgauge/batterytip/StatsManagerConfig.java</code> 中定义的常量:
</p>
<pre class="prettyprint">public @interface AnomalyType {
// This represents an error condition in the anomaly detection.
int NULL = -1;
// The anomaly type does not match any other defined type.
int UNKNOWN_REASON = 0;
// The application held a partial (screen off) wake lock for a period of time that
// exceeded the threshold with the screen off when not charging.
int EXCESSIVE_WAKELOCK_ALL_SCREEN_OFF = 1;
// The application exceeded the maximum number of wakeups while in the background
// when not charging.
int EXCESSIVE_WAKEUPS_IN_BACKGROUND = 2;
// The application did unoptimized Bluetooth scans too frequently when not charging.
int EXCESSIVE_UNOPTIMIZED_BLE_SCAN = 3;
// The application ran in the background for a period of time that exceeded the
// threshold.
int EXCESSIVE_BACKGROUND_SERVICE = 4;
// The application exceeded the maximum number of wifi scans when not charging.
int EXCESSIVE_WIFI_SCAN = 5;
// The application exceed the maximum number of flash writes
int EXCESSIVE_FLASH_WRITES = 6;
// The application used more than the maximum memory, while not spending any time
// in the foreground.
int EXCESSIVE_MEMORY_IN_BACKGROUND = 7;
// The application exceeded the maximum percentage of frames with a render rate of
// greater than 700ms.
int EXCESSIVE_DAVEY_RATE = 8;
// The application exceeded the maximum percentage of frames with a render rate
// greater than 16ms.
int EXCESSIVE_JANKY_FRAMES = 9;
// The application exceeded the maximum cold start time - the app has not been
// launched since last system start, died or was killed.
int SLOW_COLD_START_TIME = 10;
// The application exceeded the maximum hot start time - the app and activity are
// already in memory.
int SLOW_HOT_START_TIME = 11;
// The application exceeded the maximum warm start time - the app was already in
// memory but the activity wasn't created yet or was removed from memory.
int SLOW_WARM_START_TIME = 12;
// The application exceeded the maximum number of syncs while in the background.
int EXCESSIVE_BACKGROUND_SYNCS = 13;
// The application exceeded the maximum number of gps scans while in the background.
int EXCESSIVE_GPS_SCANS_IN_BACKGROUND = 14;
// The application scheduled more than the maximum number of jobs while not charging.
int EXCESSIVE_JOB_SCHEDULING = 15;
// The application exceeded the maximum amount of mobile network traffic while in
// the background.
int EXCESSIVE_MOBILE_NETWORK_IN_BACKGROUND = 16;
// The application held the WiFi lock for more than the maximum amount of time while
// not charging.
int EXCESSIVE_WIFI_LOCK_TIME = 17;
// The application scheduled a job that ran longer than the maximum amount of time.
int JOB_TIMED_OUT = 18;
// The application did an unoptimized Bluetooth scan that exceeded the maximum
// time while in the background.
int LONG_UNOPTIMIZED_BLE_SCAN = 19;
// The application exceeded the maximum ANR rate while in the background.
int BACKGROUND_ANR = 20;
// The application exceeded the maximum crash rate while in the background.
int BACKGROUND_CRASH_RATE = 21;
// The application exceeded the maximum ANR-looping rate.
int EXCESSIVE_ANR_LOOPING = 22;
// The application exceeded the maximum ANR rate.
int EXCESSIVE_ANRS = 23;
// The application exceeded the maximum crash rate.
int EXCESSIVE_CRASH_RATE = 24;
// The application exceeded the maximum crash-looping rate.
int EXCESSIVE_CRASH_LOOPING = 25;
// The application crashed because no more file descriptors were available.
int NUMBER_OF_OPEN_FILES = 26;
}
</pre>
<p>
如果用户或系统移除了应用的限制,则您必须记录移除限制的原因。下面是 <code>packages/apps/Settings/src/com/android/settings/fuelgauge/batterytip/actions/UnrestrictAppAction.java</code> 中日志记录的一个示例代码段:
</p>
<pre class="prettyprint"> public void handlePositiveAction(int metricsKey) {
final AppInfo appInfo = mUnRestrictAppTip.getUnrestrictAppInfo();
// Clear force app standby, then app can run in the background
mBatteryUtils.setForceAppStandby(appInfo.uid, appInfo.packageName,
AppOpsManager.MODE_ALLOWED);
mMetricsFeatureProvider.action(mContext,
MetricsProto.MetricsEvent.ACTION_TIP_UNRESTRICT_APP, appInfo.packageName,
Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT, metricsKey));
}
</pre>
<h3 id="testing-app-restrictions">测试应用限制</h3>
<p>
要在 Android 9.x 或更高版本中测试应用限制的行为,请使用下列命令之一:</p>
<ul>
<li>对应用设置应用限制:
<pre class="devsite-terminal devsite-click-to-copy">appops set <var>package-name</var> RUN_ANY_IN_BACKGROUND ignore</pre>
</li>
<li>移除应用限制并恢复默认行为:
<pre class="devsite-terminal devsite-click-to-copy">appops set <var>package-name</var> RUN_ANY_IN_BACKGROUND allow</pre>
</li>
<li>让后台应用立即进入闲置状态:
<pre class="devsite-terminal devsite-click-to-copy">am make-uid-idle [--user <var>user-id</var> | all | current] <var>package-name</var></pre>
</li>
<li>在短期内将软件包添加到 <code>tempwhitelist</code> 中:
<pre class="devsite-terminal devsite-click-to-copy">cmd deviceidle tempwhitelist [-u <var>user</var>] [-d <var>duration</var>] [package <var>package-name</var>]</pre>
</li>
<li>向用户白名单中添加软件包或从中移除软件包:
<pre class="devsite-terminal devsite-click-to-copy">cmd deviceidle whitelist [+/-]<var>package-name</var></pre>
</li>
<li>查看 jobscheduler 和闹铃管理器的内部状态:
<pre class="devsite-click-to-copy"><code class="devsite-terminal">dumpsys jobscheduler</code>
<code class="devsite-terminal">dumpsys alarm</code></pre>
</li>
</ul>
<h2 id="app-standby">应用待机模式</h2>
<p>
对于用户未主动使用的应用,应用待机模式会延迟其后台网络活动和作业,从而延长电池续航时间。
</p>
<h3 id="app-standby-life">应用待机模式生命周期</h3>
<p>
平台检测到未活动的应用,并使其进入应用待机模式,直到用户开始主动与相应应用互动为止。
</p>
<table>
<tbody>
<tr>
<th width="46%">检测</th>
<th width="23%">应用待机模式期间</th>
<th width="31%">退出</th>
</tr>
<tr>
<td>
<p>
当设备未充电<strong></strong>用户在特定的时钟时间内以及特定的屏幕开启时间内未直接或间接地启动某个应用时,平台会检测到该应用处于未活动状态(当前台应用访问另一个应用中的服务时,便会间接启动该应用)。
</p>
</td>
<td>
<p>
平台会阻止应用访问网络(一天多次),从而延迟应用同步和其他作业。
</p>
</td>
<td>
<p>当出现以下情况时,平台会使应用退出应用待机模式:</p>
<ul>
<li>应用变成活动状态。</li>
<li>设备接通电源并充电。</li>
</ul>
</td>
</tr>
</tbody>
</table>
<p>处于活动状态的应用不受应用待机模式影响。如果应用具有以下特点,则表示其处于活动状态:</p>
<ul>
<li>有目前在前台运行的进程(作为活动或前台服务,或者正在被其他活动或前台服务使用),例如通知侦听器、无障碍服务、动态壁纸等等。
</li>
<li>有用户要查看的通知,例如锁定屏幕或通知栏中的通知。
</li>
<li>明确由用户启动。</li>
</ul>
<p>如果某个应用在一段时间内未发生上述活动,则表示该应用处于非活动状态。
</p>
<h3 id="testing_app_standby">测试应用待机模式</h3>
<p>您可以使用以下 <code>adb</code> 命令手动测试应用待机模式:</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal">adb shell dumpsys battery unplug</code>
<code class="devsite-terminal">adb shell am set-idle <var>package-name</var> true</code>
<code class="devsite-terminal">adb shell am set-idle <var>package-name</var> false</code>
<code class="devsite-terminal">adb shell am get-idle <var>package-name</var></code>
</pre>
</body></html>