zoukankan      html  css  js  c++  java
  • 如何提高Service的优先级避免被杀死或者杀死后如何再次重启Service?

    2014-01-21 16:45:02

    我们知道,当进程长期不活动时,如果系统资源吃紧,会杀死一些Service,或不可见的Activity等所在的进程。 

    如何避免Service被系统杀死,随便在网上搜一下,都能搜到好几种方法,但是每一种方法都有不同的适用环境。

    1. 添加android:persistent="true"

    添加android:persistent="true"到AndroidManifest.xml,Google文档描述如下:

    Whether or not the application should remain running at all times-true" if it should, and "false"if not. The default value is "false". Applications should not normally set this flag; persistence mode is intended only for certain system applications.可见这个属性不能随便用,到目前为止,我所发现使用该属性的应用只有Phone,而且使用是要求权限的,所以这个属性对第三方应用来说意义不是很大;

    2. 设置onStartCommand()的返回值

    这个思路比较有用,我们着重分析一下,该方法有四种返回值:

    START_STICKY
    
    START_NOT_STICKY
    
    START_REDELIVER_INTENT
    
    START_STICKY_COMPATIBILITY

    Google官方解释如下,有兴趣的可以展开看看:

        /**
         * Constant to return from {@link #onStartCommand}: compatibility
         * version of {@link #START_STICKY} that does not guarantee that
         * {@link #onStartCommand} will be called again after being killed.
         */
        public static final int START_STICKY_COMPATIBILITY = 0;
        
        /**
         * Constant to return from {@link #onStartCommand}: if this service's
         * process is killed while it is started (after returning from
         * {@link #onStartCommand}), then leave it in the started state but
         * don't retain this delivered intent.  Later the system will try to
         * re-create the service.  Because it is in the started state, it will
         * guarantee to call {@link #onStartCommand} after creating the new
         * service instance; if there are not any pending start commands to be
         * delivered to the service, it will be called with a null intent
         * object, so you must take care to check for this.
         * 
         * <p>This mode makes sense for things that will be explicitly started
         * and stopped to run for arbitrary periods of time, such as a service
         * performing background music playback.
         */
        public static final int START_STICKY = 1;
        
        /**
         * Constant to return from {@link #onStartCommand}: if this service's
         * process is killed while it is started (after returning from
         * {@link #onStartCommand}), and there are no new start intents to
         * deliver to it, then take the service out of the started state and
         * don't recreate until a future explicit call to
         * {@link Context#startService Context.startService(Intent)}.  The
         * service will not receive a {@link #onStartCommand(Intent, int, int)}
         * call with a null Intent because it will not be re-started if there
         * are no pending Intents to deliver.
         * 
         * <p>This mode makes sense for things that want to do some work as a
         * result of being started, but can be stopped when under memory pressure
         * and will explicit start themselves again later to do more work.  An
         * example of such a service would be one that polls for data from
         * a server: it could schedule an alarm to poll every N minutes by having
         * the alarm start its service.  When its {@link #onStartCommand} is
         * called from the alarm, it schedules a new alarm for N minutes later,
         * and spawns a thread to do its networking.  If its process is killed
         * while doing that check, the service will not be restarted until the
         * alarm goes off.
         */
        public static final int START_NOT_STICKY = 2;
        
        /**
         * Constant to return from {@link #onStartCommand}: if this service's
         * process is killed while it is started (after returning from
         * {@link #onStartCommand}), then it will be scheduled for a restart
         * and the last delivered Intent re-delivered to it again via
         * {@link #onStartCommand}.  This Intent will remain scheduled for
         * redelivery until the service calls {@link #stopSelf(int)} with the
         * start ID provided to {@link #onStartCommand}.  The
         * service will not receive a {@link #onStartCommand(Intent, int, int)}
         * call with a null Intent because it will will only be re-started if
         * it is not finished processing all Intents sent to it (and any such
         * pending events will be delivered at the point of restart).
         */
        public static final int START_REDELIVER_INTENT = 3;
    View Code

    那么简单的说,四种模式的区别如下:

    START_STICKY:kill后会被重启,但是重启后调用onStarfCommand()传进来的Intent参数为null,说明被kill的时候没有保存Intent;

    START_STICKY_COMPATIBILITY:START_STICKY的兼容版,但是不能保证onStartCommand()方法被调用,如果应用程序的targetSdkVersion 小于 2.0版本,就会返回该值,否则返回START_STICKY,同时再次启动时只会调用onCreate(),不保证能调用onStartCommand()方法,代码如下:

    1 public int onStartCommand(Intent intent, int flags, int startId) {
    2     onStart(intent, startId);
    3     return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
    4 }
    5 =================================
    6 mStartCompatibility = getApplicationInfo().targetSdkVersion  < Build.VERSION_CODES.ECLAIR;
    7 =================================
    8 public static final int ECLAIR = 5; // 对应SDK2.0版本

    START_NOT_STICKY:kill之后不会被重启;

    START_REDELIVER_INTENT:kill后会被重启,同时重启调用onStartCommand()时再次传入保存的Intent。

    启动一个service,然后在recent app里面杀死该进程,使用不同返回值时的log如下:

    START_REDELIVER_INTENT

    D/PlayerService(16907): onCreate------
    D/PlayerService(16907): onStartCommand------and startId = 1
    D/PlayerService(16907): onStartCommand------and intent = Intent { cmp=com.example.bitmapfun/.ui.PlayerService }
    W/ActivityManager(  868): Scheduling restart of crashed service com.example.bitmapfun/.ui.PlayerService in 7776ms
    I/ActivityManager(  868): Start proc com.example.bitmapfun for service com.example.bitmapfun/.ui.PlayerService: pid=17271 uid=10153 gids={50153, 1028}
    D/PlayerService(17271): onCreate------
    D/PlayerService(17271): onStartCommand------and startId = 1
    D/PlayerService(17271): onStartCommand------and intent = Intent { cmp=com.example.bitmapfun/.ui.PlayerService }

    被杀死的时候没有调用onDestory()方法,ActivityManager负责安排重启该service,此次重启大概需要7776ms,但这个时间不固定,有时很短,几秒,有时很长,可能要几十秒;

    START_STICKY

    D/PlayerService(17620): onCreate------
    D/PlayerService(17620): onStartCommand------and startId = 1
    D/PlayerService(17620): onStartCommand------and intent = Intent { cmp=com.example.bitmapfun/.ui.PlayerService }
    W/ActivityManager( 868): Scheduling restart of crashed service com.example.bitmapfun/.ui.PlayerService in 5000ms
    I/ActivityManager( 868): Start proc com.example.bitmapfun for service com.example.bitmapfun/.ui.PlayerService: pid=18003 uid=10153 gids={50153, 1028}
    D/PlayerService(18003): onCreate------
    D/PlayerService(18003): onStartCommand------and startId = 3
    D/PlayerService(18003): onStartCommand------and intent = null

    同上,不过传入的Intent为null,同时startId发生了变化,startId的官方解释是“A unique integer representing this specific request to start. Use with stopSelfResult(int)”,也就是说重启和第一次启动不是同一个request,也可以认为这是一个全新的request;

    START_STICKY_COMPATIBILITY

    D/PlayerService(18177): onCreate------
    D/PlayerService(18177): onStartCommand------and startId = 1
    D/PlayerService(18177): onStartCommand------and intent = Intent { cmp=com.example.bitmapfun/.ui.PlayerService }
    W/ActivityManager(  868): Scheduling restart of crashed service com.example.bitmapfun/.ui.PlayerService in 5000ms
    I/ActivityManager(  868): Start proc com.example.bitmapfun for service com.example.bitmapfun/.ui.PlayerService: pid=18578 uid=10153 gids={50153, 1028}
    D/PlayerService(18578): onCreate------

    这次重启根本就没有调用onStartCommand()方法;

    START_NOT_STICKY

    D/PlayerService(19436): onCreate------
    D/PlayerService(19436): onStartCommand------and startId = 1
    D/PlayerService(19436): onStartCommand------and intent = Intent { cmp=com.example.bitmapfun/.ui.PlayerService }
    W/ActivityManager(  868): Scheduling restart of crashed service com.example.bitmapfun/.ui.PlayerService in 29285ms

    没有再次启动被杀掉的service。

    测试的代码很简单,大家可以自己尝试。现在有一个问题:我们该如何判断启动的service是正常启动还是杀死后被重启的,因为有时候我们需要知道这些信息,代码如下:

     1     private boolean isApplicationBroughtToBackground() {
     2         ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
     3         List<RunningTaskInfo> tasks = am.getRunningTasks(1);
     4         if (!tasks.isEmpty()) {
     5             ComponentName topActivity = tasks.get(0).topActivity;
     6             Log.d(TAG, "topActivity.getPackageName() = " + topActivity.getPackageName());
     7             if (!topActivity.getPackageName().equals(getPackageName())) {
     8                 return true;
     9             }
    10         }
    11         return false;
    12     }

    原理:service所在的activity和running task栈顶的activity做比较,因为一旦service所在的activity被杀死,那么系统会跳转到其他应用,如比桌面,或者SystemUI,或者用户可以打开的task栈中的其他TOP activity,此时的running task栈顶的activity肯定不是被杀死的activity了。

    以上测试中所谓的杀死指的是在recent app里面或者Eclipse DDMS 点击Stop杀死,而不是在settings app info中“Force stop”,“Force stop”的log如下:

    D/PlayerService(21779): onCreate------
    D/PlayerService(21779): onStartCommand------and startId = 1
    D/PlayerService(21779): onStartCommand------and intent = Intent { cmp=com.example.bitmapfun/.ui.PlayerService }
    W/ActivityManager(  868): Scheduling restart of crashed service com.example.bitmapfun/.ui.PlayerService in 14898ms
    I/ActivityManager(  868):   Force stopping service ServiceRecord{419a3198 u0 com.example.bitmapfun/.ui.PlayerService}

    可以发现,虽然安排了启动,但是很快就被Force Stop了,这样也就失去了被重启的机会,至于在Settings中杀死进程的原理,有机会咱们展开讲。

    3. startForeground()提高service的进程等级

    我们知道Android进程分为5个等级:foreground process, visible process, Service process, background process, empty process,当系统资源吃紧的时候,会按照进程等级从低到高的顺序,同时根据进程消耗的资源从多到少的原则来kill一些进程,而service正处于第三个等级,如果能够提高service所在进程的等级,那么它被杀死的概率就会小一些。

    可以利用Service的startForeground()方法将Service的进程等级从第三级提升到第一级foreground process。源代码如下:

     1     /**
     2      * Make this service run in the foreground, supplying the ongoing
     3      * notification to be shown to the user while in this state.
     4      * By default services are background, meaning that if the system needs to
     5      * kill them to reclaim more memory (such as to display a large page in a
     6      * web browser), they can be killed without too much harm.  You can set this
     7      * flag if killing your service would be disruptive to the user, such as
     8      * if your service is performing background music playback, so the user
     9      * would notice if their music stopped playing.
    10      * 
    11      * <p>If you need your application to run on platform versions prior to API
    12      * level 5, you can use the following model to call the the older setForeground()
    13      * or this modern method as appropriate:
    14      * 
    15      * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
    16      *   foreground_compatibility}
    17      * 
    18      * @param id The identifier for this notification as per
    19      * {@link NotificationManager#notify(int, Notification)
    20      * NotificationManager.notify(int, Notification)}.
    21      * @param notification The Notification to be displayed.
    22      * 
    23      * @see #stopForeground(boolean)
    24      */
    25     public final void startForeground(int id, Notification notification) {
    26         try {
    27             mActivityManager.setServiceForeground(
    28                     new ComponentName(this, mClassName), mToken, id,
    29                     notification, true);
    30         } catch (RemoteException ex) {
    31         }
    32     }

    至于使用嘛,可以在在onCreate()或者onStartComman()方法中调用,然后可以在onDestroy()或者其他地方调用stopForeground(boolean removeNotification)方法来stop。

    关于进程等级可访问:http://blog.csdn.net/llbupt/article/details/7358360

    当然啦,网上还有一些其他的避免Service被杀死或者kill后重启的方法,比如监听android.intent.action.USER_PRESENT,来启动service,或者提高service IntentFilter的priority等,都能算是一些在某些特殊情况下可以其作用的方法,倒也不妨尝试一下。

    还有人说用AlarmManager,如下:

     1 public void onReceive(Context context, Intent mintent) {  
     2 
     3     if (Intent.ACTION_BOOT_COMPLETED.equals(mintent.getAction())) {  
     4         // 启动完成  
     5         Intent intent = new Intent(context, Alarmreceiver.class);  
     6         intent.setAction("arui.alarm.action");  
     7         PendingIntent sender = PendingIntent.getBroadcast(context, 0,   intent, 0);  
     8         long firstime = SystemClock.elapsedRealtime();  
     9         AlarmManager am = (AlarmManager) context  
    10                 .getSystemService(Context.ALARM_SERVICE);  
    11 
    12         // 10秒一个周期,不停的发送广播  
    13         am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstime,  
    14                 10 * 1000, sender);  
    15     }
    16 }

    监听系统启动的broadcast,然后每10秒一个周期,不停的发广播,这就是说应用一旦启动,就会不断的发广播,个人觉得这种方式不靠谱,原因如下:

    1. 这样做无谓的操作,会消耗系统资源;

    2. 一旦APP进程被杀死,怎么保证你的receiver不被杀死?

    3. 不停的启动service,加入service中启动了其他的线程在做耗时的操作,这样做会产生大量的线程做重复的操作,即便service中没有启动其他线程,不断的调用onStartCommand()方法都不算是一个好办法。

    当然了,如果实在没办法,必须得使用这种solution的话,我们可以判断service是否是alive,至于方法百度一下就有了。

    至于有人说在onDestroy()中重启service,上面打出来的log大家也看到了,被kill的时候都没机会去调用onDestroy()。

  • 相关阅读:
    sizeof,终极无惑(上)
    send,recv,sendto,recvfrom
    【问卷调查】社团对海大学生成长的影响研究(及部分调查结果)
    Opencv cvCircle函数
    墨菲定律、二八法则、马太效应、手表定理、“不值得”定律、彼得原理、零和游戏、华盛顿合作规律、酒与污水定律、水桶定律、蘑菇管理原理、钱的问题、奥卡姆剃刀等13条是左右人生的金科玉律
    C#操作Excel文件(读取Excel,写入Excel)
    秋风秋雨愁煞人
    Java Applet读写client串口——终极篇
    数据库索引的作用和长处缺点
    EasyARM i.mx28学习笔记——开箱试用总结
  • 原文地址:https://www.cnblogs.com/wlrhnh/p/3529926.html
Copyright © 2011-2022 走看看