如何让我们的Android应用进程保活?

一、线程 进程 应用

默认下,同一个应用的所有组件都运行在同一个进程中。当然也可以在manifest清单文件中设置组件运行的进程。

组件元素activityservicereceiverprovider,都有一个process属性可以指定组件运行在哪个进程中。这个属性可以设置为每个组件运行在自己的进程中,或者设置进程同名与其他一些组件共享一个进程。

Android会在某些时刻决定关闭一个进程,比如内存剩余较小并且其他进程更迫切需要内存时,某些进程会被关闭时,且进程中的组件们都被销毁.如果重新需要这些组件工作时,进程又会被创建出来。

系统不会为每个组件的实例分别创建线程。所有运行于一个进程的组件都在主线程(UI线程)中被实例化,并且系统对每个组件的调用都在这个线程中派发,且只能在UI线程中管理的你的界面。

可以 线程 进程 应用 打个比方:

线程(Thread):流水线 、进程(process): 车间 、应用:工厂

为什么使用多线程?

线程如同一条流水线,当我们执行一些比如网络连接或数据库请求这样的耗时操作,会造成该线程阻塞。而我们又需要在主线程(UI线程)中对整个界面进行响应。所以为了不阻塞UI线程,提高应用程序的性能,需要使用多线程来操作。

为什么使用多进程?

在Android系统中,每个进程都有一个内存限制。如果一个应用可以多有个进程,那么这个应用可以有更多的内存来运行,使我们的应用内存限制变大,优化程序的速度。

二、进程的等级(进程的生命周期)

一. 进程按照优先级分为不同的等级:

前台进程(Foreground process):

A. 拥有用户正在交互的 Activity( onResume()状态)

B. 正在与bound绑定服务交互的 Activity

C. 正在“前台”运行的 Service(startForeground()被调用)

D. 生命周期函数正在被执行的 Service(onCreate()、onStart() 或 onDestroy())

E. 正执行 onReceive() 方法的 BroadcastReceiver

(该进程优先级别最高,杀死前台进程需要用户的响应。)

1

2

3

4

5

6

7

8

9

10

11

12

可见进程(Visible process):

该进程并不是在最前端,并没有得到焦点,但是我们却能看到它们。

A. 拥有不在前台、但仍对用户可见的 Activity(例如弹出一个对话框的Activity)

B. 绑定到可见进程(或前台进程)中的Activity 的 Service

1

2

3

4

服务进程(Service process):

A. 正在运行的通过 startService() 启动的,且不属于上述两个更高进程状态的Service

1

2

后台进程(Background process):

A. 不可见状态的Activity进程

1

2

空进程(Empty process):

A. 没有运行任何应用组件的进程,保留这个进程主要是为了缓存的需要,待下次相关组件运行时直接从内存中获取数据,缩短对数据获取的时间。

1

2

进程等级判断原则:当存在多种等级的进程状态时,优先考虑优先级较高的进程。

三、进程回收机制:Low Memory Killer

在Android系统,当用户退出应用程序之后,应用程序的进程还是会存在于系统中,这样方便于程序的再次启动。

但随着打开的应用程序数量的增加,系统内存会变得不足,就需要杀掉一部分应用程序的进程以释放内存空间。

至于是否需要杀死哪些进程需要被杀死,是通过Low Memory Killer机制来进行判定的。

Low Memory Killer是通过进程的oom_adj与占用内存的大小决定要杀死的进程,oom_adj越小越不容易被杀死。

有一组系统内存临界值和与之一一对应的一组oom_adj值,当系统剩余内存位于内存临界值中的一个范围内时,如果一个进程的oom_adj值大于/等于这个临界值对应的oom_adj值,该进程就会被杀掉。

四、防止进程被杀

轻量化进程:按照Low Memory Killer的杀死进程的规则,应尽量让Service在后台做较少的事情,且及时释放内存资源。

利用service提升进程权限: 调用 startForeground方法将service置为“前台进程”,不过这样我们需要发送一个Notification通知。

如何取消通知:

1. 在Android 4.3之前,可以通过构造一个空的Notification使通知栏不会显示我们发送的Notification。

2. 在Android 4.3之后,谷歌不再允许构造空的Notification。但有一个奇葩的方法:

可以将两个同进程的Service都通过startForeground设置为前台进程,但它们使用的是同一个ID的Notification通知,这样只会产生一个Notification。然后其中一个Service取消前台通知,那么该通知会被关闭。这样剩下的Service还是一个前台Service,且通知栏没有通知。

1

2

3

4

5

6

7

在application标签中加入android:persistent=“true”

在androidmanifest.xml中的application标签中加入android:persistent="true" 属性后能够达到保证该应用程序所在进程不会被LMK杀死,异常出现后也可以自启。但前提是应用程序必须是系统应用,即应用程序不能采用通常的安装方式。必须将应用程序的apk包直接放到/system/app目录下。而且必须重启系统后才能生效。

1

2

五、被杀后重启

利用Service机制重启:

安卓系统设计Service时,可以通过其onStartCommand方法中返回不同的值告知系统,让系统在Service因为内存不足被杀掉后可以在资源不紧张时重启。

START_NOT_STICKY

如果系统在onStartCommand()方法返回之后杀死这个服务,那么直到接受到新的Intent对象,这个服务才会被重新创建。

START_STICKY

如果系统在onStartCommand()返回后杀死了这个服务,系统就会重新创建这个服务并且调用onStartCommand()方法,但是它不会重新传递最后的Intent对象,系统会用一个null的Intent对象来调用onStartCommand()方法。

在这个情况下,除非有一些被发送的Intent对象在等待启动服务。这适用于不执行命令的媒体播放器(或类似的服务),它只是无限期的运行着并等待工作的到来。

START_REDELIVER_INTENT

如果系统在onStartCommand()方法返回后,系统就会重新创建了这个服务,并且用发送给这个服务的最后的Intent对象调用了onStartCommand()方法。任意等待中的Intent对象会依次被发送。这适用于那些应该立即恢复正在执行的工作的服务,如下载文件。

1

2

3

4

5

6

7

8

9

10

虽然在这种情况可以让Service被系统重启的,但不能立即被重启。而且在某些定制ROM上失效。

双进程守护

设计AB两个不同进程,A进程里面轮询检查B进程是否存活,没存活的话将其拉起,同样B进程里面轮询检查A进程是否存活,没存活的话也将其拉起,而后台逻辑可以随便放在某个进程里执行即可。

android:name=".services.FirService"

android:process=":fir" />

android:name=".services.SecService"

android:process=":sec" />

1

2

3

4

5

6

7

使用两个进程分别装载两个Service,两个Service相互轮询唤醒:

public class FirService extends Service {

public final static String TAG = "com.example.servicedemo.FirService";

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

Log.e(TAG, "onStartCommand");

thread.start();

return START_STICKY;

}

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

Timer timer = new Timer();

TimerTask task = new TimerTask() {

@Override

public void run() {

Log.e(TAG, "FirService Run: "+System.currentTimeMillis());

boolean b = Util.isServiceWorked(FirService.this, "com.example.servicedemo.SecService");

if(!b) {

Intent service = new Intent(FirService.this, SecService.class);

startService(service);

Log.e(TAG, "Start SecService");

}

}

};

timer.schedule(task, 0, 1000);

}

});

@Override

public IBinder onBind(Intent arg0) {

return null;

}

}

--------------------------------------

public class SecService extends Service {

public final static String TAG = "com.example.servicedemo.SecService";

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

Log.e(TAG, "onStartCommand");

thread.start();

return START_REDELIVER_INTENT;

}

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

Timer timer = new Timer();

TimerTask task = new TimerTask() {

@Override

public void run() {

Log.e(TAG, "SecService Run: " + System.currentTimeMillis());

boolean b = Util.isServiceWorked(SecService.this, "com.example.servicedemo.FirService");

if(!b) {

Intent service = new Intent(ServiceTwo.this, FirService.class);

startService(service);

}

}

};

timer.schedule(task, 0, 1000);

}

});

@Override

public IBinder onBind(Intent arg0) {

return null;

}

}

-----------------------------------

public class Util {

public static boolean isServiceWorked(Context context, String serviceName) {

ActivityManager myManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

ArrayList runningService = (ArrayList) myManager.getRunningServices(Integer.MAX_VALUE);

for (int i = 0; i < runningService.size(); i++) {

if (runningService.get(i).service.getClassName().toString().equals(serviceName)) {

return true;

}

}

return false;

}

}

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

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

利用静态广播接收器重启进程:

使用静态注册的广播接收器BroadcastReceiver来监听一些系统广播/其他应用发出的广播在onReceive中重启进程。

无法接受到系统?:

1. 在Android 3.1之后,处于stopped状态的应用无法接收到系统广播。

2. 如果一个应用在安装后从来没有启动过,或者已经被用户强制停止了,那么这个应用就处于停止状态(stopped state)。

3. 如果想使处于stopped状态的应用也接收到广播,需要在intent中增加FLAG_INCLUDE_STOPPED_PACKAGES这个Flag。要注意的是,用户无法自定义系统广播。

4. 有些广播只有静态接收器可以接收得到。

5. 深度定制ROM使应用重回STOPPED状态

1

2

3

4

5

6

7

8

与系统Service捆绑:NotificationListenerService

NotificationListenerService是通过系统调起的服务,当有应用发起通知的时候,系统会将通知的动作和信息回调给NotificationListenerService。

android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"

android:process=":service">

-------------------

public class NotificationService extends NotificationListenerService {

@Override

public void onNotificationPosted(StatusBarNotification sbn) {

}

@Override

public void onNotificationRemoved(StatusBarNotification sbn) {

}

}

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

当系统发现某应用产生通知或者用户删除某通知,都会回调该服务的上述这两个函数,函数的参数StatusBarNotification包含着该通知的具体信息。.

其他:全家桶/SDK唤醒、手机厂商的白名单定制

六、Android应用进程保活的权衡

进程保活要回到用户体验。有的进程保活不能做到性能优化,反而在不断重启费电、亦或如幽灵般重复出现在用户面前。在某些应用场景里,有的进程保活起到了应有的作用,比如音乐播放时的前台Service…..

流氓的进程保活只会搞坏Android 生态环境,伤害 Android 平台开发者等人的利益。当然,Google也不会让这么流氓方式胡作非为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值