如何讓我們的Android應用程式保活?

cxmscb發表於2016-08-14

一、執行緒 程式 應用

  1. 預設下,同一個應用的所有元件都執行在同一個程式中。當然也可以在manifest清單檔案中設定元件執行的程式。

    元件元素 activityservicereceiverprovider,都有一個process屬性可以指定元件執行在哪個程式中。這個屬性可以設定為每個元件執行在自己的程式中,或者設定程式同名與其他一些元件共享一個程式。

  2. Android會在某些時刻決定關閉一個程式,比如記憶體剩餘較小並且其他程式更迫切需要記憶體時,某些程式會被關閉時,且程式中的元件們都被銷燬.如果重新需要這些元件工作時,程式又會被建立出來。

  3. 系統不會為每個元件的例項分別建立執行緒。所有執行於一個程式的元件都在主執行緒(UI執行緒)中被例項化,並且系統對每個元件的呼叫都在這個執行緒中派發,且只能在UI執行緒中管理的你的介面。

  4. 可以 執行緒 程式 應用 打個比方:

    執行緒(Thread):流水線 、 程式(process): 車間 、 應用:工廠

  5. 為什麼使用多執行緒?

    執行緒如同一條流水線,當我們執行一些比如網路連線或資料庫請求這樣的耗時操作,會造成該執行緒阻塞。而我們又需要在主執行緒(UI執行緒)中對整個介面進行響應。所以為了不阻塞UI執行緒,提高應用程式的效能,需要使用多執行緒來操作。

  6. 為什麼使用多程式?

    在Android系統中,每個程式都有一個記憶體限制。如果一個應用可以多有個程式,那麼這個應用可以有更多的記憶體來執行,使我們的應用記憶體限制變大,優化程式的速度。

二、程式的等級(程式的生命週期)

一. 程式按照優先順序分為不同的等級:

  1. 前臺程式(Foreground process):

    A. 擁有使用者正在互動的 Activity( onResume()狀態)
    
    B. 正在與bound繫結服務互動的 Activity
    
    C. 正在“前臺”執行的 Service(startForeground()被呼叫)
    
    D. 生命週期函式正在被執行的 Service(onCreate()、onStart() 或 onDestroy())
    
    E. 正執行 onReceive() 方法的 BroadcastReceiver    
    
    (該程式優先順序別最高,殺死前臺程式需要使用者的響應。)
    
  2. 可見程式(Visible process):

    該程式並不是在最前端,並沒有得到焦點,但是我們卻能看到它們。

    A. 擁有不在前臺、但仍對使用者可見的 Activity(例如彈出一個對話方塊的Activity)
    
    B. 繫結到可見程式(或前臺程式)中的Activity 的 Service
    
  3. 服務程式(Service process):

    A. 正在執行的通過 startService() 啟動的,且不屬於上述兩個更高程式狀態的Service
    
  4. 後臺程式(Background process):

    A. 不可見狀態的Activity程式
    
  5. 空程式(Empty process):

    A. 沒有執行任何應用元件的程式,保留這個程式主要是為了快取的需要,待下次相關元件執行時直接從記憶體中獲取資料,縮短對資料獲取的時間。
    
  6. 程式等級判斷原則:當存在多種等級的程式狀態時,優先考慮優先順序較高的程式。

三、程式回收機制:Low Memory Killer

  1. 在Android系統,當使用者退出應用程式之後,應用程式的程式還是會存在於系統中,這樣方便於程式的再次啟動。

  2. 但隨著開啟的應用程式數量的增加,系統記憶體會變得不足,就需要殺掉一部分應用程式的程式以釋放記憶體空間。

  3. 至於是否需要殺死哪些程式需要被殺死,是通過Low Memory Killer機制來進行判定的。

  4. Low Memory Killer是通過程式的oom_adj與佔用記憶體的大小決定要殺死的程式,oom_adj越小越不容易被殺死。

  5. 有一組系統記憶體臨界值和與之一一對應的一組oom_adj值,當系統剩餘記憶體位於記憶體臨界值中的一個範圍內時,如果一個程式的oom_adj值大於/等於這個臨界值對應的oom_adj值,該程式就會被殺掉。

四、防止程式被殺

  1. 輕量化程式:按照Low Memory Killer的殺死程式的規則,應儘量讓Service在後臺做較少的事情,且及時釋放記憶體資源。

  2. 利用service提升程式許可權: 呼叫 startForeground方法將service置為“前臺程式”,不過這樣我們需要傳送一個Notification通知。

    如何取消通知:
    
        1. 在Android 4.3之前,可以通過構造一個空的Notification使通知欄不會顯示我們傳送的Notification。
    
        2. 在Android 4.3之後,谷歌不再允許構造空的Notification。但有一個奇葩的方法: 
           可以將兩個同程式的Service都通過startForeground設定為前臺程式,但它們使用的是同一個ID的Notification通知,這樣只會產生一個Notification。然後其中一個Service取消前臺通知,那麼該通知會被關閉。這樣剩下的Service還是一個前臺Service,且通知欄沒有通知。
    
  3. 在application標籤中加入 android:persistent=“true”

    在androidmanifest.xml中的application標籤中加入android:persistent="true" 屬性後能夠達到保證該應用程式所在程式不會被LMK殺死,異常出現後也可以自啟。但前提是應用程式必須是系統應用,即應用程式不能採用通常的安裝方式。必須將應用程式的apk包直接放到/system/app目錄下。而且必須重啟系統後才能生效。
    

五、被殺後重啟

  1. 利用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物件會依次被髮送。這適用於那些應該立即恢復正在執行的工作的服務,如下載檔案。
    

    雖然在這種情況可以讓Service被系統重啟的,但不能立即被重啟。而且在某些定製ROM上失效。

  2. 雙程式守護
    設計AB兩個不同程式,A程式裡面輪詢檢查B程式是否存活,沒存活的話將其拉起,同樣B程式裡面輪詢檢查A程式是否存活,沒存活的話也將其拉起,而後臺邏輯可以隨便放在某個程式裡執行即可。

    <service
             android:name=".services.FirService"
             android:process=":fir" />
    <service
            android:name=".services.SecService"
            android:process=":sec" />
    

    使用兩個程式分別裝載兩個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<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;
        }
    
    
    }
    
  3. 利用靜態廣播接收器重啟程式:
    使用靜態註冊的廣播接收器BroadcastReceiver來監聽一些系統廣播/其他應用發出的廣播在onReceive中重啟程式。

    無法接受到系統?:
    
    1. 在Android 3.1之後,處於stopped狀態的應用無法接收到系統廣播。
    2. 如果一個應用在安裝後從來沒有啟動過,或者已經被使用者強制停止了,那麼這個應用就處於停止狀態(stopped state)。
    3. 如果想使處於stopped狀態的應用也接收到廣播,需要在intent中增加FLAG_INCLUDE_STOPPED_PACKAGES這個Flag。要注意的是,使用者無法自定義系統廣播。
    4. 有些廣播只有靜態接收器可以接收得到。
    5. 深度定製ROM使應用重回STOPPED狀態
    
  4. 與系統Service捆綁 : NotificationListenerService

    NotificationListenerService是通過系統調起的服務,當有應用發起通知的時候,系統會將通知的動作和資訊回撥給NotificationListenerService。

    <service  android:name=".services.NotificationService "
        android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
        android:process=":service">
    
        <intent-filter>
            <action android:name="android.service.notification.NotificationListenerService" />
        </intent-filter>
    
    </service>
    
    -------------------
    
    public class NotificationService extends NotificationListenerService {
    
      @Override
      public void onNotificationPosted(StatusBarNotification sbn) {
    
    
      }
    
      @Override
      public void onNotificationRemoved(StatusBarNotification sbn) {
    
    
      }
    
    }
    

    當系統發現某應用產生通知或者使用者刪除某通知,都會回撥該服務的上述這兩個函式,函式的引數StatusBarNotification包含著該通知的具體資訊。.

  5. 其他:全家桶/SDK喚醒、手機廠商的白名單定製

六、Android應用程式保活的權衡

  1. 程式保活要回到使用者體驗。有的程式保活不能做到效能優化,反而在不斷重啟費電、亦或如幽靈般重複出現在使用者面前。在某些應用場景裡,有的程式保活起到了應有的作用,比如音樂播放時的前臺Service…..

  2. 流氓的程式保活只會搞壞Android 生態環境,傷害 Android 平臺開發者等人的利益。當然,Google也不會讓這麼流氓方式胡作非為。


參考:

http://blog.csdn.net/aigestudio/article/details/51348408
http://www.jianshu.com/p/63aafe3c12af
http://www.cnblogs.com/angeldevil/archive/2013/05/21/3090872.html

相關文章