怎么避免app中activity不被系统杀死?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了怎么避免app中activity不被系统杀死?相关的知识,希望对你有一定的参考价值。

参考技术A 方法:
对于一个service,可以首先把它设为在前台运行:
publicvoidMyService.onCreate()
super.onCreate();
Notificationnotification=newNotification(android.R.drawable.my_service_icon,
"my_service_name",
System.currentTimeMillis());
PendingIntentp_intent=PendingIntent.getActivity(this,0,
newIntent(this,MyMainActivity.class),0);
notification.setLatestEventInfo(this,"MyServiceNotification,"MyServiceNotificationisRunning!",p_intent);
Log.d(TAG,String.format("notification=%s",notification));
startForeground(0x1982,notification);//notificationID:0x1982,youcannameitasyouwill.


重要设置-------------------------------
相较于/data/app下的应用,放在/system/app下的应用享受更多的特权,比如若在其Manifest.xml文件中设置persistent属性为true,则可使其免受out-of-memorykiller的影响。如应用程序\'Phone\'的AndroidManifest.xml文件:
android:persistent="true"
android:label="@string/dialerIconLabel"
android:icon="@drawable/ic_launcher_phone">
...

设置后app提升为系统核心级别,任何情况下不会被kill掉,settings->applications里面也会屏蔽掉stop操作。

这样设置前的log:Proc#19:adj=svc/B4067b028255:com.xxx.xxx/10001(started-services)
#cat/proc/255/oom_adj

设置后的log:PERS#19:adj=core/F406291f0155:com.xxx.xxx/10001(fixed)
#cat/proc/155/oom_adj
-12#这是CORE_SERVER_ADJ
注:init进程的oom_adj为-16(即SYSTEM_ADJ):cat/proc/1/oom_adj

Android相关部分分析:
在文件frameworks/base/services/java/com/android/server/am/ActivityManagerService.java中有以下的代码:
finalProcessRecordaddAppLocked(ApplicationInfoinfo)
ProcessRecordapp=getProcessRecordLocked(info.processName,info.uid);

if(app==null)
app=newProcessRecordLocked(null,info,null);
mProcessNames.put(info.processName,info.uid,app);
updateLruProcessLocked(app,true,true);


if((info.flags&(ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT))
==(ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT))
app.persistent=true;
app.maxAdj=CORE_SERVER_ADJ;//这个常数值为-12。

if(app.thread==null&&mPersistentStartingProcesses.indexOf(app)<0)
mPersistentStartingProcesses.add(app);
startProcessLocked(app,"addedapplication",app.processName);


returnapp;


可见要想成为coreservice(即app.maxAdj=CORE_SERVER_ADJ(-12)),应用程序需要FLAG_SYSTEM和FLAG_PERSISTENT两个标志,FLAG_SYSTEM指的是应用位于/system/app下,FLAG_PERSISTENT就是指persistent属性。

而对于frameworks/base/services/java/com/android/server/SystemServer.java,则调用
ActivityManagerService.setSystemProcess();
把自己的app.maxAdj设置成SYSTEM_ADJ,即-16。

原理:
Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。由此带来三个问题:
1)回收规则:什么时候回收与回收哪一个?
2)避免误杀:如何阻止被回收?
3)数据恢复与保存:被回收了怎么办?

Android将进程分为6个等级,它们按优先级顺序由高到低依次是:
1.前台进程(FOREGROUND_APP)
2.可视进程(VISIBLE_APP)
3.次要服务进程(SECONDARY_SERVER)
4.后台进程(HIDDEN_APP)
5.内容供应节点(CONTENT_PROVIDER)
6.空进程(EMPTY_APP)

特征:
1.如果一个进程里面同时包含service和可视的activity,那么这个进程应该归于可视进程,而不是service进程。
2.另外,如果其他进程依赖于它的话,一个进程的等级可以提高。例如,一个A进程里的service被绑定到B进程里的组件上,进程A将总被认为至少和B进程一样重要。
3.系统中的phone服务被划分到前台进程而不是次要服务进程.

在android中,进程的oom_adj值也就代表了它的优先级。oom_adj值越高代表该进程优先级越低。文件/init.rc中有以下属性设置:
setpropro.FOREGROUND_APP_ADJ0
setpropro.VISIBLE_APP_ADJ1
setpropro.SECONDARY_SERVER_ADJ2
setpropro.HIDDEN_APP_MIN_ADJ7
setpropro.CONTENT_PROVIDER_ADJ14
setpropro.EMPTY_APP_ADJ15
/init.rc中,将PID为1的进程(init进程)的oom_adj设置为SYSTEM_ADJ(-16):
#Setinititsforkedchildren\'soom_adj.
write/proc/1/oom_adj-16

查看本机设置:
cat/sys/module/lowmemorykiller/parameters/adj
0,1,2,7,14,15

回收时机:
文件/init.rc中:
setpropro.FOREGROUND_APP_MEM1536//6M
setpropro.VISIBLE_APP_MEM2048//8M
setpropro.SECONDARY_SERVER_MEM4096//16M
setpropro.HIDDEN_APP_MEM5120//20M
setpropro.CONTENT_PROVIDER_MEM5632//22.4M
setpropro.EMPTY_APP_MEM6144//24M
这些数字也就是对应的内存阈值,一旦低于该值,Android便开始按顺序关闭相应等级的进程。
注意这些数字的单位是page:1page=4kB。所以上面的六个数字对应的就是(MB):6,8,16,20,22,24。

查看现在的内存阈值设置:
cat/sys/module/lowmemorykiller/parameters/minfree

要想重新设置该值(对应不同的需求):
echo"1536,2048,4096,5120,15360,23040">/sys/module/lowmemorykiller/parameters/minfree
这样当可用内存低于90MB的时候便开始杀死"空进程",而当可用内存低于60MB的时候才开始杀死"内容供应节点"类进程。

具体的回收实现在ActivityManagerService.java中的函数trimApplications():
1.首先移除package已被卸载的无用进程;
2.基于进程当前状态,更新oom_adj值,然后进行以下操作:
1)移除没有activity在运行的进程;
2)如果AP已经保存了所有的activity状态,结束这个AP。
3.最后,如果目前还是有很多activities在运行,那么移除那些activity状态已经保存好的activity。

更新oom_adj的值:
在ActivityManagerService.java文件的ComputeOomAdjLocked()中计算出进程的oom_adj,例如:
if(app==TOP_APP)
//Thelastapponthelististheforegroundapp.
adj=FOREGROUND_APP_ADJ;
app.adjType="top-activity";


Androidkernel中的lowmemorykiller
Android的LowMemoryKiller根据需要(当系统内存短缺时)杀死进程释放其内存,源代码在kernel/drivers/misc/lowmemorykiller.c中。简单说,就是寻找一个最合适的进程杀死,从而释放它占用的内存。
最合适的进程是:
•oom_adj越大
•占用物理内存越多

一旦一个进程被选中,内核会发送SIGKILL信号将之杀死:
for_each_process(p)
??
if(selected==NULL||p->oomkilladj>selected->oomkilladj||
(p->oomkilladj==selected->oomkilladj&&tasksize>selected_tasksize))

selected=p;


if(selected!=NULL)
force_sig(SIGKILL,selected);


查看LRU列表:adbshelldumpsysactivity
当activitydemo在前台时:
包含Service的进程的优先级比较高,在computeOomAdjLocked中将其分为了两小类:
staticfinalintMAX_SERVICE_INACTIVITY=30*60*1000;
if(now<(s.lastActivity+MAX_SERVICE_INACTIVITY))
if(adj>SECONDARY_SERVER_ADJ)
adj=SECONDARY_SERVER_ADJ;
app.adjType="started-services";
app.hidden=false;


if(adj>SECONDARY_SERVER_ADJ)
app.adjType="started-bg-services";

完全让进程不被kill是不可能的,我们可以通过一些操作,使进程被kill的几率变小:
1)提高进程的优先级:
*后台操作采用运行于前台的Service形式,因为一个运行着service的进程比一个运行着后台activity的等级高;
*按back键使得进程中的activity在后台运行而不是destory,需重载back按键(没有任何activity在运行的进程优先被杀).
*依赖于其他优先级高的进程;

2)强制修改进程属性:
*在进程中设置:setPersistent(true);
*在Manifest文件中设置(如上)。
    官方服务
      官方网站官方网站

避免踩坑,内存不足时系统回收Activity的流程解析

前言

android开发中,activity我们会经常遇到,作为view的容器,activity天然就具备了生命周期的特点,当然这篇不是讲生命周期,而是关于系统不足时回收的动作,有可能导致app运行时会出现一些不可预料的“逻辑”异常行为。

以一个例子出发

在一些比较久的项目中,可能会存在这样一个事务处理架构,比如有个推送到来,同时eventbus发送给activity1进行部分逻辑处理,然后再把处理好的数据发送给其他Activity,比如例子中的Activity2,此时Activity2就处于可见状态。

可能有读者问为什么会有这么一个奇怪的架构,emmm,在笔者所经历的项目中,还真的有这样的处理,我们暂且抛开这个架构不谈,我们来思考一下,这个架构有什么不妥的地方!很明显,事件的处理依赖了Activity1这个中间环节,倘若Activity1被系统所回收了,那么整个消息处理环节就中断了!从而导致不可预期的逻辑出现。

Activity回收

那么问题来了,这个不可见的activity(这里activity1跟activity2属于同一个任务栈),有没有可能会被系统所回收,如果有可能会被回收,那么什么情况下才会出现,如果会出现,有没有手段可以避免?

我们带着这三个问题,去继续我们探索

ActivityThread的内存回收机制

我们都知道,Activity创建过程中,会通过ActivityThread进行各种初始化,其中我们特别关注一下attach函数(以master分支为例子,android13) cs.android.com/android/pla…

ActivityThread.java

private void attach(boolean system, long startSeq) 
        ...
        // Watch for getting close to heap limit.
        BinderInternal.addGcWatcher(new Runnable() 
            @Override public void run() 
                if (!mSomeActivitiesChanged) 
                    return;
                
                Runtime runtime = Runtime.getRuntime();
                long dalvikMax = runtime.maxMemory();
                long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                // 当内存大于3/4的时候,启动回收策略
                if (dalvikUsed > ((3*dalvikMax)/4)) 
                    if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                            + " total=" + (runtime.totalMemory()/1024)
                            + " used=" + (dalvikUsed/1024));
                    mSomeActivitiesChanged = false;
                    try 
                    // 释放逻辑
                        ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
                     catch (RemoteException e) 
                        throw e.rethrowFromSystemServer();
                    
                
            
        );

通过上面源码我们可以看到,attach中通过BinderInternal.addGcWatcher进行了一个gc的监听,如果此时已用内存大于runtime.maxMemory()即当前进程最大可用内存的3/4的时候,就会进入一个释放逻辑,我们继续看ActivityTaskManager.getService().releaseSomeActivities中releaseSomeActivities函数的实现

@Override
public void releaseSomeActivities(IApplicationThread appInt) 
    synchronized (mGlobalLock) 
        final long origId = Binder.clearCallingIdentity();
        try 
            // 真正的释放,通过WindowProcessController,原因是low-mem
            final WindowProcessController app = getProcessController(appInt);
            app.releaseSomeActivities("low-mem");
         finally 
            Binder.restoreCallingIdentity(origId);
        
    

这个比较简单,就是直接包了一层,真正处理的是通过WindowProcessController的releaseSomeActivities方法,这个releaseSomeActivities非常重要,是我们上面三个问题的答案

void releaseSomeActivities(String reason) 
    // Examine all activities currently running in the process.
    // Candidate activities that can be destroyed.
    ArrayList<ActivityRecord> candidates = null;
    if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Trying to release some activities in " + this);
    for (int i = 0; i < mActivities.size(); i++) 
        遍历所有的ActivityRecord
        final ActivityRecord r = mActivities.get(i);
        如果当前activity本来就处于finishing或者DESTROYING/DESTROYED状态,continue,即不加入activity的释放列表
        if (r.finishing || r.isState(DESTROYING, DESTROYED)) 
            if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
            return;
        
        // 如果处于以下状态,则该activity也不会被回收
        if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
                || r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING)) 
            if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
            continue;
        

        // 稍后我们会讲到,这里其实就是说明当前window是不是合法的window
        if (r.getParent() != null) 
            if (candidates == null) 
                candidates = new ArrayList<>();
            
            candidates.add(r);
        
    

    // 上面所以要释放的activityRecord信息都存在了candidates中
    if (candidates != null) 
        // Sort based on z-order in hierarchy.
        candidates.sort(WindowContainer::compareTo);
        // Release some older activities
        int maxRelease = Math.max(candidates.size(), 1);
        do 
            final ActivityRecord r = candidates.remove(0);
            if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + r
                    + " in state " + r.getState() + " for reason " + reason);
            // 回收
            r.destroyImmediately(reason);
            --maxRelease;
         while (maxRelease > 0);
    

我们一步步解释一下上面的关键方法,上面ArrayList candidates 就是一个即将被释放的ActivityRecord列表,那么ActivityRecord是什么呢?相关的解释已经有很多了,这里我们其实简单理解ActivityRecord其实 是Activity的标识,与每个Activity是一一对应,只不过在ActivityThread中我们操作的对象是ActviityRecord而不是Activity罢了,关系图可以参考以下

总之,candidates 就包含了系统即将回收的activity,这里就回答了我们第一个问题,activity是有可能被回收的

接着我们继续看

if (r.finishing || r.isState(DESTROYING, DESTROYED)) 
  if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
    return;

如果当前的activity的finishing 为true 或者 当前状态处于DESTROYING, DESTROYED,那么这个activity就不会再被加入回收列表了,因为本来已经要被回收

接着,处于以下状态的ActivityRecord,也不会被回收

r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()

这几个判断条件非常有意思

  • mVisibleRequested 当前activity虽然处于onstop,但是已经被要求可见,比如后台播放activity,不过现在大部分不支持了,还有就是壁纸类应用,也可以设置mVisibleRequested == true
  • stopped 处于非stopped状态,就是当前可见activity
  • !r.hasSavedState(),这个并非只activity没有重载onSaveInstanceState,没有重载onSaveInstanceState也有可能回收,可看源码
boolean hasSavedState() 
    return mHaveState;

void setSavedState(@Nullable Bundle savedState) 
   mIcicle = savedState;
   mHaveState = mIcicle != null;

setSavedState 中savedState为null的时候基本是activity已经被回收的情况,比如activity处于不在历史任务里面,此时savedState就为null(但是这种activity不可见时就会被回收,可以尝试一下)

  • !r.isDestroyable isDestroyable == false的activity,处于前台可见时,就是isDestroyable == false

这里就回答了我们的第二个问题,回收条件是当已用内存超过3/4且activity不可见时,且不满足上诉条件的activity就会被加入回收列表中。

验证

到了验证环节,我们可以通过创建三个activity,如下

在可见的MyActivity3中,通过以下代码模拟内存分配

companion object
    @JvmStatic
    var list = ArrayList<ByteArray>()

val runtime = Runtime.getRuntime()
val byteArray = ByteArray(1024*10000)
list.add(byteArray)
Log.e("hello","$runtime.maxMemory() $runtime.totalMemory() $runtime.freeMemory()")

多次分配后,就能看到处于非任务栈顶的MyActivity1跟MyActivity2就被回收掉了

思考与拓展

那么我们有没有办法阻止系统这种回收行为呢?我们来思考一下问题3,有读者可能会想到,打破这几个判断条件之一就可以了r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable() 但是很遗憾的是,除了可见activity外,笔者暂时还没找到其他打破以上规则的方法,因为大部分都是系统应用才能做到(如果有黑科技的话,可望告知),当然,系统回收activity然后用到的时候帮我们再次创建,这也是一个非常合理的行为,所以如果不是非常特殊的情况,请不要干扰正常的内存回收行为。

总结

从一个小小的activity回收,我们能看到系统做了很多很多的内部处理,保证了app运行时内存的充足,同时回归本文一开始提到的架构问题,我们尽量不要采取这种方式去传递信息,相反的,如果需要中转处理,我们完全可以依赖一个静态的全局类去处理即可,而不是把处理依赖于具有生命周期的activity,大家也可以检查一下自己的项目中有没有这种写法,有的话要尽量改掉噢!否则线上说不定还真的出现这种异常的逻辑情况,好啦本篇到此结束,感谢阅读!!

作者:Pika
链接:https://juejin.cn/post/7162521119004557348

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

以上是关于怎么避免app中activity不被系统杀死?的主要内容,如果未能解决你的问题,请参考以下文章

怎么让 Android 程序一直后台运行,像 QQ 一样不被杀死

Android Service 不被杀死并提高优先级

怎么让 Android 程序一直后台运行,像 QQ 一样不被杀死

怎么让Android程序一直后台运行,像QQ一样不被杀死

如果 Activity 被操作系统杀死,我们如何检索保存的状态?

使用极光/友盟推送,APP进程杀死后为啥收不到推送