Sad Goblin

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  3 随笔 :: 0 文章 :: 4 评论 :: 10240 阅读

 

首先吐槽下某米的手机,质量不错,去年这时候收到的,用到现在除了摄像头里进灰去售后免费修了一次之外,其他的都还好。

美中不足就在于其搭载的MIUI v5不能完全释放APQ8064T 2G内存的潜力,刚打开的程序,往往切换到后台之后没多久就被“终结”了,想再切换回去只能等待系统重新加载一遍应用。

我最开始怀疑是系统占用资源过多所致,但是每次查看内存,总还有700MB可用(这里MIUI的任务管理器和Android自带的应用管理器给出的结果不一致,MIUI给出的数值一般较小,但也有700MB)。而且手中那台老掉渣内存仅有1G的Moto Atrix即使放一夜也不会自动关掉你之前打开的程序,这就否定了内存不足这个猜测。

然后我转而怀疑是MIUI的进程管理自动关掉了空闲的后台程序。于是我在任务管理器里把所有进程都上了锁(就是长按HOME之后把App图标往下拉),之后自动关闭的情况虽然会好一些,但是仍不能完全根除。哪怕是占用内存很少的程序,比如设置等,闲置一段时间后仍然会被kill掉。

既然还是找不到幕后杀手,就只有查看Logcat,看看凶手有没有留下蛛丝马迹了。

经过一番搜索,Logcat给了我这些:

1
2
3
4
04-28 14:47:37.844: I/ActivityManager(597): No longer want com.cleanmaster.miui_module (pid 918): hidden #25
04-28 14:47:37.925: I/ActivityManager(597): No longer want com.miui.guardprovider (pid 1342): hidden #25
04-28 14:47:43.020: W/ExtraActivityManagerService(597): No longer want com.miui.networkassistant (pid 1823) for more free memory
04-28 14:47:43.020: I/ActivityManager(597): No longer want com.android.fileexplorer (pid 1805): hidden #25

可以看出是ActivityManager(或者更确切点,ActivityManagerService)和ExtraActivityManagerService两个家伙在不停地干掉我的后台程序。

Google之,试图找到已有的解决办法,结果仅有的几篇相关文章也没把问题的根本原因说明白,只是诸如“为什么我开发的程序在后台被关闭了”等等泛泛的讨论。

搜索无果后,我决定直接查看Android源码,Google搜索 "No longer want" site:android.googlesource.com/

搜到了这个,https://android.googlesource.com/platform/frameworks/base/+/ee7621c0f5de6eca2cfb9fb2b6117fb61e13cc41%5E!/

commit描述里面写了对empty process以及hidden process分开处理;以前是二者统一处理,共用一个上限(mProcessLimit),现在empty和hidden有了独立的上限,但是不知道该commit的版本是否和MIUI的Android版本一致。先不管这个,直接查看2s的MIUI v5对应的Android版本,是4.1.1:

https://android.googlesource.com/platform/frameworks/base/+/android-4.1.1_r6.1/services/java/com/android/server/am/ActivityManagerService.java

这个是am(Activity Manager)的源码之一,ActivityManagerService.java。在里面搜索"No longer want",得到:

if (numHidden > mProcessLimit) {
    Slog.i(TAG, "No longer want " + app.processName
            + " (pid " + app.pid + "): hidden #" + numHidden);
    EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
            app.processName, app.setAdj, "too many background");
    app.killedBackground = true;
    Process.killProcessQuiet(app.pid);
}

看样子是由于后台进程数量过多,导致系统关闭了多余的hidden进程。并且上面提到的commit中对empty process的处理机制在这里并不存在,hidden进程和empty进程一并作为后台进程处理,并且其数量之和不能超过一个阈值。这个阈值mProcessLimit,其初始化为:

1
int mProcessLimit = ProcessList.MAX_HIDDEN_APPS;

这样一来基本可以确定问题的解决方法了:只要增大ActivityManagerService实例的mProcessLimit,或修改ProcessList.MAX_HIDDEN_APPS即可。先看后者,ProcessList.java中,MAX_HIDDEN_APPS为static final,故无法修改,除非自己编译ROM。有趣的是AOSP中该值为15,而MIUI似乎把这个值增大到了24,以容纳其更加臃肿的系统,但是看来还是不够。

好在ActivityServiceManager中提供了这样一个方法:

1
public void setProcessLimit(int max)

可以直接调用之来修改mProcessLimit。那么如何调用一个系统类中的方法呢?ActivityManagerService并不存在于Android SDK的android.jar中,所以在第三方App中直接调用是不可能的。或许可以通过自行编译一个含有隐藏类的android.jar来实现调用,但这会随着系统版本更迭而产生很多兼容性问题,故否定。

那么只好祭出我们的大杀器了:Xposed框架!Xposed Framework是一款专门用来修改系统资源及代码注入的工具:

http://repo.xposed.info/module/de.robv.android.xposed.installer

过程简述如下:通过Xposed框架,在ActivityManagerService的startRunning()方法之后注入代码,执行setProcessLimit(40)。

 

代码如下:

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
102
103
104
105
106
package com.barius.morebackground;
 
import java.lang.reflect.Method;
 
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
 
public class XposedModule implements IXposedHookLoadPackage {
 
    private static boolean LOG_ON = true;
    private static void LOG(String content) {
        if (LOG_ON) {
            XposedBridge.log(content);
        }
    }
 
    private static final String[] TARGET_PACKAGE_NAMES = {
        "android",
        "com.barius.morebackground"
    };
 
    private static final int NEW_PACKAGE_LIMIT = 40;
 
    public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
        // only want certain target packages
        boolean targetFound = false;
        int targetIdx = -1;
        for (int i = 0; i < TARGET_PACKAGE_NAMES.length; i++) {
            if (lpparam.packageName.equals(TARGET_PACKAGE_NAMES[i])) {
                targetFound = true;
                targetIdx = i;
            }
        }
        if (!targetFound) {
            return;
        }
 
        LOG("=== MoreBackground Loaded app: " + lpparam.packageName);
 
        switch (targetIdx) {
        case 0:
            hackActivityManagerService(lpparam);
            break;
        case 1:
            //changeProcessLimit(lpparam);
            //checkProcessLimit(lpparam);
            break;
        }
 
        LOG("=== Job done.");
    }
 
    private boolean hackActivityManagerService(final LoadPackageParam lpparam) {
        return changeProcessLimit(lpparam);
    }
     
     
    // !!! MASSIVE DESTRUCTION !!! USE WITH CAUTION !!!
    private void hookEveryMethod(LoadPackageParam lpparam) {
        String targetClassName = "com.android.server.am.ActivityManagerService";
         
        final Class<?> clazz = XposedHelpers.findClass(targetClassName, lpparam.classLoader);
        Method[] methods = clazz.getMethods();
        for(int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            XposedBridge.hookMethod(m, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    LOG("--- Called: " + param.method.getName());
                }
            });
        }
    }
     
    private boolean changeProcessLimit(LoadPackageParam lpparam) {
        final String targetClassName = "com.android.server.am.ActivityManagerService";
        final String targetMethodName = "startRunning";
 
        final Class<?> clazz = XposedHelpers.findClass(targetClassName, lpparam.classLoader);
        final Method startRunning = XposedHelpers.findMethodExact(clazz, targetMethodName,
                String.class, String.class, String.class, String.class);
        XposedBridge.hookMethod(startRunning, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                LOG("After " + targetMethodName + "()");
 
                Object _this = param.thisObject;
                LOG(_this.getClass().getName());
 
 
                LOG("--- Using XposedHelper to invoke method");
                XposedHelpers.callMethod(_this, "setProcessLimit", NEW_PACKAGE_LIMIT);
                LOG("--- ... done");
            }
        });
         
        return true;
    }
 
    private void checkProcessLimit(LoadPackageParam lpparam) {
 
    }
}

 

 运行之后发现进程退出现象明显好转(ExtraActivityManagerService还是会杀进程,但这似乎是MIUI的进程管理器,并且上了锁之后就不会乱杀,先不管了)。Logcat显示:

1
04-28 15:17:53.242: I/ActivityManager(597): No longer want com.miui.notes (pid 1786): hidden #41

#41 说明修改成功,后台进程限制被改为40。MIUI报告剩余内存在500MB左右浮动。尚不清楚这么做会对系统耗电量有多大影响,先试试看看吧。

 

OK,这下踏实了,也不用为了进程问题去刷不稳定的第三方系统了。

posted on   Sad Goblin  阅读(7686)  评论(4编辑  收藏  举报
编辑推荐:
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
阅读排行:
· 如何给本地部署的DeepSeek投喂数据,让他更懂你
· 超详细,DeepSeek 接入PyCharm实现AI编程!(支持本地部署DeepSeek及官方Dee
· 用 DeepSeek 给对象做个网站,她一定感动坏了
· .NET 8.0 + Linux 香橙派,实现高效的 IoT 数据采集与控制解决方案
· .NET中 泛型 + 依赖注入 的实现与应用
点击右上角即可分享
微信分享提示