基础知识

Android基础(32)Android内存及进程包活

2019-03-05  本文已影响296人  perry_Fan

1)Android为每个应用程序分配的内存大小是多少?
2)Android中进程内存的分配,能不能自己分配定额内存?
3)进程保活的方式
4)如何保证一个后台服务不被杀死?(相同问题:如何保证service在后台不被kill?)比较省电的方式是什么?
5)App中唤醒其他进程的实现方式

一. Android为每个应用程序分配的内存大小?

熟悉Android内存分配机制的朋友都知道,Android为每个进程分配内存时,采用弹性的分配方式,即刚开始并不会给应用分配很多的内存,而是给每一个进程分配一个“够用”的内存大小。
实际测试一下:

  private void getMaxMemoryInfo(){
        Runtime rt = Runtime.getRuntime();
        long maxMemory = rt.maxMemory();
        Log.e("MaxMemory:", Long.toString(maxMemory/(1024*1024)));
        ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        Log.e("MemoryClass:", Long.toString(activityManager.getMemoryClass()));
        Log.e("LargeMemoryClass:", Long.toString(activityManager.getLargeMemoryClass()));
 }

输出结果为:

06-06 15:27:22.740 11917-11917/com.suning.myapp E/MaxMemory:: 192
06-06 15:27:22.740 11917-11917/com.suning.myapp E/MemoryClass:: 192
06-06 15:27:22.740 11917-11917/com.suning.myapp E/LargeMemoryClass:: 512

把AndroidManifest.xml中的application标签加上

<application
        ...
        android:largeHeap="true"
        ...>
        ...
</application>

输出结果为:

06-06 15:32:06.168 21973-21973/com.suningtang.myapp E/MaxMemory:: 512
06-06 15:32:06.168 21973-21973/com.suningtang.myapp E/MemoryClass:: 192
06-06 15:32:06.168 21973-21973/com.suningtang.myapp E/LargeMemoryClass:: 512

设置largeHeap为true时, 通过rt.maxMemory();获取的值为512M。
这台手机,系统正常分配的内存最多为192M;当设置largeHeap时,最多可申请512M。当超过这个值时,就会出现OOM了。
这个值是在哪设置的呢?查看/system/build.prop文件内容:

shell@NX510J:/ $ cat /system/build.prop | grep heap
dalvik.vm.heapsize=36m
   
dalvik.vm.heapstartsize=8m    ----起始分配内存
dalvik.vm.heapgrowthlimit=192m ---- 一般情况app申请的最大内存 dalvik.vm.heapsize=512m   
---- 设置largeheap时,App可用的最大内存dalvik.vm.heaptargetutilization=0.75  ---- GC相关

dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m     ----- GC机制相关

ActivityManagergetMemoryClass()getLargeMemoryClass() 方法返回的的是哪里的值呢?
//ActivityManager.java

public int getMemoryClass() {
        return staticGetMemoryClass();
    }
    
    /** @hide */
    static public int staticGetMemoryClass() {
       
        String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
        if (vmHeapSize != null && !"".equals(vmHeapSize)) {
            return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
        }
        return staticGetLargeMemoryClass();
    }
    
    /**
     * Return the approximate per-application memory class of the current
     * device when an application is running with a large heap.  This is the
     * space available for memory-intensive applications; most applications
     * should not need this amount of memory, and should instead stay with the
     * {@link #getMemoryClass()} limit.  The returned value is in megabytes.
     * This may be the same size as {@link #getMemoryClass()} on memory
     * constrained devices, or it may be significantly larger on devices with
     * a large amount of available RAM.
     *
     * <p>The is the size of the application's Dalvik heap if it has
     * specified <code>android:largeHeap="true"</code> in its manifest.
     */
    public int getLargeMemoryClass() {
        return staticGetLargeMemoryClass();
    }
    
    /** @hide */
    static public int staticGetLargeMemoryClass() {
        String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
        return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length() - 1));
    }

上面的源码表明,getMemoryClass()getLargeMemoryClass()方法最终读取的仍然是dalvik.vm.heapgrowthlimit 和 dalvik.vm.heapsize 的值。而且,dalvik.vm.heapsize 默认值为16M,这也是解释了google的原生OS默认值是16M了。而 dalvik.vm.heapgrowthlimit 和 dalvik.vm.heapsize 的值各个手机厂家的OS会对这个值进行修改,所以存在差异。

在App中获取内存信息

我们在应用中可以通过ActivityManager的MemoryInfo内部类获取内存信息,方法如下:

private void getMemoryInfo() {
    ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
    manager.getMemoryInfo(info);  
    Log.e("Memory","系统总内存:"+(info.totalMem / (1024*1024))+"M");
    Log.e("Memory","系统剩余内存:"+(info.availMem / (1024*1024))+"M");
    Log.e("Memory","系统是否处于低内存运行:"+info.lowMemory );
    Log.e("Memory","系统剩余内存低于"+( info.threshold  / (1024*1024))+"M时为低内存运行");
}
二. 进程保活方式

Android 进程拉活包括两个层面:

  1. 提供进程优先级,降低进程被杀死的概率
  2. 在进程被杀死后,进行拉活
2.1 进程的优先级

进程的重要性,划分5级:

前台进程 (Foreground process)
可见进程 (Visible process)
服务进程 (Service process)
后台进程 (Background process)
空进程 (Empty process)

2.1.1 前台进程
用户当前操作所必需的进程。通常在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。
1) 拥有用户正在交互的 Activity(已调用 onResume())
2) 拥有某个 Service,后者绑定到用户正在交互的 Activity
3) 拥有正在“前台”运行的 Service(服务已调用 startForeground())
4) 拥有正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
5) 拥有正执行其 onReceive() 方法的 BroadcastReceiver

2.1.2 可见进程
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
1)拥有不在前台、但仍对用户可见的 Activity(已调用 onPause())
2)拥有绑定到可见(或前台)Activity 的 Service

2.1.3 服务进程
尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
正在运行 startService() 方法启动的服务,且不属于上述两个更高类别进程的进程。

2.1.4 后台进程
后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU 列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

对用户不可见的 Activity 的进程(已调用 Activity的onStop() 方法)

2.1.5 空进程
保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

不含任何活动应用组件的进程。

####### 2.2 Android进程回收策略
Android 中对于内存的回收,主要依靠 Lowmemorykiller 来完成,是一种根据 OOM_ADJ 阈值级别触发相应力度的内存回收的机制。
关于 OOM_ADJ 的说明如下:

其中红色部分代表比较容易被杀死的 Android 进程(OOM_ADJ>=4),绿色部分表示不容易被杀死的 Android 进程,其他表示非 Android 进程(纯 Linux 进程)。在 Lowmemorykiller 回收内存时会根据进程的级别优先杀死 OOM_ADJ 比较大的进程,对于优先级相同的进程则进一步受到进程所占内存和进程存活时间的影响。

Android 手机中进程被杀死可能有如下情况:


综上,可以得出减少进程被杀死概率无非就是想办法提高进程优先级,减少进程在内存不足等情况下被杀死的概率。

2.3 提升进程优先级的方案

2.3.1 利用 Activity 提升权限
方案设计思想:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。

通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1。
方案适用范围:

方案具体实现:首先定义 Activity,并设置 Activity 的大小为1像素:


其次,从 AndroidManifest 中通过如下属性,排除 Activity 在 RecentTask 中的显示:


最后,控制 Activity 为透明:


Activity 启动与销毁时机的控制:


2.3.2 利用 Notification 提升权限

方案设计思想:Android 中 Service 的优先级为4,通过 setForeground 接口可以将后台 Service 设置为前台 Service,使进程的优先级由 4 提升为 2 ,从而使进程的优先级仅仅低于用户当前正在交互的进程,与可见进程优先级一致,使进程被杀死的概率大大降低。

方案实现挑战:从 Android2.3 开始调用 setForeground 将后台 Service 设置为前台 Service 时,必须在系统的通知栏发送一条通知,也就是前台 Service 与一条可见的通知时绑定在一起的。

对于不需要常驻通知栏的应用来说,该方案虽好,但却是用户感知的,无法直接使用。

方案挑战应对措施:通过实现一个内部 Service,在 LiveService 和其内部 Service 中同时发送具有相同 ID 的 Notification,然后将内部 Service 结束掉。随着内部 Service 的结束,Notification 将会消失,但系统优先级依然保持为2。

方案适用范围:适用于目前已知所有版本。
方案具体实现:


2.4 进程死后拉活的方案
2.4.1 利用系统广播拉活

方案设计思想:在发生特定系统事件时,系统会发出响应的广播,通过在 AndroidManifest 中“静态”注册对应的广播监听器,即可在发生响应事件时拉活。

常用的用于拉活的广播事件包括:


方案适用范围:适用于全部 Android 平台。但存在如下几个缺点:

  1. 广播接收器被管理软件、系统软件通过“自启管理”等功能禁用的场景无法接收到广播,从而无法自启。
  2. 系统广播事件不可控,只能保证发生事件时拉活进程,但无法保证进程挂掉后立即拉活。
2.4.2 利用第三方应用广播拉活

方案设计思想:该方案总的设计思想与接收系统广播类似,不同的是该方案为接收第三方 Top 应用广播。

通过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,以及友盟、信鸽、个推等 SDK,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。

方案适用范围:该方案的有效程度除与系统广播一样的因素外,主要受如下因素限制:

  1. 反编译分析过的第三方应用的多少
  2. 第三方应用的广播属于应用私有,当前版本中有效的广播,在后续版本随时就可能被移除或被改为不外发。
2.4.3 利用系统Service机制拉活

方案设计思想:将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活:



方案适用范围:如下两种情况无法拉活

  1. Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起。
  2. 进程被取得 Root 权限的管理工具或系统工具通过 forestop 停止掉,无法重启。
2.4.4 利用Native进程拉活

方案设计思想:

2.4.5 利用 JobScheduler 机制拉活

方案设计思想:Android5.0 以后系统对 Native 进程等加强了管理,Native 拉活方式失效。系统在 Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操作。
对 JobScheduler 进行了进一步封装,兼容 Android5.0 以下版本。封装后 JobScheduler 接口的使用如下:


方案适用范围:该方案主要适用于 Android5.0 以上版本手机。

该方案在 Android5.0 以上版本中不受 forcestop 影响,被强制停止的应用依然可以被拉活,在 Android5.0 以上版本拉活效果非常好。

仅在小米手机可能会出现有时无法拉活的问题。

2.5 其他有效拉活方案

这些方案包括:

  1. 利用系统通知管理权限进行拉活
  2. 利用辅助功能拉活,将应用加入厂商或管理软件白名单。

这些方案需要结合具体产品特性来搞。

上面所有解释这些方案都是考虑的无 Root 的情况。
其他还有一些技术之外的措施,比如说应用内 Push 通道的选择:

https://segmentfault.com/a/1190000006251859

上一篇下一篇

猜你喜欢

热点阅读