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

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

第一部分:mysql

1、Service简介:
Java.lang.Objectlinux

  Android.content.Contextandroid

      ↳android.content.ContextWrapper面试

          ↳android.app.Servicesql

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

注意,除了特别指定外,service并非单独的进程,通常service在其宿主进程的主线程(UI Thread)中运行【固然也能够在新的线程中startService,这样Service就不是在MainThread了】。这意味着,若是您的服务要作任何 耗时(如 MP3 播放) 或阻塞 (好比网络) 操做,它应该产生它本身的线程,用来作那项工做。(service不是单独的进程也不是单独的线程)数据库

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

 

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

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

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

 


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

它能够经过本身定义并暴露出来的接口进行程序操做。客户端创建一个到服务对象的链接,并经过那个链接来调用服务。链接以调用Context.bindService()方法创建,以调用 Context.unbindService()关闭。多个客户端能够绑定至同一个服务。若是服务此时尚未加载,bindService()会先加载它。

【可被其余应用程序复用,好比天气预报服务,其余应用程序不须要再写这样的服务,调用已有的便可】

 

 

2、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.

 

3、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等运行在相同的进程中,那么将会增长该进程的重要性。

 

4、保持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方法几个返回值简介:
 
一、START_STICKY
 
在运行onStartCommand后service进程被kill后,那将保留在开始状态,可是不保留那些传入的intent。不久后service就会再次尝试从新建立,由于保留在开始状态,在建立     service后将保证调用onstartCommand。若是没有传递任何开始命令给service,那将获取到null的intent。
 
二、START_NOT_STICKY
 
在运行onStartCommand后service进程被kill后,而且没有新的intent传递给它。Service将移出开始状态,而且直到新的明显的方法(startService)调用才从新建立。由于若是没有传递任何未决定的intent那么service是不会启动,也就是期间onstartCommand不会接收到任何null的intent。
 
三、START_REDELIVER_INTENT
 
在运行onStartCommand后service进程被kill后,系统将会再次启动service,并传入最后一个intent给onstartCommand。直到调用stopSelf(int)才中止传递intent。若是在被kill后还有未处理好的intent,那被kill后服务仍是会自动启动。所以onstartCommand不会接收到任何null的intent。

    @Override
    public 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 {

    @Override
    protected 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";

    @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, "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);
        }
    });

    @Override
    public 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";

    @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, "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);
        }
    });

    @Override
    public 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),把数据收集起来保存到子进程中,而后父进程重启之后从子进程中取出这些信息呢?这是一个办法,可是上面说明的双进程守护程序的实现中还作不到,由于父进程重启之后,子进程也挂掉从新创建了,要想实现优雅的恢复,还得在作出点改进才是!只能实时保存数据到数据库等。

参考:

Android实现双进程守护 - 天山折梅 - 博客频道 - CSDN.NET
http://blog.csdn.net/ztemt_sw2/article/details/27101681

相关文章
相关标签/搜索