MIUI 省电策略无图标App自动重置问题分析

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>
0%