1 前言
前面一篇【Android实例】1像素进程保活(一)是最基础的写法,但是这种写法还有些不足,还可以进行优化。因为内存也是一个考虑的因素,内存越多的进程会最先被kill掉,所以我们可以开启一个服务,然后将该服务放在另一个进程中,这样这个进程就更加的轻量,更不容易被杀死。
系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app, 这套杀进程回收内存的机制就叫 Low Memory Killer。
2 查看手机内存阈值
- 首先你的手机需要得到root权限
- 在获得root权限的前提下进行以下步骤:
- 在手机开启USB调试并连接AS的情况下,在Terminal里输入:adb shell
- 然后输入(获取超级权限):su
- 接着输入(授权):chmod 777 /sys/module/lowmemorykiller/parameters/minfree
- 最后输入(查看):cat /sys/module/lowmemorykiller/parameters/minfree
一般,你可以得到的数值为:18432,23040,27648,32256,36864,46080。这6个数值分别代表android系统回收6种进程的阈值,这么看不方便查看,转换为M会更直观,这6个数值的单位为page,,所以通过 就能转换为M:72M,90M,108M,126M,144M,180M,即、、、、、这6类进程进行回收的内存阈值分别为72M,90M,108M,126M,144M,180M。这些数值指的是可用内存。
当内存到180M的时候会将空进程进行回收,当内存到144M的时候把空进程回收完以后开始对内容供应节点进行回收,并不是所有的内容供应节点都回收,而是通过判断它的优先级进行回收,优先级是用oom_adj的值来表示,值越大回收的几率越高。可以通过:
ps|grep 包名
来得到进程的id。通过进程id可以查看oom_adj的值:
cat /proc/进程id/oom_adj
adj值为0,代表前台进程;我们按下Back键,将应用至于后台,再次查看adj值为9;如果按home键adj值为8,不同的手机可能值不太一样。adj级别如下表所示:
adj级别 | 值 | 解释 |
---|---|---|
UNKNOWN_ADJ | 16 | 预留的最低级别,一般对于缓存的进程才有可能设置成这个级别 |
CACHED_APP_MAX_ADJ | 15 | 缓存进程,空进程,在内存不足的情况下就会优先被kill |
CACHED_APP_MIN_ADJ | 9 | 缓存进程,也就是空进程 |
SERVICE_B_ADJ | 8 | 不活跃的进程 |
PREVIOUS_APP_ADJ | 7 | 切换进程 |
HOME_APP_ADJ | 6 | 与Home交互的进程 |
SERVICE_ADJ | 5 | 有Service的进程 |
HEAVY_WEIGHT_APP_ADJ | 4 | 高权重进程 |
BACKUP_APP_ADJ | 3 | 正在备份的进程 |
PERCEPTIBLE_APP_ADJ | 2 | 可感知的进程,比如那种播放音乐 |
VISIBLE_APP_ADJ | 1 | 可见进程 |
FOREGROUND_APP_ADJ | 0 | 前台进程 |
PERSISTENT_SERVICE_ADJ | -11 | 重要进程 |
PERSISTENT_PROC_ADJ | -12 | 核心进程 |
SYSTEM_ADJ | -16 | 系统进程 |
NATIVE_ADJ | -17 | 系统起的Native进程 |
备注:(上表的数字可能在不同系统会有一定的出入)
3 Service的onStartCommand的4种返回值
START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。
START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
4 源码讲解
代码在【Android实例】1像素进程保活(一)基础上改动,改动的地方有:
- 新建一个OnePixelService服务
package com.yds.jianshu.onepixel;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class OnePixelService extends Service {
OnePixelManager manager;
public OnePixelService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
manager = new OnePixelManager();
manager.registerOnePixelReceiver(this);//注册广播接收者
return START_STICKY;
}
@Override
public void onDestroy() {
manager.unregisterOnePixelReceiver(this);
}
}
这里onStartCommand返回为START_STICKY,保证服务被杀死后可以被重新创建。初始化OnePixelManager管理类,然后注册OnePixelReceiver服务,该服务主要是用来监听屏幕变化,registerOnePixelReceiver源码如下:
/**
* 一像素广播接收者注册方法。该方法中初始化OnePixelReceiver,并添加了过滤条件
* 屏幕息屏和亮屏。然后注册该广播接收者
* @param context
*/
public void registerOnePixelReceiver(Context context){
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
onePixelReceiver = new OnePixelReceiver();
context.registerReceiver(onePixelReceiver,filter);
}
- AndroidManifest.xml中注册服务
- android:exported="false":表示该服务不能被其他应用程序组件调用或跟它交互,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。
- android:process=":one_pixel_service":开启一个进程,并且服务处于该进程中。
- MainActivity中调用
package com.yds.jianshu.mobile;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.example.myapplication.R;
import com.yds.jianshu.onepixel.OnePixelManager;
import com.yds.jianshu.onepixel.OnePixelService;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity{
private static final String TAG = "[MainActivity]";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
}
private void initData(){
startOnePixelService();
}
private void startOnePixelService(){
Intent intent = new Intent();
intent.setClass(MainActivity.this, OnePixelService.class);
startService(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
Android5.0以下,ActivityManagerService杀死进程代码如下:
Process.killProcessQuiet(pid);
如上代码所示,应用退出后,ActivityManagerService就把主进程给杀死了。在Android5.0以后,ActivityManagerService杀死进程代码如下:
Process.killProcessQuiet(app.pid);
Process.killProcessGroup(app.info.uid, app.pid);
如上所示,在应用退出后,ActivityManagerService不仅把主进程给杀死,另外把主进程所属的进程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也就停止了。所以在Android5.0以后的手机应用在进程被杀死后,要采用其他方案。
5 类图
涉及到的几个类的类图如下所示(仅供参考):
实线箭头:关联
虚线箭头:依赖
6 总结
为什么要用服务来进行1像素保活?
因为当进程在同一adj级别下,内存越多,就会最先被杀死,所以可以重开一个进程,将一像素的业务逻辑放在Service里,这样使得相比之前,Service所在进程更加轻量,更不容易被杀死。而且,在onStartCommand中使用START_STICKY,可以让服务在被杀死后重新创建,提高保活率。1像素保活是为了降低应用被杀死的概率,在Android5.0以上的手机上无法保证应用被杀死后能再次“复活”应用。
源码:https://github.com/Yedongsheng/Jianshu/tree/develop
【Android实例】1像素进程保活(一)
【Android实例】1像素进程保活(二)