Android系统的进程保活是一个老生常谈的问题了,经历了历代系统的更新,也产生了很多的保活方案,有的已经失效,有的依然坚挺。如此漫长的保活征程,我们回过头来考虑一下,我们的应用为什么要做保活?或者说我们的应用真的都需要保活吗?
首先,我想这个问题的答案应该是否定的,我认为并不是所有的应用都应该做保活,对于大多数的应用来说长时间的运行在后台并没有什么太大的好处,也许只是在切换回应用的时候能够得到更积极的响应而已,而这个优点可能要付出非常惨重的代价,而且这个代价是个连锁反应,会传染,当n多个手机上的应用都要做进程保活的时候,可想而知,Android手机会卡成什么样,不管这个手机是1k买的,还是6k买的,我想卡出翔只是个时间问题。所以我认为,在做进程保活之前需要考虑一下,真的有这个必要嘛,当然,也许保不保活不是我们程序员能决定的,得看领导的意思,但是我们多少应该考虑一下吧,万一以后我们中的某些称为领导那!
其次,我认为大多数应用不需要保活的原因是,Android系统为了维护系统的平稳流畅运行才有了杀进程的机制,我们知道,内存在移动端是稀缺资源,我们必须珍惜的用,不然的话,不管6G还是8G的内存,都有用光的时候,当系统开始杀进程的时候,说明内存真的有点紧张了,需要某些应用做出些牺牲,从系统的角度来看,这种行为是非常合理地,我总不能为了让你们都活着把我自己饿死吧!另外,考虑到进程被杀对于应用来说是一种猝不及防的事件,系统在杀进程之前还会通知应用,也就是onLowMemory
和onTrimMemory(int level)
会被调用,尤其是onTrimMemory(int level)
,系统会及时通知应用目前系统的内存状况,也就是会有不同的level,如下;
我们完全可以根据目前的内存状况作出自己的响应,比如释放一些内存。这样做,兴许系统内存就够用了,系统不杀进程了,也就不存在保活的顾虑的,当然这种行为是不可靠的,你开发的应用也许做得很好,但是别人的应用在内存方面考虑的不全面,这样系统还是会照杀不误的,你是良民也不行啊!
最后,对于一些特别的应用,进程保活还是很有必要,甚至是必须的。比如IM,导航,运动类。这些应用的特点就是,有可能并且非常多时候是在后台运行的,没有UI,根据系统的设定,后台进程的优先级很低,被杀的可能性极大,而且这些应用都需要得到及时的响应,你想想,你发个QQ然后切到后台,焦急的等待妹子的回复,结果一直没动静,心灰意冷的重新启动应用的时候发现进程已经被杀死了,因为这个原因又错过一段美好姻缘!再比如,你兴奋的跑了20公里,准备发朋友圈炫耀一下,结果打开Keep,发现进程死了,运动没有被记录下来,多可恨。所以对于这些应用来说进程保活几乎是必须的,即使没法百分百的确保进程能存活下来,但是只要有一线希望就不能放弃。
本节内容引用自http://geek.csdn.net/news/detail/95035,感谢作者。
系统要杀你的应用,总不能无缘无故的吧,主要有以下几点:
前台进程(Foreground process)
可见进程(Visible process)
服务进程(Service process)
后台进程(Background process)
空进程(Empty process)
前台进程的重要性最高,依次递减,空进程的重要性最低,下面分别来阐述每种级别的进程
1.1. 前台进程 —— Foreground process
用户当前操作所必需的进程。通常在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。
A. 拥有用户正在交互的 Activity(已调用 onResume())
B. 拥有某个 Service,后者绑定到用户正在交互的 Activity
C. 拥有正在“前台”运行的 Service(服务已调用 startForeground())
D. 拥有正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
E. 拥有正执行其 onReceive() 方法的 BroadcastReceiver
1.2. 可见进程 —— Visible process
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
A. 拥有不在前台、但仍对用户可见的 Activity(已调用 onPause())。
B. 拥有绑定到可见(或前台)Activity 的 Service
1.3. 服务进程 —— Service process
尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
A. 正在运行 startService() 方法启动的服务,且不属于上述两个更高类别进程的进程。
1.4. 后台进程 —— Background process
后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU 列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。
A. 对用户不可见的 Activity 的进程(已调用 Activity的onStop() 方法)
1.5. 空进程 —— Empty process
保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
Android 中对于内存的回收,主要依靠 Lowmemorykiller 来完成,是一种根据 OOM_ADJ 阈值级别触发相应力度的内存回收的机制。
关于 OOM_ADJ 的说明如下:
其中红色部分代表比较容易被杀死的 Android 进程(OOM_ADJ>=4),绿色部分表示不容易被杀死的 Android 进程,其他表示非 Android 进程(纯 Linux 进程)。在 Lowmemorykiller 回收内存时会根据进程的级别优先杀死 OOM_ADJ 比较大的进程,对于优先级相同的进程则进一步受到进程所占内存和进程存活时间的影响。
Android 手机中进程被杀死可能有如下情况:
综上,可以得出减少进程被杀死概率无非就是想办法提高进程优先级,减少进程在内存不足等情况下被杀死的概率。
终于到了要保活的时候了,根据上文,我认为要想做好进程保活,主要有两点,第一点做好性能优化,尽量少的占用内存,营造一个好的环境,这样就能减少系统杀进程的次数;第二点,就是提高系统的优先级,降低oom_adj,减小被杀死的几率。以下是具体的措施:
在onCreate
中,调用startForeground
开启一个前台进程,并且给它设置一个通知。我们可以通过检查当前进程的oom_adj来看看有木有效果。
λ adb shell
vbox86p:/ # su
vbox86p:/ # ps | grep zephyr // zephyr是包名的一部分
u0_a63 1697 266 843320 93968 ep_poll ed4e2bb9 S com.zephyr.app.servicedemo
vbox86p:/ # cat /proc/1697/oom_adj // 当应用在前台的时候
0
vbox86p:/ # cat /proc/1697/oom_adj // 切到了后台了
11
vbox86p:/ #
我们可以明显的看到,当应用在前台的时候oom_adj=0,到后台就变成了oom_adj=11,极易被杀死,这就是为啥后台服务容易被杀的原因。然后我们开启前台服务,重新启动应用看一下;
vbox86p:/ # ps | grep zephyr
u0_a63 1879 266 843324 94196 ep_poll ed4e2bb9 S com.zephyr.app.servicedemo
vbox86p:/ # cat /proc/1879/oom_adj // 前台
0
vbox86p:/ # cat /proc/1879/oom_adj // 切到后台了
3
vbox86p:/ #
效果还是很明显的,oom_adj变成3了,小于4,不那么容易被杀死了。
另外,我们还可以将onStartCommand
的返回值设置为START_STICKY
,并且在onDestroy中,当服务被杀死的时候,重新开启服务。前者,当系统在条件允许的时候,会重新启动这个Service,但是传过来的intent为null,在原生系统上可以复现,但是对于国内很多深度定制的系统来说并没有什么卵用。后者,Service被杀死的时候,onDestroy不一定被调用,所以也不太靠谱。
这种方案流传已久,目的也是为了降低oom_adj,主要就是通过监听系统的锁屏和解锁广播,然后开启和销毁一个1像素的Activity。实现如下;
ScreenListenerManager
封装了广播接收器
ScreenManager
封装了对1像素Activity的管理
SinglePixelActivity
1像素的Activity
MainActivity
调起1像素Activity的Activity,比如某运动APP的运动界面,我们要锁屏的时候就调起它
// ScreenListenerManager
public class ScreenListenerManager {
private final Receiver mReceiver;
private WeakReference mReference;
private OnScreenListener mScreenListener;
public ScreenListenerManager(Context context) {
mReference = new WeakReference<>(context);
mReceiver = new Receiver();
}
public void register() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_PRESENT);
mReference.get().registerReceiver(mReceiver, filter);
}
public void unRegister() {
mReference.get().unregisterReceiver(mReceiver);
}
public class Receiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (mScreenListener == null) return;
switch (action) {
case Intent.ACTION_SCREEN_ON: // 开屏
mScreenListener.onScreenOn();
break;
case Intent.ACTION_SCREEN_OFF: // 锁屏
mScreenListener.onScreenOff();
break;
case Intent.ACTION_USER_PRESENT: // 解锁
mScreenListener.onUserresent();
break;
}
}
}
public void setOnScreenListener(OnScreenListener listener) {
mScreenListener = listener;
}
public interface OnScreenListener{
void onScreenOn();
void onScreenOff();
void onUserresent();
}
}
// ScreenManager
public class ScreenManager {
private Context mContext;
private WeakReference mWeakReference;
private static ScreenManager mScreenManager;
private ScreenManager(Context context) {
mContext = context;
}
public static ScreenManager getInstance(Context context) {
if (mScreenManager == null) {
synchronized (ScreenManager.class) {
if (mScreenManager == null) {
mScreenManager = new ScreenManager(context);
}
}
}
return mScreenManager;
}
public void setSinglePixelActivity(Activity activity) {
mWeakReference = new WeakReference<>(activity);
}
public void startSinglePixelActivity(Activity from) {
Intent intent = new Intent(from, SinglePixelActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
public void finishSinglePixelActivity() {
if (mWeakReference != null &&mWeakReference.get() != null) {
mWeakReference.get().finish();
}
}
}
// SinglePixelActivity
public class SinglePixelActivity extends AppCompatActivity {
private static final String TAG = "SinglePixelActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Window window = getWindow();
WindowManager.LayoutParams attributes = window.getAttributes();
attributes.x = 0;
attributes.y = 0;
attributes.gravity = Gravity.LEFT | Gravity.TOP;
attributes.width = 1;
attributes.height = 1;
window.setAttributes(attributes);
ScreenManager.getInstance(getApplicationContext()).setSinglePixelActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
// MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ScreenListenerManager mManager;
private ScreenManager mScreenManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mManager = new ScreenListenerManager(this);
mManager.register();
mManager.setOnScreenListener(new ScreenListenerManager.OnScreenListener() {
@Override
public void onScreenOn() {
Log.d(TAG, "onScreenOn: ");
}
@Override
public void onScreenOff() {
Log.d(TAG, "onScreenOff: ");
// 开启1像素的Activity
mScreenManager.startSinglePixelActivity(MainActivity.this);
}
@Override
public void onUserresent() {
Toast.makeText(MainActivity.this, "onUserresent!", Toast.LENGTH_LONG).show();
Log.d(TAG, "onUserresent: ");
// 关闭1像素的Activity
mScreenManager.finishSinglePixelActivity();
}
});
mScreenManager = ScreenManager.getInstance(getApplicationContext());
}
@Override
protected void onDestroy() {
super.onDestroy();
mManager.unRegister();
}
}
".SinglePixelActivity"
android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="false"
android:launchMode="singleInstance"
android:theme="@style/SingleActivityStyle">
以上就是具体的实现。
到这里,这个应用已经没治了。死了还要救过来,虽然随着系统的更新,很多拉活的方法已经失效,但是也有新的方法产生,总之就是道高一尺魔高一丈。
目前比较有效的方法有JobScheduler, 使用特定平台的推送等。由于这些行为实在是太流氓了,没有用过,推荐看下http://www.52im.net/thread-1140-1-1.html。
合理保活进程,维护Android系统的平稳流畅,是每个Android开发者的责任和义务,毕竟谁也不想用卡成翔的手机!
参考:
http://geek.csdn.net/news/detail/95035
https://juejin.im/entry/59e9aa7b5188257f6970bb4a