上海天局信息技术有限责任公司

网站首页 > 天局论坛 > Android > App

Android 保持Service不被Kill掉的方法--双Service守护 && Android实现双进程守护

2017-12-08 14:17:43 上海天局信息技术有限责任公司 已读

本文分为两个部分,第一部分为双Service守护,第二部分为双进程守护

第一部分:

一、Service简介:
Java.lang.Object

  Android.content.Context

      ↳android.content.ContextWrapper

          ↳android.app.Service

Service是应用程序Application的一个组件(component)。
它的作用有两点:1.用来提供一个长期在后台运行并且不与用户交互的操作,2.也可以为其他应用程序提供服务。
Service必须和其他四大组件一样,使用<service>标签在AndroidManifest.xml中进行声明。
启动service有两种方式Context.startService() 和 Context.bindService()。

注意,除了特别指定外,service并不是单独的进程,一般service在其宿主进程的主线程(UI Thread)中运行【当然也可以在新的线程中startService,这样Service就不是在MainThread了】。这意味着,如果您的服务要做任何 耗时(如 MP3 播放) 或阻塞 (比如网络) 操作,它应该产生它自己的线程,用来做那项工作。(service不是单独的进程也不是单独的线程)

Service提供了两大功能:
Context.startService()用来在后台启动一个服务;
Context.bindService()用来绑定其他服务,以此来获取其他service提供的服务;

 

本地服务 Local Service 用于应用程序内部

它可以启动并运行,直至有人停止了它或它自己停止。在这种方式下,它以调用Context.startService()启动,而以调用Context.stopService()结束。它可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。不论调用了多少次startService()方法,你只需要调用一次stopService()来停止服务。

【用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好】

 


远程服务 Remote Service 用于android系统内部的应用程序之间

它可以通过自己定义并暴露出来的接口进行程序操作。客户端建立一个到服务对象的连接,并通过那个连接来调用服务。连接以调用Context.bindService()方法建立,以调用 Context.unbindService()关闭。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。

【可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可】

 

 

二、Service运行方式和生命周期图:

以startService()启动服务,系统将通过传入的Intent在底层搜索相关符合Intent里面信息的service。如果服务没有启动则先运行onCreate,然后运行onStartCommand (可在里面处理启动时传过来的Intent和其他参数),直到明显调用stopService或者stopSelf才将停止Service。无论运行startService多少次,只要调用一次stopService或者stopSelf,Service都会停止。使用stopSelf(int)方法可以保证在处理好intent后再停止。onStartCommand ,在2.0后被引入用于service的启动函数,2.0之前为public void onStart(Intent intent, int startId) 。


以bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止。onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。采用Context.bindService()方法启动服务时只能调用onUnbind()方法解除调用者与服务解除,服务结束时会调用onDestroy()方法。

 

(注意这个新老API的改变)


void onStart(Intent intent, int startId)
This method was deprecated      in API level 5.    Implement onStartCommand(Intent, int, int) instead.

 

int onStartCommand(Intent intent, int flags, int startId)
Called by the system every time a client explicitly starts the service by calling  startService(Intent), providing the arguments it supplied and a  unique integer token representing the start request.

 

三、Service的优先级

官方文档告诉我们,Android系统会尽量保持拥有service的进程运行,只要在该service已经被启动(start)或者客户端连接(bindService)到它。当内存不足时,需要保持,拥有service的进程具有较高的优先级。

1. 如果service正在调用onCreate,onStartCommand或者onDestory方法,那么用于当前service的进程则变为前台进程以避免被killed。
2. 如果当前service已经被启动(start),拥有它的进程则比那些用户可见的进程优先级低一些,但是比那些不可见的进程更重要,这就意味着service一般不会被killed.
3. 如果客户端已经连接到service (bindService),那么拥有Service的进程则拥有最高的优先级,可以认为service是可见的。
4. 如果service可以使用startForeground(int, Notification)方法来将service设置为前台状态,那么系统就认为是对用户可见的,并不会在内存不足时killed。
5. 如果有其他的应用组件作为Service,Activity等运行在相同的进程中,那么将会增加该进程的重要性。

 

四、保持service不被kill掉

方法一:

START_STICKY is used for services that are explicitly started and stopped as needed, while START_NOT_STICKY or START_REDELIVER_INTENT are used for services that should only remain running while processing any commands sent to them

onStartCommand方法几个返回值简介:
 
1、START_STICKY
 
在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建     service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent。
 
2、START_NOT_STICKY
 
在运行onStartCommand后service进程被kill后,并且没有新的intent传递给它。Service将移出开始状态,并且直到新的明显的方法(startService)调用才重新创建。因为如果没有传递任何未决定的intent那么service是不会启动,也就是期间onstartCommand不会接收到任何null的intent。
 
3、START_REDELIVER_INTENT
 
在运行onStartCommand后service进程被kill后,系统将会再次启动service,并传入最后一个intent给onstartCommand。直到调用stopSelf(int)才停止传递intent。如果在被kill后还有未处理好的intent,那被kill后服务还是会自动启动。因此onstartCommand不会接收到任何null的intent。

@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {
flags = START_STICKY;return super.onStartCommand(intent, flags, startId);
}

【结论】 手动返回START_STICKY,亲测当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错,但是不能保证任何情况下都被重建,比如进程被干掉了....

方法二:

提升service优先级

 在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。

复制代码
        <service
android:name="com.dbjtech.acbxt.waiqin.UploadService"
android:enabled="true" >
            <intent-filter android:priority="1000" >
                <action android:name="com.dbjtech.myservice" />
            </intent-filter>
        </service>
复制代码

【结论】目前看来,priority这个属性貌似只适用于broadcast,对于Service来说可能无效

方法三:

提升service进程优先级

Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:

   1.前台进程( FOREGROUND_APP)
   2.可视进程(VISIBLE_APP )
   3. 次要服务进程(SECONDARY_SERVER )
   4.后台进程 (HIDDEN_APP)
   5.内容供应节点(CONTENT_PROVIDER)
   6.空进程(EMPTY_APP)

当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以使用startForeground将service放到前台状态。这样在低内存时被kill的几率会低一些。

在onStartCommand方法内添加如下代码:

         Notification notification = new Notification(R.drawable.ic_launcher,getString(R.string.app_name), System.currentTimeMillis());

 PendingIntent pendingintent = PendingIntent.getActivity(this, 0,new Intent(this, AppMain.class), 0);
 notification.setLatestEventInfo(this, "uploadservice", "请保持程序在后台运行", pendingintent);
 startForeground(0x111, notification);

注意在onDestroy里还需要stopForeground(true),运行时在下拉列表会看到自己的APP在:

【结论】如果在极度极度低内存的压力下,该service还是会被kill掉,并且不一定会restart 

保持Service不被Kill掉的方法--双Service守护,代码如下:

AndroidManifest.xml:

复制代码
    <activity
android:name=".MainActivity"
android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
android:name="ServiceOne"
android:process=":remote" >
            <intent-filter>
                <action android:name="com.example.servicedemo.ServiceOne" />
            </intent-filter>
        </service>
        
        <service
android:name="ServiceTwo"
android:process=":remote" >
            <intent-filter>
                <action android:name="com.example.servicedemo.ServiceTwo" />
            </intent-filter>
        </service>
复制代码

MainActivity.java:

复制代码
package com.example.servicedemo;

import java.util.ArrayList;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;public class MainActivity extends Activity {

@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Intent serviceOne = new Intent();
serviceOne.setClass(MainActivity.this, ServiceOne.class);
startService(serviceOne);

Intent serviceTwo = new Intent();
serviceTwo.setClass(MainActivity.this, ServiceTwo.class);
startService(serviceTwo);
}public static boolean isServiceWorked(Context context, String serviceName) {
ActivityManager myManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ArrayList<RunningServiceInfo> runningService = (ArrayList<RunningServiceInfo>) 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;
}
}
复制代码

ServiceOne.java:

复制代码
package com.example.servicedemo;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;public class ServiceOne extends Service {
public final static String TAG = "com.example.servicedemo.ServiceOne";

@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand");

thread.start();return START_STICKY;
}

Thread thread = new Thread(new Runnable() {

@Overridepublic void run() {
Timer timer = new Timer();
TimerTask task = new TimerTask() {

@Overridepublic void run() {
Log.e(TAG, "ServiceOne Run: "+System.currentTimeMillis());
boolean b = MainActivity.isServiceWorked(ServiceOne.this, "com.example.servicedemo.ServiceTwo");if(!b) {
Intent service = new Intent(ServiceOne.this, ServiceTwo.class);
startService(service);
Log.e(TAG, "Start ServiceTwo");
}
}
};
timer.schedule(task, 0, 1000);
}
});

@Overridepublic IBinder onBind(Intent arg0) {return null;
}

}
复制代码

ServiceTwo.java:

复制代码
package com.example.servicedemo;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;public class ServiceTwo extends Service {public final static String TAG = "com.example.servicedemo.ServiceTwo";

@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand");

thread.start();return START_REDELIVER_INTENT;
}

Thread thread = new Thread(new Runnable() {

@Overridepublic void run() {
Timer timer = new Timer();
TimerTask task = new TimerTask() {

@Overridepublic void run() {
Log.e(TAG, "ServiceTwo Run: " + System.currentTimeMillis());
boolean b = MainActivity.isServiceWorked(ServiceTwo.this, "com.example.servicedemo.ServiceOne");if(!b) {
Intent service = new Intent(ServiceTwo.this, ServiceOne.class);
startService(service);
}
}
};
timer.schedule(task, 0, 1000);
}
});

@Overridepublic IBinder onBind(Intent arg0) {return null;
}

}
复制代码

第二部分:

做过android开发的人应该都知道应用会在系统资源匮乏的情况下被系统杀死!当后台的应用被系统回收之后,如何重新恢复它呢?网上对此问题有很多的讨论。这里先总结一下网上流传的各种解决方案,看看这些办法是不是真的可行。
1.提高优先级
这个办法对普通应用而言,应该只是降低了应用被杀死的概率,但是如果真的被系统回收了,还是无法让应用自动重新启动!
2.让service.onStartCommand返回START_STICKY
通过实验发现,如果在adb shell当中kill掉进程模拟应用被意外杀死的情况(或者用360手机卫士进行清理操作),如果服务的onStartCommand返回START_STICKY,在eclipse的进程管理器中会发现过一小会后被杀死的进程的确又会出现在任务管理器中,貌似这是一个可行的办法。但是如果在系统设置的App管理中选择强行关闭应用,这时候会发现即使onStartCommand返回了START_STICKY,应用还是没能重新启动起来!

3.android:persistent="true"
网上还提出了设置这个属性的办法,通过实验发现即使设置了这个属性,应用程序被kill之后还是不能重新启动起来的!

4.让应用成为系统应用
实验发现即使成为系统应用,被杀死之后也不能自动重新启动。但是如果对一个系统应用设置了persistent="true",情况就不一样了。实验表明对一个设置了persistent属性的系统应用,即使kill掉会立刻重启。一个设置了persistent="true"的系统应用,在android中具有core service优先级,这种优先级的应用对系统的low memory killer是免疫的!

OK,说了半天,只有core service优先级的应用才能保证在被意外杀死之后做到立刻满血复活。而普通应用要想成为系统应用就必须要用目标机器的签名文件进行签名,但这样又造成了应用无法保证兼容所有不同厂商的产品。那么该怎么办呢?这里就来说一说双进程守护。网上也有人提到过双进程守护的办法,但是很少能搜索到类似的源码!如果从进程管理器重观察会发现新浪微博或者360卫视都有两个相关的进程,其中一个就是守护进程,由此可以猜到这些商业级的软件也采用了双进程守护的办法。

什么是双进程守护呢?顾名思义就是两个进程互相监视对方,发现对方挂掉就立刻重启!不知道应该把这样的一对进程是叫做相依为命呢还是难兄难弟好呢,但总之,双进程守护的确是一个解决问题的办法!相信说到这里,很多人已经迫切的想知道如何实现双进程守护了。这篇文章就介绍一个用NDK来实现双进程保护的办法,不过首先说明一点,下面要介绍的方法中,会损失不少的效率,反应到现实中就是会使手机的耗电量变大!但是这篇文章仅仅是抛砖引玉,相信看完之后会有更多高人指点出更妙的实现办法。

需要了解些什么?
这篇文章中实现双进程保护的方法基本上是纯的NDK开发,或者说全部是用C++来实现的,需要双进程保护的程序,只需要在程序的任何地方调用一下JAVA接口即可。下面几个知识点是需要了解的:
1.Linux中多进程;
2.unix domain套接字实现跨进程通信;
3.linux的信号处理;
4.exec函数族的用法;

其实这些东西本身并不是多复杂的技术,只是我们把他们组合起来实现了一个双进程守护而已,没有想象中那么神秘!在正式贴出代码之前,先来说说几个实现双进程守护时的关键点:
1.父进程如何监视到子进程(监视进程)的死亡?
很简单,在linux中,子进程被终止时,会向父进程发送SIG_CHLD信号,于是我们可以安装信号处理函数,并在此信号处理函数中重新启动创建监视进程;
2.子进程(监视进程)如何监视到父进程死亡?
当父进程死亡以后,子进程就成为了孤儿进程由Init进程领养,于是我们可以在一个循环中读取子进程的父进程PID,当变为1就说明其父进程已经死亡,于是可以重启父进程。这里因为采用了循环,所以就引出了之前提到的耗电量的问题。
3.父子进程间的通信
有一种办法是父子进程间建立通信通道,然后通过监视此通道来感知对方的存在,这样不会存在之前提到的耗电量的问题,在本文的实现中,为了简单,还是采用了轮询父进程PID的办法,但是还是留出了父子进程的通信通道,虽然暂时没有用到,但可备不时之需!

腾讯的面试官问我:应用程序死了如何恢复?确实,双进程守护只能做到进程被杀死后重新启动,但是重启后如何恢复到之前的状态这是一个问题。因为进程被意外杀死的情况,onSaveInstance是来不及执行的,所以程序的状态没法保存!对于双进程守护来说,不知道是不是可以再父进程进入后台以后(onStop),把数据收集起来保存到子进程中,然后父进程重启以后从子进程中取出这些信息呢?这是一个办法,但是上面说明的双进程守护程序的实现中还做不到,因为父进程重启以后,子进程也挂掉重新建立了,要想实现优雅的恢复,还得在做出点改进才是!只能实时保存数据到数据库等。


Powered by MetInfo 5.3.17 ©2008-2022 www.metinfo.cn