如何提高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()。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM