Android 应用冻结机制与防冻结方案分析
目录
1. 背景介绍
从 Android 11 (API 30) 开始,Android 系统引入了应用冻结 (App Freezing) 机制,作为 Cached App Optimizer 的一部分。该机制旨在通过冻结后台缓存应用来减少系统资源消耗,延长电池续航时间,同时保持应用在内存中以便快速恢复。
问题场景
在实际应用中,某些后台服务(如计时器、倒计时小工具)在运行一段时间后会突然停止工作,查看日志发现应用被系统主动冻结:
1
| ActivityManager: freezing 29669 com.example.stopwatch
|
这种冻结会导致:
- 后台计时停止
- 定时任务无法执行
- 网络连接被挂起
- 用户体验受影响
2. 应用冻结机制详解
2.1 什么是应用冻结
应用冻结是一种进程状态管理技术,通过 Linux cgroup freezer 机制暂停进程的执行:
- 冻结 (Frozen): 进程被暂停执行,不消耗 CPU 资源,但保留在内存中
- 解冻 (Unfrozen): 进程恢复执行,从暂停点继续运行
- 与杀死的区别: 冻结的进程仍在内存中,解冻后可立即恢复;被杀死的进程需要重新启动
2.2 冻结触发条件
系统在以下条件下会冻结应用(源码位置:CachedAppOptimizer.java):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| private void freezeProcess(final ProcessRecord proc) { synchronized (mProcLock) { pid = proc.getPid(); if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ || opt.shouldNotFreeze()) { if (DEBUG_FREEZER) { Slog.d(TAG_AM, "Skipping freeze for process " + pid + " " + name + " curAdj = " + proc.mState.getCurAdj() + ", shouldNotFreeze = " + opt.shouldNotFreeze()); } return; } if (mFreezerOverride) { opt.setFreezerOverride(true); return; } if (pid == 0 || opt.isFrozen()) { return; } Slog.d(TAG_AM, "freezing " + pid + " " + name); } }
|
核心条件总结:
- ADJ >= 900 (CACHED_APP_MIN_ADJ)
- shouldNotFreeze() == false
- 默认延迟时间: 600 秒(10 分钟)
1 2
| static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L;
|
2.3 OOM Adjustment 机制
OOM Adjustment (简称 ADJ) 是 Android 进程优先级管理的核心机制,值越小优先级越高:
| ADJ级别 |
名称 |
取值 |
说明 |
是否会被冻结 |
| NATIVE_ADJ |
native |
-1000 |
Native 进程 |
❌ |
| SYSTEM_ADJ |
sys |
-900 |
System Server |
❌ |
| PERSISTENT_PROC_ADJ |
pers |
-800 |
Persistent 进程 |
❌ |
| PERSISTENT_SERVICE_ADJ |
psvc |
-700 |
Persistent 服务 |
❌ |
| FOREGROUND_APP_ADJ |
fg |
0 |
前台应用 |
❌ |
| PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ |
|
50 |
最近在前台的应用 |
❌ |
| VISIBLE_APP_ADJ |
vis |
100 |
可见应用 |
❌ |
| PERCEPTIBLE_APP_ADJ |
prcp |
200 |
可感知应用(如音乐播放) |
❌ |
| PERCEPTIBLE_LOW_APP_ADJ |
prcl |
250 |
低优先级可感知应用 |
❌ |
| BACKUP_APP_ADJ |
bkup |
300 |
备份进程 |
❌ |
| HEAVY_WEIGHT_APP_ADJ |
hvy |
400 |
重量级后台进程 |
❌ |
| SERVICE_ADJ |
svc |
500 |
服务进程 |
❌ |
| HOME_APP_ADJ |
home |
600 |
桌面进程 |
❌ |
| PREVIOUS_APP_ADJ |
prev |
700 |
上一个应用 |
❌ |
| SERVICE_B_ADJ |
svcb |
800 |
B List 服务 |
❌ |
| CACHED_APP_MIN_ADJ |
cch |
900 |
缓存应用最小值 |
✅ 冻结阈值 |
| CACHED_APP_LMK_FIRST_ADJ |
|
950 |
LMK 首选杀死 |
✅ |
| CACHED_APP_MAX_ADJ |
|
999 |
缓存应用最大值 |
✅ |
| UNKNOWN_ADJ |
|
1001 |
未知状态 |
✅ |
关键点:
- ADJ < 900: 不会被冻结
- ADJ >= 900: 会在一定时间后被冻结
- ADJ 值由
OomAdjuster 动态计算
2.4 冻结流程源码分析
步骤1: OomAdjuster 计算 ADJ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @GuardedBy({"mService", "mProcLock"}) private void maybeUpdateIsolatedFreeze(ProcessRecord app, @OomAdjReason int oomAdjReason, boolean immediate) { if (!mCachedAppOptimizer.useFreezer()) { return; }
if (app.mOptRecord.isFreezeExempt()) { return; }
final ProcessCachedOptimizerRecord opt = app.mOptRecord; if (opt.isFrozen() && opt.shouldNotFreeze()) { mCachedAppOptimizer.unfreezeAppLSP(app, CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason)); return; }
final ProcessStateRecord state = app.mState; if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen() && !opt.shouldNotFreeze()) { if (!immediate) { mCachedAppOptimizer.freezeAppAsyncLSP(app); } else { mCachedAppOptimizer.freezeAppAsyncAtEarliestLSP(app); } } else if (state.getSetAdj() < FREEZER_CUTOFF_ADJ) { mCachedAppOptimizer.unfreezeAppLSP(app, CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason)); } }
|
步骤2: CachedAppOptimizer 执行冻结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private void freezeProcess(final ProcessRecord proc) { try { freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS); Process.setProcessFrozen(pid, proc.uid, true); opt.setFrozen(true); opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis()); mFrozenProcesses.put(pid, proc); } catch (Exception e) { Slog.e(TAG_AM, "Unable to freeze " + pid + " " + proc.processName); } }
|
3. 防冻结方案详解
3.1 ContentProvider 方案
原理
通过声明 ContentProvider 并调用系统 API getContentProviderExternal(),使系统认为该 Provider 具有外部进程依赖,从而将 ADJ 提升到 0。
关键机制
步骤1: 外部进程句柄机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class ContentProviderRecord { ArrayMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle; int externalProcessNoHandleCount; public void addExternalProcessHandleLocked(IBinder token, int callingUid, String callingTag) { if (token == null) { externalProcessNoHandleCount++; } else { if (externalProcessTokenToHandle == null) { externalProcessTokenToHandle = new ArrayMap<>(); } ExternalProcessHandle handle = externalProcessTokenToHandle.get(token); if (handle == null) { handle = new ExternalProcessHandle(token, callingUid, callingTag); externalProcessTokenToHandle.put(token, handle); handle.startAssociationIfNeeded(this); } handle.mAcquisitionCount++; } } public boolean hasExternalProcessHandles() { return (externalProcessTokenToHandle != null || externalProcessNoHandleCount > 0); } }
|
步骤2: getContentProviderExternal 添加句柄
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private ContentProviderConnection incProviderCountLocked(ProcessRecord r, final ContentProviderRecord cpr, IBinder externalProcessToken, int callingUid, String callingPackage, String callingTag, boolean stable, boolean updateLru, long startTime, ProcessList processList, @UserIdInt int expectedUserId) { if (r == null) { cpr.addExternalProcessHandleLocked(externalProcessToken, callingUid, callingTag); return null; } }
|
步骤3: OomAdjuster 提升 ADJ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private int computeOomAdjLSP(ProcessRecord app, int cachedAdj, ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval, boolean computeClients, @OomAdjReason int oomAdjReason, boolean couldRecurse) { for (int iproc = ppr.numberOfPublishedProviders() - 1; iproc >= 0; iproc--) { ContentProviderRecord cpr = ppr.getPublishedProviderAt(iproc); if (cpr.hasExternalProcessHandles()) { if (adj > FOREGROUND_APP_ADJ) { adj = FOREGROUND_APP_ADJ; state.setCurRawAdj(adj); schedGroup = SCHED_GROUP_DEFAULT; state.setAdjType("ext-provider"); state.setAdjTarget(cpr.name); } if (procState > PROCESS_STATE_IMPORTANT_FOREGROUND) { procState = PROCESS_STATE_IMPORTANT_FOREGROUND; state.setCurRawProcState(procState); } } } }
|
完整实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
|
public class AdjAdjuster extends ContentProvider { private static final String TAG = "AdjAdjuster"; private static final String AUTHORITY = "com.example.countdown.provider.AdjAdjuster"; private static final Set<Integer> sKeySet = new HashSet<>(); private static final IBinder sToken = new Binder();
public static void onStart(int key) { synchronized (sKeySet) { sKeySet.add(key); if (sKeySet.size() == 1) { try { ActivityManager.getService().getContentProviderExternal( AUTHORITY, UserHandle.USER_CURRENT, sToken, TAG ); Log.d(TAG, "Anti-freeze protection enabled"); } catch (RemoteException e) { Log.e(TAG, "Failed to enable anti-freeze protection", e); } } } }
public static void onStop(int key) { synchronized (sKeySet) { sKeySet.remove(key); if (sKeySet.isEmpty()) { try { ActivityManager.getService().removeContentProviderExternal( AUTHORITY, sToken ); Log.d(TAG, "Anti-freeze protection disabled"); } catch (RemoteException e) { Log.e(TAG, "Failed to disable anti-freeze protection", e); } } } } @Override public boolean onCreate() { return false; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { return null; } @Nullable @Override public String getType(@NonNull Uri uri) { return null; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { return null; } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { return 0; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { return 0; } }
|
AndroidManifest.xml 配置:
1 2 3 4 5 6 7 8 9
| <provider android:name=".provider.AdjAdjuster" android:authorities="com.example.countdown.provider.AdjAdjuster" android:exported="false" android:enabled="true" />
<uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" />
|
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class CountdownService extends Service { private static final int KEY_COUNTDOWN_SERVICE = 1; @Override public void onCreate() { super.onCreate(); AdjAdjuster.onStart(KEY_COUNTDOWN_SERVICE); } @Override public void onDestroy() { AdjAdjuster.onStop(KEY_COUNTDOWN_SERVICE); super.onDestroy(); } }
|
优势
- 改动极小: 只需添加一个 Provider 类和 AndroidManifest 声明
- 完全自控: 应用内部可随时启停,无需外部进程配合
- ADJ 最低: ADJ = 0,等同前台应用,不会被冻结或杀死
- 零业务侵入: 不影响原有业务逻辑
- 引用计数: 支持多场景同时使用,安全可靠
劣势
- 仅限系统应用: 需要
ACCESS_CONTENT_PROVIDERS_EXTERNALLY 权限(signature|privileged)
- 需要主动管理: 必须手动调用
onStart() 和 onStop(),忘记调用会导致应用长期占用高优先级
3.2 前台服务方案
原理
通过启动前台服务(Foreground Service)提升应用优先级,前台服务的 ADJ 通常为 PERCEPTIBLE_APP_ADJ (200)。
实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class AntiFreezeService extends Service { private static final int NOTIFICATION_ID = 1001; private static final String CHANNEL_ID = "anti_freeze_channel"; @Override public void onCreate() { super.onCreate(); createNotificationChannel(); startForeground(NOTIFICATION_ID, createNotification()); } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "防冻结服务", NotificationManager.IMPORTANCE_LOW ); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } } private Notification createNotification() { return new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("服务运行中") .setContentText("计时器正在运行") .setSmallIcon(R.drawable.ic_timer) .build(); } @Override public IBinder onBind(Intent intent) { return null; } }
|
优势
- 标准方案: Android 原生支持,API 稳定
- 适用所有应用: 不需要特殊权限
- 用户感知: 通知栏显示,用户知道服务在运行
劣势
- 必须显示通知: 无法隐藏,占用通知栏空间
- ADJ 较高: ADJ = 200,虽不会被冻结但优先级低于 ContentProvider 方案
- 用户可关闭: 用户可通过通知栏停止服务
- 需要修改业务逻辑: 需要将功能迁移到服务中
3.3 省电白名单方案
原理
将应用加入省电白名单(Power Save Whitelist),系统会设置 shouldNotFreeze = true。
源码机制
1 2 3 4 5 6 7 8 9
| private int computeOomAdjLSP(ProcessRecord app, ...) { if (!couldRecurse || !cycleReEval) { final UidRecord uidRec = app.getUidRecord(); app.mOptRecord.setShouldNotFreeze(uidRec != null && uidRec.isCurAllowListed()); } }
|
实现方式
方式1: 用户手动添加
1
| 设置 -> 电池 -> 电池优化 -> 选择应用 -> 不优化
|
方式2: 代码请求(需要用户授权)
1 2 3 4 5 6 7 8 9
| PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); String packageName = getPackageName();
if (!pm.isIgnoringBatteryOptimizations(packageName)) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + packageName)); startActivity(intent); }
|
方式3: 系统预配置(厂商)
1 2 3 4
| <array name="config_allowlistedAppsForPowerWhitelist"> <item>com.example.app</item> </array>
|
优势
- 无需改代码: 通过配置实现
- 厂商可控: OEM 可预置白名单
- 适用所有应用: 不限系统应用
劣势
- 需要系统配置或用户授权: 普通应用难以自动实现
- 不同 ROM 差异大: 各厂商实现可能不同
- 用户可撤销: 用户可随时移出白名单
3.4 Persistent 应用方案
原理
在 AndroidManifest.xml 中声明 android:persistent="true",使应用成为系统常驻进程,ADJ = -800。
实现方式
1 2 3 4 5 6
| <application android:name=".MyApplication" android:persistent="true" android:label="@string/app_name"> </application>
|
源码机制
1 2 3 4 5 6 7 8 9 10 11
| private int computeOomAdjLSP(ProcessRecord app, ...) { if (state.getMaxAdj() <= FOREGROUND_APP_ADJ) { state.setAdjType("fixed"); state.setCurRawAdj(state.getMaxAdj()); state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT); return app.mState.getCurRawAdj(); } }
|
优势
- 最高优先级: ADJ = -800,系统级保护
- 改动极小: 只需添加一个属性
- 自动重启: Crash 后系统会立即重启
劣势
- 仅限系统签名应用: 需要
android:sharedUserId="android.uid.system" 或平台签名
- 资源占用高: 常驻内存,占用系统资源
- Crash 频繁重启: 可能导致系统不稳定
3.5 Service 绑定方案
原理
通过绑定到其他不会被冻结的进程的服务,利用 shouldNotFreeze 的传播机制。
源码机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private boolean computeServiceHostOomAdjLSP(ServiceRecord s, ProcessRecord app, ...) { if (client.mOptRecord.shouldNotFreeze()) { if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) { return true; } } }
|
实现示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class KeeperService extends Service { private final IBinder binder = new Binder(); @Override public IBinder onBind(Intent intent) { return binder; } }
public class MyActivity extends Activity { private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, KeeperService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { unbindService(connection); super.onStop(); } }
|
优势
- 灵活性高: 可以根据需要动态绑定/解绑
- 适用所有应用: 不需要特殊权限
劣势
- 需要另一个进程配合: 必须有一个不被冻结的进程提供服务
- 依赖其他进程状态: 如果服务进程被杀死,保护失效
- 管理复杂: 需要处理绑定生命周期
4. 方案对比与评估
综合对比表
| 维度 |
ContentProvider |
前台服务 |
省电白名单 |
Persistent |
Service 绑定 |
| ADJ 值 |
0 |
200 |
不改变(设置 shouldNotFreeze) |
-800 |
继承客户端 |
| 防冻结效果 |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐ |
| 防 LMK 效果 |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐ |
⭐⭐⭐ |
⭐⭐⭐⭐⭐ |
⭐⭐⭐ |
| 权限要求 |
系统权限 |
无 |
用户授权/系统配置 |
系统签名 |
无 |
| 适用范围 |
系统应用 |
所有应用 |
所有应用 |
系统签名应用 |
所有应用 |
| 改动成本 |
⭐ |
⭐⭐⭐ |
⭐ |
⭐ |
⭐⭐⭐⭐ |
| 维护成本 |
⭐ |
⭐⭐ |
⭐ |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐ |
| 用户感知 |
无 |
有(通知) |
无 |
无 |
无 |
| 可控性 |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐ |
⭐⭐ |
⭐⭐⭐⭐⭐ |
⭐⭐⭐ |
| 资源占用 |
低 |
低 |
无额外占用 |
高(常驻) |
低 |
| ROM 兼容性 |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐⭐ |
⭐⭐⭐ |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐ |
详细评估
4.1 ContentProvider 方案
适用场景:
- ✅ 系统应用(具备 ACCESS_CONTENT_PROVIDERS_EXTERNALLY 权限)
- ✅ 需要最高优先级(ADJ = 0)
- ✅ 希望完全自主控制启停
- ✅ 无需用户感知
不适用场景:
技术特点:
- 优先级最高,等同前台应用
- 完全应用内部控制,无需外部配合
- 代码简单,易于维护
- 支持引用计数,可多场景使用
推荐指数: ⭐⭐⭐⭐⭐(系统应用首选)
4.2 前台服务方案
适用场景:
- ✅ 所有类型应用
- ✅ 需要用户知晓服务状态
- ✅ 可以接受通知栏显示
不适用场景:
技术特点:
- Android 标准方案,API 稳定
- 用户可见,体验较好
- ADJ = 200,优先级较高但不如 ContentProvider
推荐指数: ⭐⭐⭐⭐(第三方应用首选)
4.3 省电白名单方案
适用场景:
- ✅ OEM 厂商预置应用
- ✅ 客供应用(厂商配置白名单)
- ✅ 需要用户主动授权的场景
不适用场景:
- ❌ 需要自动实现的场景
- ❌ 对不同 ROM 兼容性要求高
技术特点:
- 无需修改应用代码
- 厂商可控,适合客供场景
- 依赖系统配置或用户授权
推荐指数: ⭐⭐⭐(辅助方案)
4.4 Persistent 方案
适用场景:
- ✅ 核心系统服务
- ✅ 需要系统级保护
- ✅ Crash 后必须立即重启
不适用场景:
- ❌ 非系统签名应用
- ❌ 对资源占用敏感
- ❌ 应用不够稳定(频繁 Crash)
技术特点:
- 最高优先级(ADJ = -800)
- 系统级保护,不会被杀死
- 资源占用高,影响系统稳定性
推荐指数: ⭐⭐(仅限核心系统服务)
4.5 Service 绑定方案
适用场景:
- ✅ 多进程应用
- ✅ 已有不被冻结的进程可提供服务
- ✅ 需要灵活控制
不适用场景:
- ❌ 单进程应用
- ❌ 无法保证服务进程不被杀死
- ❌ 希望简单实现
技术特点:
- 利用 shouldNotFreeze 传播机制
- 依赖其他进程状态
- 管理复杂,需要处理生命周期
推荐指数: ⭐⭐(特殊场景)
5. 实践建议
5.1 方案选择决策树
1 2 3 4 5 6 7
| ┌─ 是系统应用? │ ├─ 是 ──→ 使用 ContentProvider 方案(最优) │ └─ 否 ──┬─ 可以显示通知? │ ├─ 是 ──→ 使用前台服务方案 │ └─ 否 ──┬─ 是客供应用? │ ├─ 是 ──→ 联系厂商加入省电白名单 │ └─ 否 ──→ 考虑业务重构或使用前台服务
|
5.2 系统应用推荐实践
步骤1: 添加 ContentProvider
1 2 3 4
| public class AdjAdjuster extends ContentProvider { }
|
步骤2: 配置 AndroidManifest.xml
1 2 3 4
| <provider android:name=".provider.AdjAdjuster" android:authorities="${applicationId}.provider.adjuster" android:exported="false" />
|
步骤3: 在合适的时机调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class CountdownService extends Service { private static final int KEY_SERVICE = 1; @Override public void onCreate() { super.onCreate(); AdjAdjuster.onStart(KEY_SERVICE); } @Override public void onDestroy() { AdjAdjuster.onStop(KEY_SERVICE); super.onDestroy(); } }
|
5.3 第三方应用推荐实践
步骤1: 使用前台服务
1 2 3 4 5 6 7
| public class TimerService extends Service { @Override public void onCreate() { super.onCreate(); startForeground(NOTIFICATION_ID, createNotification()); } }
|
步骤2: 提供用户手动加入白名单的入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private void requestBatteryOptimizationExemption() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); if (!pm.isIgnoringBatteryOptimizations(getPackageName())) { new AlertDialog.Builder(this) .setTitle("电池优化") .setMessage("为保证计时器正常运行,建议关闭电池优化") .setPositiveButton("去设置", (dialog, which) -> { Intent intent = new Intent(); intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + getPackageName())); startActivity(intent); }) .setNegativeButton("取消", null) .show(); } }
|
5.4 调试方法
查看进程 ADJ:
1 2 3 4 5 6 7 8
| adb shell dumpsys activity processes | grep -A 5 "com.your.package"
while true; do adb shell dumpsys activity processes | grep -A 5 com.your.package; sleep 1; done
watch -n 1 "adb shell dumpsys activity processes | grep -A 5 com.your.package"
|
查看冻结状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| adb shell dumpsys activity | grep -A 20 "Apps frozen"
adb shell dumpsys activity | grep -A 100 "CachedAppOptimizer"
adb shell am freeze <package-name> adb shell am unfreeze <package-name>
|
查看 OOM Adj 日志:
1
| adb logcat -s ActivityManager:D | grep -E "freezing|unfreezing|ext-provider"
|
5.5 常见问题
Q1: ContentProvider 方案会增加内存占用吗?
A: 几乎不会。Provider 本身是空实现,只是通过外部句柄机制改变了 ADJ 值,不会额外占用内存。
Q2: 忘记调用 onStop() 会有什么影响?
A: 应用会一直保持 ADJ = 0 的高优先级,占用系统资源。建议使用 try-finally 或在组件生命周期方法中确保调用。
Q3: ContentProvider 方案在 Android 12+ 上是否仍然有效?
A: 是的。该机制是 AOSP 的基础设计,在 Android 12、13、14 上均有效。
Q4: 可以同时使用多个防冻结方案吗?
A: 可以,但没有必要。系统会取最低的 ADJ 值,使用 ContentProvider 方案已经是最高优先级。
6. 参考文献
源码文件
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
frameworks/base/services/core/java/com/android/server/am/ContentProviderHelper.java
frameworks/base/services/core/java/com/android/server/am/ContentProviderRecord.java
frameworks/base/services/core/java/com/android/server/am/ProcessList.java
相关文档
相关配置
1 2 3 4 5 6 7 8
| ProcessList.CACHED_APP_MIN_ADJ = 900 ProcessList.FOREGROUND_APP_ADJ = 0 ProcessList.PERCEPTIBLE_APP_ADJ = 200 ProcessList.PERSISTENT_PROC_ADJ = -800
CachedAppOptimizer.DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L
|
总结
应用冻结机制是 Android 系统为优化资源使用而引入的重要特性。对于需要后台长期运行的应用,选择合适的防冻结方案至关重要:
- 系统应用: 优先使用 ContentProvider 方案,改动小、效果好、完全可控
- 第三方应用: 使用 前台服务方案,符合 Android 规范,用户体验好
- 客供应用: 可联系厂商加入 省电白名单,无需修改代码
- 核心系统服务: 考虑 Persistent 方案,但需谨慎使用
无论选择哪种方案,都应该充分理解其原理和限制,在保证功能的同时,兼顾系统资源和用户体验。
文档版本: 1.0
最后更新: 2025-01-15
作者: Android Framework 团队
适用版本: Android 11+