大家都知道,Android App在退出页面的时候,是不会杀死进程的,这就可能导致Memory不足。为了在移动设备上运行Android系统,必然有一套完整的杀死进程的机制,称为LMK机制。
通过阅读本文,可以了解Android LMK机制,从而避免App被频繁的杀死,以及一些开发中会遇到的问题。
为了空出足够的内存供前台进程使用,Android会定时进行CHECK进程树,然后杀死优先级别不高的进程。而进程的优先级别是按照属性 oom_adj 来判断的。oom_adj数值越低,越不会被杀死。
oom_adj的数字大致为这几种:
ro.FOREGROUND_APP_ADJ 0 :前台进程,正在活动的Activity或者使用startForeground的Service
ro.VISIBLE_APP_ADJ 1 :可见进程,不可操作的Activity,但是可见
ro.SECONDARY_SERVER_ADJ 2 :拥有后台服务器的进程
ro.HIDDEN_APP_MIN_ADJ 7 :Activity没有完全退出,直接采用 moveTaskToBack 到HOME的进程??
ro.CONTENT_PROVIDER_ADJ 14:内容提供进程
ro.EMPTY_APP_ADJ 15:空进程
可以发现,oom_adj的定义和我们学习过的android教材定义(进程优先级)是一样的。当系统的内存不足的时候,那么就会杀死发送KILL SIGNAL, 杀死一些优先级别低的进程,用来提供足够的内存给前台进程使用。
防止App被系统杀死,主要是为了Service能正常的运行提供服务,如音乐播放,PUSH服务等。这时候,就需要一些手段来完成这个目标。
1. Service 使用 startForeground ,设置自己的优先级为前台进程,这样子,就不会被杀死。
2. 若Application被LMK杀死,那么归属于这个Application的Service当然也不能幸免。但是AMS进程会监听KILL SIGNAL,如果被杀死的Application中存在onStartCommand返回START_STICKY的Service,则AMS会过一段时间后,自动启动该Service(不一定会启动成功)。
3.Service放在另外一个进程中,避免占用内存过多,导致优先被杀死。
4. 当前页面处于MainActivity的时候,重写onBackPressed()->使用moveTaskToBack(true)来完成切换到HOME,避免MainActivity被回收,从而提高优先级。
5.监听系统广播,如 BOOT_COMPLETED,ACTION_TIME_TICK 等。但是这种方法越来越不可靠。
当然,还有一些其他方法,但是在Android系统越来越完善的情况下,利用API不提供的方法(如漏洞,Native)来保证Service运行的方式越来越不可靠,所以建议还是采用Android系统提供的方式,来保证Service的长时间运行。
当Application被杀死后,重新启动Application会发生一些特殊的情况,比如说,会莫名其妙的读取到空指针之类的(建议用原生系统测试,因为现在第三方ROM可能会做了这些优化)。
如果一个App,在后台运行的时候,超过30min,就可能会被Android杀死,用来释放内存,给其他App使用。这时候进入App,会遇到奇怪的问题。
上图描述了App被杀死,对于Activity的处理流程。结合http://stackoverflow.com/questions/14375720/android-destroying-activities-killing-processes(介绍android Activity 被杀死进程后的样子),在android杀死进程后,再次进入App,在不同的版本有不同的情况
1. android2.3之前的,会直接从MainActivity进入
2. android2.3之后,会从最后的Activity进入onCreate方法,然后进行还原。还原依赖onSaveInstanceState()方法保存的数据,并且点击back按钮,也是类似的。也就是所,会重建整个之间的Activity栈,并且都是从onCreate进入。
情况2,会引发其他问题,比如说,在Application中的全局数据之类的,会导致读取空指针的问题。
所以,通常的做法就是,如果Activity初始化的时候,检测到Application等依赖数据没有初始化,则直接杀死进程重启。
下面有这面文章介绍了如何模拟进程被杀死的情况
http://stackoverflow.com/questions/11365301/how-to-simulate-android-killing-my-process/
这里再提一下,FragmentActivity在被杀死的情况下,还原的情况。在点击HOME后,Activity会调用onSaveInstanceState()方法保存当前Fragment状态,用于重建FragmentActivity使用。这时候,就会造成一个问题,会重建的时候,出现重叠的Fragment,这并不是我们想要的结果。
如图,App崩溃后,点击ICON进入App后,在FragmentActivity的onCreate方法中,重建了之前通过onSaveInstanceState()保存的Frament,并且调用Fragment中的onCreate方法。这时候,极其容易出现空指针的错误。特别是getActivity() 会获取到null,因为这时候没有调用onAttach()。
所以,需要重写onSaveInstanceState()方法,让它不调用 super.onSaveInstanceState()方法,这样子,就不会保存点击HOME的时候Fragment的状态。
许多Andorid的ROM都被第三方厂商定制过,导致出现许多标准Android上未发现的电池管理。
魅族flyme 4.5.7 和 MIUI 7 有一个自启动管理,如果你的APP不在列表中,那么极其容易被KILL 回收内存。而一旦加入白名单,那么,系统将允许你在休眠后保留在后台,并且侧滑杀死App是无效或自动重启
只有白名单的App才能在后台访问网络以及其他资源,这会导致即使你的App在白名单中,也无法正常的使用推送服务。当然微信,QQ,之类的已经默认是白名单了。但是,这对我们普通App的话,会导致推送不及时的问题,需要逐个的测试各个定制ROM,才能保证我们的APP不被定制ROM杀死了也不知道是什么情况。
对于APP来说,完全退出是一项非常基本的功能,但是,这个功能要实现的比较好,也是比较困难的。最麻烦的地方就是“帐号切换了,但是还存在子进程或者子线程在运行上一个用户业务,导致完成的业务数据写入当前切换成功的用户数据库中”,如果拥有推送进程的话,那么还需要notify推送进程关闭,避免杀死Service进程又自重启。通常的做法是关闭所有服务,杀死所有的进程,然后重启。如下是一段比较完善的代码:
/** * 重启App */ static public void killApp(Context context, boolean restart) { //重启 if (restart) { Intent intent = new Intent(context, MyActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } //停止后台 service try { //读取所有的SERVICE信息 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SERVICES); if (packageInfo != null && packageInfo.services != null) { for (ServiceInfo serviceInfo : packageInfo.services) { try { Class cls = Class.forName(serviceInfo.name); context.stopService(new Intent(context, cls)); } catch (ClassNotFoundException e) { } } } } catch (Exception e) { Log.e(MyActivity.class.getName(), e.getMessage(), e); } //杀死所有进程 try { Set<String> processNameSet = new HashSet<String>(); //读取 所有的Process { //读取Activity { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES); if (packageInfo != null && packageInfo.activities != null) { for (ActivityInfo activityInfo : packageInfo.activities) { processNameSet.add(activityInfo.processName); } } } //读取Service { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SERVICES); if (packageInfo != null && packageInfo.services != null) { for (ServiceInfo serviceInfo : packageInfo.services) { processNameSet.add(serviceInfo.processName); } } } //读取 RECEIVERS { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_RECEIVERS); if (packageInfo != null && packageInfo.receivers != null) { for (ActivityInfo activityInfo : packageInfo.receivers) { processNameSet.add(activityInfo.processName); } } } //读取 PROVIDERS { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS); if (packageInfo != null && packageInfo.providers != null) { for (ProviderInfo providerInfo : packageInfo.providers) { processNameSet.add(providerInfo.processName); } } } } //关闭所有进程 { int myPid = android.os.Process.myPid(); ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); Iterator iterator = mActivityManager.getRunningAppProcesses().iterator(); while (iterator.hasNext()) { ActivityManager.RunningAppProcessInfo appProcess = (ActivityManager.RunningAppProcessInfo) iterator.next(); if (processNameSet.contains(appProcess.processName)) { //先KILL其他进程 if (appProcess.pid != myPid) { android.os.Process.killProcess(appProcess.pid); } } } //杀死自己 android.os.Process.killProcess(myPid); } } catch (Exception e) { Log.e(MyActivity.class.getName(),e.getMessage(),e); throw new RuntimeException(e); } }
核心思想是:
1. 发送重启自己到主Activity事件到AMS
2.读取AndroidManifest.xml 中所有Service信息,调用stopService进行关闭,避免重启Service
3. 杀死所有AndroidManifest.xml process 指向的进程,除了自己
4. 杀死自己
这样子就能完全退出app了,值得一提的是网上出现 restartPackage, killBackgroundProcesses 貌似在5.1上都不可行。当然,上面的代码测试环境,也是5.1了。
当然,如果是针对多用户切换,也可以考虑专门构建用户线程池,然后在退出的时候关闭这个指定的用户线程池,最后直接跳到登入页面
源码地址: http://git.oschina.net/darkgem/KillApplication