MIUI 省电策略无图标 App 自动重置问题分析
简介
解决无图标 APP 在 MIUI 系统下省电策略会被自动重置问题
测试机型:红米 K30Pro
MIUI 版本:12.2.3
Android 版本:11
无图标 App 安装后,应用详情中将设置省电策略为"无限制",手机重启后,APP 的省电策略会重置为"智能限制后台运行",导致 APP 保活失效
问题排查
找到 MIUI 电源管理的 app ,包名为 com.miui.powerkeeper
,从手机中提取出 apk 进行人工分析
调用时序图如下:
@startuml
PowerKeeperBackgroundService -> PowerKeeperManager : onBootCompleted() 监听开机完成
PowerKeeperManager -> PowerKeeperConfigureManager : 调用onBootCompleted()
PowerKeeperConfigureManager -> PowerKeeperConfigureManager : 调用initUserConfigure()
PowerKeeperConfigureManager -> PowerKeeperConfigureManager : 调用pkgHasIcon()
PowerKeeperConfigureManager -> ApplicationPackageManager : 调用getLaunchIntentForPackage()
@enduml
详细代码流程如下:
APP 启动的后台 Service com.miui.powerkeeper.PowerKeeperBackgroundService.onCreate()
中判断开机是否完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| /* loaded from: classes.dex */
public class PowerKeeperBackgroundService extends Service {
@Override // android.app.Service
public void onCreate() {
super.onCreate();
//...
// 判断开机完成
if (SystemProperties.get("sys.boot_completed").equals("1")) {
PowerKeeperManager.getInstance(this).onBootCompleted();
}
}
}
|
监听开机完成后,调用 this.mPowerKeeperConfigureManager.onBootCompleted()
1
2
3
4
5
6
7
8
9
10
11
12
| package com.miui.powerkeeper;
public class PowerKeeperManager {
public void onBootCompleted() {
Log.i(TAG, "onBootCompleted");
synchronized (this.mSyncObject) {
if (!this.isBootCompleted) {
this.isBootCompleted = true;
this.mPowerKeeperConfigureManager.onBootCompleted();
}
}
}
}
|
调用初始化逻辑 PowerKeeperConfigureManager.this.initUserConfigure();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| package com.miui.powerkeeper.provider;
public class PowerKeeperConfigureManager {
public void onBootCompleted() {
this.mHandler.post(new Runnable() { // from class: com.miui.powerkeeper.provider.PowerKeeperConfigureManager.3
@Override // java.lang.Runnable
public void run() {
int[] iArr;
int[] runningUsers;
synchronized (PowerKeeperConfigureManager.this.mLock) {
// ..
PowerKeeperConfigureManager.this.initUserConfigure();
}
}
});
}
}
|
初始化过程中有以下逻辑
- 判断 APP 图标是否隐藏
- 将隐藏图标的 APP 策略修改为
miuiAuto
- 判断的方式是调用系统
context.getPackageManager().getLaunchIntentForPackage(pkg)
函数,返回 false
则为无图标 APP
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
| package com.miui.powerkeeper.provider;
public class PowerKeeperConfigureManager {
public void initUserConfigure(int i) {
// ...
for (String str4 : packageUidsMap.keySet()) {
int intValue = packageUidsMap.get(str4).intValue();
if (isControlApp(intValue)) {
if (PreSetGroup.isGroupUid(intValue)) {
str4 = PreSetGroup.getGroupHead(intValue);
}
powerpkgs.add(str4);
// 判断是否隐藏图标
if (!pkgHasIcon(str4) || PreSetApp.isPreSetApp(str4)) {
hideiconmap.add(str4);
if (sb2 == null) {
sb2 = new StringBuilder(4096);
sb2.append(str4);
} else {
sb2.append(":").append(str4);
}
}
}
}
// ...
// 隐藏图标的APP,策略重置为miuiAuto
if (hideiconmap.contains(pkg)) {
if (DEBUG) {
str = TAG;
sb = new StringBuilder();
sb.append("insert ");
sb.append(pkg);
Log.d(str, sb.toString());
}
contentValues.put("userId", Integer.valueOf(i));
contentValues.put("pkgName", pkg);
contentValues.put(UserConfigure.Columns.BG_CONTROL, "miuiAuto");
contentResolverForUser.insert(UserConfigure.CONTENT_URI, contentValues);
}
// ...
}
private boolean pkgHasIcon(String str) {
return Utils.pkgHasIcon(this.mContext, str);
}
public static boolean pkgHasIcon(Context context, String str) {
return context.getPackageManager().getLaunchIntentForPackage(str) != null;
}
}
|
分析系统 ApplicationPackageManager.getLaunchIntentForPackage
源码,发现只要能够查询到
-
<action android:name="android.intent.action.MAIN" />
-
<category android:name="android.intent.category.INFO" />
信息,MIUI 系统就会判定为有图标
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
| //frameworks/base/core/java/android/app/ApplicationPackageManager.java
@Override
public Intent getLaunchIntentForPackage(String packageName) {
// First see if the package has an INFO activity; the existence of
// such an activity is implied to be the desired front-door for the
// overall package (such as if it has multiple launcher entries).
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(Intent.CATEGORY_INFO);
intentToResolve.setPackage(packageName);
List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0);
// Otherwise, try to find a main launcher activity.
if (ris == null || ris.size() <= 0) {
// reuse the intent instance
intentToResolve.removeCategory(Intent.CATEGORY_INFO);
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
intentToResolve.setPackage(packageName);
ris = queryIntentActivities(intentToResolve, 0);
}
if (ris == null || ris.size() <= 0) {
return null;
}
Intent intent = new Intent(intentToResolve);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(ris.get(0).activityInfo.packageName,
ris.get(0).activityInfo.name);
return intent;
}
|
绕过方法
在 AndroidManifest.xml
中添加以下 intent-filter
的 activity
,在 APP 没有图标的情况下,getLaunchIntentForPackage(pkg)
能够返回数据,MIUI 系统判定为有图标 APP,最终绕过重置逻辑
1
2
3
4
5
6
7
|
<activity android:name=".TestActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.INFO" />
</intent-filter>
</activity>
|