进程保护

进程保护

四月 27, 2018

基本概念

进程类型

  • 前台进程(foreground)
  • 可见进程(visible)
  • 次要服务进程
  • 后台进程(会被保存在 LRU 列表中)
  • 内容提供者进程
  • 空进程

ADJ级别

定义在ProcessList.java文件,oom_adj划分为16级,从-17到16之间取值

进程state级别

定义在ActivityManager.java文件,process_state划分18类,从-1到16之间取值

force-stop

  1. force-stop并不会杀 persistent 进程;

  2. 当app被force-stop后,无法接收到任何普通广播(带FLAG_INCLUDE_STOPPED_PACKAGES的Intent除外),那么监听系统的变化来拉起进程肯定不可行;

    通过force-stop杀掉的进程在packagemanager中还会设置一个flag,以至于之后所有的广播都会将你过滤掉,防止你被系统的广播呼起,直到用户手动点击icon启动该应用,该flag才会置为false。当然这只针对系统广播,我们自己发的广播只要加入flag为FLAG_INCLUDE_STOPPED_PACKAGES就可以了;

  3. 当app被force-stop后,那么alarm闹钟一并被清理;

  4. app被force-stop后,四大组件以及相关进程都被一一清理,即便多进程架构的app也无法拉起自己;

  5. 级联诛杀:当app通过ClassLoader加载另一个app,则会在force-stop的过程中会被级联诛杀;

  6. 生死与共:当app与另一个app使用了share uid,则会在force-stop的过程,任意一方被杀则另一方也被杀,建立起生死与共的强关系。

  7. 需要反射调用,且需要系统签名,即root权限

杀进程方法

  • Process.killProcess(int pid): 杀pid进程
  • Process.killProcessQuiet(int pid):杀pid进程,且不输出log信息
  • Process.killProcessGroup(int uid, int pid):杀同一个uid下同一进程组下的所有进程

无论是系统杀(android机型上长按home键中清除),或者是他杀(第三方管理软件,如360,腾讯等)。其实现方法,基本都是借助ActivityManagerService的removeLruProcessLocked。

5.0以下

1
2
3
4
5
6
7
8
9
10
final void removeLruProcessLocked(ProcessRecord app) {
/.......
if (lrui >= 0) {
if (!app.killed) {
Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
Process.killProcessQuiet(app.pid);
}
/.......
}
}

5.0以上

1
2
3
4
5
6
7
8
9
10
11
final void removeLruProcessLocked(ProcessRecord app) {
/.......
if (lrui >= 0) {
if (!app.killed) {
Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
Process.killProcessQuiet(app.pid);
Process.killProcessGroup(app.info.uid, app.pid);//同组杀
}
/.......
}
}

同组杀

参考文档

  • /acct/uid_XX/pid_XX/cgroup.procs记录了这个进程的所fork()或execlp出来的任何进程,都是该进程的同组进程;
  • Process.killProcessGroup中的group不是linux中的组

App主动杀(Process.killProcess)
内存不够内核杀(LowMemoryKiller)
手动停止(killPackageProcesses)
最近任务(removeTask)

进程被杀的场景

场景 调用接口 可能影响范围
触发系统进程管理机制 LowMemoryKiller 由oom_adj从大到小杀进程
被第三方应用杀死(无Root) killBackground或Force-stop(AccessibilityService) 前者只杀死iim_adj > 4;后者可杀所有非系统进程
被第三方应用杀死(Root) Force-stop或kill 理论上可杀所有进程
厂商杀进程功能 Force-stop或kill 理论上可杀所有进程
用户点击Force-stop Force-stop 可杀所有非系统进程
关机、重启 *** ***

查看进程oom_adj

查看进程id

1
ps | grep [PackageName]

或者在其他调试工具中查看,如:AndroidStudio

查看oom_adj

1
cat /proc/[进程ID]/oom_adj
  • UI进程和常驻Service进程的oom_adj=0,而普通后台进程oom_adj=15
  • App退到后台时,其所有的进程优先级都会降低。但是UI进程是降低最为明显的,因为它占用的内存资源最多,系统内存不足的时候肯定优先杀这些占用内存高的进程来腾出资源。所以,为了尽量避免后台UI进程被杀,需要尽可能的释放一些不用的资源,尤其是图片、音视频之类的。

保活策略

返回START_STICKY

1
2
3
4
public int onStartCommand(Intent intent, int flags, int startId) {  
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}

Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起

双service

Low Memory Killer 机制决定是否杀进程,除了内存大小,还有进程优先级。从这个原理来说,我们可以通过提高进程的优先级来保活。

值得注意的是,Android 的前台service机制。但该机制的缺陷是通知栏保留了图标。

API level < 18

调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。

API level >= 18

在需要提优先级的serviceA启动一个InnerService,两个服务同时startForeground,且绑定同样的NotificationID。Stop掉InnerService ,这样通知栏图标即被移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(R.id.notify, new Notification());
startService(new Intent(this, FakeService.class));
return super.onStartCommand(intent, flags, startId);
}

public class FakeService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(R.id.notify, new Notification());
stopSelf();
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
stopForeground(true);
super.onDestroy();
}
}

<service android:name=".ForegroundEnablingService" />
<service android:name=".ForegroundService" />

no longer works in Android 7.1

监听系统广播判断Service状态

类型 权限/广播
开机广播 RECEIVE_BOOT_COMPLETED
网络变化 ACCESS_NETWORK_STATE
CHANGE_NETWORK_STATE
ACCESS_WIFI_STATE
CHANGE_WIFI_STATE
ACCESS_FINE_LOCATION
ACCESS_LOCATION_EXTRA_COMMANDS
文件挂载 MOUNT_UNMOUNT_FILESYSTEMS
屏幕亮、灭 SCREEN_ON
SCREEN_OFF
锁屏、解锁 RECEIVE_USER_PRESENT
应用安装、卸载 PACKAGE_ADDED
PACKAGE_REMOVED
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<receiver android:name="com.dbjtech.acbxt.waiqin.BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
<action android:name="com.dbjtech.waiqin.destroy" />
</intent-filter>
</receiver>

@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
System.out.println("手机开机了....");
startUploadService(context);
}
if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
startUploadService(context);
}
}

persistent进程

AndroidManifest.xml:android:persistent=”true”

force-stop并不会杀persistent进程,需要系统shareuid

重写Service的onDestroy方法

1
2
3
4
5
6
@Override
public void onDestroy() {
Intent intent = new Intent(this, KeeLiveService.class);
startService(intent);
super.onDestroy();
}

监听第三方应用的静态广播

通过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,找出它们外发的广播,在应用中进行监听

AlarmManager唤醒

1
2
3
4
5
6
7
8
9
10
public void startKeepLiveService(Context context, int timeMillis, String action) {
//获取AlarmManager系统服务
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
//包装Intent
Intent intent = new Intent(context,KeepLiveServie.class);
intent.setAction(action);
PendingIntent pendingIntent = PendingIntent.getService(context,0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
//添加到AlarmManager
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),timeMillis,pendingIntent);
}

账户同步

Android系统里有一个账户系统,系统定期唤醒账号更新服务,同步的事件间隔是有限制的,最短1分钟

1像素悬浮层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MainActivity extends AppCompatActivity {
private static final StringTAG="keeplive";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
Window window = getWindow();
window.setGravity(Gravity.LEFT|Gravity.TOP);
WindowManager.LayoutParams params = window.getAttributes();
params.x=0;
params.y=0;
params.height=1;
params.width=1;
window.setAttributes(params);
}
}

//排除 Activity 在 RecentTask 中的显示
<activity
android:name=".KeepAliveActivity"
android:excludeFromRecents="true"
android:exported="false"
android:finishOnTaskLaunch="false"
android:launchMode="singleInstance"
android:process=":live"
android:theme="@style/LiveActivityStyle"
>
</activity>

//控制 Activity 为透明
<stylename="LiveActivityStyle">
<itemname="android:windowBackground">@android:color/transparent</item>
<itemname="android:windowFrame">@null</item>
<itemname="android:windowNoTitle">true</item>
<itemname="android:windowIsFloating">true</item>
<itemname="android:windowIsTranslucent">true</item>
<itemname="android:windowContentOverlay">@null</item>
<itemname="android:windowAnimationStyle">@null</item>
<itemname="android:windowDisablePreview">true</item>
<itemname="android:windowNoDisplay">true</item>
</style>

//Activity 启动与销毁时机的控制
public class KeepLiveReceiver extends BroadcastReceiver {
privateContextmContext;

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
KeepLiveManeger.getInstance(mContext).startKeepLiveActivity();
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
KeepLiveManeger.getInstance(mContext).destroyKeepLiveActivity();
}
KeepLiveManeger.getInstance(mContext).startKeepLiveService();
}
}

应用间互相拉起

App之间知道包名就可以相互唤醒了,比如杀了qq,只要微信还在就能确保随时唤醒qq。还有百度全系App都通过bdshare实现互拉互保,自定义一个广播,定时发,其他app收广播自起等

C进程

要点:

  • 怎样监听到进程挂掉
  • 怎样把进程拉起来

单进程

Android5.0在应用退出后,ActivityManagerService不仅把主进程给杀死,也会把主进程所属的进程组一并杀死

  1. 让c进程脱离主进程,不要受到主进程的影响:两次fork,退出第一次fork的进程,使第二个子进程是会话组长。
  2. 感知主进程是否存活有两种实现方式:
    • 轮训判断主进程是否存活,其pid是否变为1,耗电;
    • 文件锁,需要封装 Linux 层的文件锁供上层调用;
  3. 循环startService(),通过执行am命令;
  4. 利用 Localsocket 保证 Native 进程的唯一性,不至于出现创建多个 Native 进程以及 Native 进程变成僵尸进程等问题

双进程

父进程如何监视到子进程(监视进程)的死亡?

在linux中,子进程被终止时,会向父进程发送SIG_CHLD信号,于是我们可以安装信号处理函数,并在此信号处理函数中重新启动创建监视进程;

子进程(监视进程)如何监视到父进程死亡?

当父进程死亡以后,子进程就成为了孤儿进程由Init进程领养,于是我们可以在一个循环中读取子进程的父进程PID,当变为1就说明其父进程已经死亡,于是可以重启父进程。

最终还是执行am命令唤起常驻进程


方案3
实际运行的c进程(可执行二进制文件)

1
2
3
4
5
/data/data/com.taobao.idlefish/files/DaemonServer

/data/data/com.netease.mail/files/gdaemon_20161017

/data/data/com.myj.takeout.merchant/lldb/bin/lldb-server

AppWidget

添加一个桌面组件,这个组件也是App的一部分, 但它却是一个广播,一直会被系统持续唤醒。
参考

JobScheduler

Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操作