1. 程式人生 > >Android 雙程序守護

Android 雙程序守護

前言

  最近有在專案中用到高德的定位SDK,功能是每隔一定的時間獲取一次使用者的地理位置,採取的方案是在後臺開啟一個 Service,監聽高德地圖的位置變化。
  該功能在使用者手機螢幕亮時完美實現,但是當螢幕被關閉的時候,位置資訊卻無法被獲取了,經過原因的排查,發現是由於在使用者手機息屏後,後臺的 Service 被系統清除,所以功能無法起作用,也就是所謂的程序被殺了。
  殺程序,一方面是因為手機記憶體不足,另一方面其實是 Google 從使用者的方面考慮,把一些常駐後臺的程式通過一定的演算法進行管理,將那些過度消耗系統資源的流氓軟體殺除,保證手機的效能和續航。但是有的軟體,像定位這類的必須要保持後臺的執行,如何才能避免被系統殺掉呢。其實避免被殺程序很難做到,除非是像微信、QQ、支付寶這類系統廠商認可的軟體被官方加入白名單可以避免被殺程序。那其他的小軟體怎麼辦,我們可以另闢蹊徑,無法避免被殺程序,那就讓我們的軟體在被殺程序後,能自動重啟。
  我這裡介紹一下雙程序守護的方法,來實現程序被殺後的拉起。

雙程序守護

  雙程序守護的思想就是,兩個程序共同執行,如果有其中一個程序被殺,那麼另一個程序就會將被殺的程序重新拉起,相互保護,在一定的意義上,維持程序的不斷執行。
  雙程序守護的兩個程序,一個程序用於我們所需的後臺操作,且叫它本地程序,另一個程序只負責監聽著本地程序的狀態,在本地程序被殺的時候拉起,於此同時本地程序也在監聽著這個程序,準備在它被殺時拉起,我們將這個程序稱為遠端程序。
  由於在 Android 中,兩個程序之間無法直接互動,所以我們這裡還要用到 AIDL (Android interface definition Language ),進行兩個程序間的互動。

程式碼實現

  先來看一下demo程式碼結構,結構很簡單,我這裡建立了一個 Activity 作為介面,以及兩個 Service ,一個是後臺操作的 本地Service,另一個是守護程序的 遠端Service,還有一個 AIDL檔案用作程序間互動用。
專案結構
  Activity 的定義很簡單,就幾個按鈕,控制 Service 的狀態,我這邊定義了三個按鈕,一個是開啟後臺服務,另外兩個分別是關閉本地Service和遠端的Service。

/**
 * @author chaochaowu
 */
public class GuardActivity extends AppCompatActivity
{
@BindView(R.id.button) Button button; @BindView(R.id.button2) Button button2; @BindView(R.id.button3) Button button3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getSupportActionBar().hide(); setContentView(R.layout.activity_guard); ButterKnife.bind(this); } @OnClick({R.id.button, R.id.button2, R.id.button3}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.button: startService(new Intent(this, LocalService.class)); break; case R.id.button2: stopService(new Intent(this, LocalService.class)); break; case R.id.button3: stopService(new Intent(this, RemoteService.class)); break; default: break; } } }

  可以看一下介面。
主介面

  AIDL檔案可以根據業務需要新增介面。

/**
 * @author chaochaowu
 */
interface IMyAidlInterface {
    String getServiceName();
}

  重點是在兩個 Service 上。
  在定義Service時,需要在 AndroidManifest 中宣告一下 遠端Service 的 process 屬性,保證 本地Service 和 遠端Service 兩者跑在不同的程序上,如果跑在同一個程序上,該程序被殺,那就什麼都沒了,就沒有了雙程序守護的說法了。

<service
    android:name=".guard.LocalService"
    android:enabled="true"
    android:exported="true" />
<service
    android:name=".guard.RemoteService"
    android:enabled="true"
    android:exported="true"
    android:process=":RemoteProcess"/>

  先來看 LocalService 的程式碼,重點關注 onStartCommand 方法 和 ServiceConnection 中重寫的方法。onStartCommand 方法是在 Service 啟動後被呼叫,在 LocalService 被啟動後,我們將 RemoteService 進行了啟動,並將 LocalService 和 RemoteService 兩者綁定了起來(因為遠端Service 對於使用者來說是不可見的,相對於我們實際工作的程序也是獨立的,它的作用僅僅是守護執行緒,所以說 RemoteService 僅與 LocalService 有關係,應該只能由 LocalService 將它啟動)。
  啟動並繫結之後,我們需要重寫 ServiceConnection 中的方法,監聽兩者之間的繫結關係,關鍵的是對兩者繫結關係斷開時的監聽。
  當其中一個程序被殺掉時,兩者的繫結關係就會被斷開,觸發方法 onServiceDisconnected ,所以,我們要在斷開時,進行程序拉起的操作,重寫 onServiceDisconnected 方法,在方法中將另外一個 Service 重新啟動,並將兩者重新繫結。

/**
 * @author chaochaowu
 */
public class LocalService extends Service {

    private MyBinder mBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            try {
                Log.i("LocalService", "connected with " + iMyAidlInterface.getServiceName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Toast.makeText(LocalService.this,"連結斷開,重新啟動 RemoteService",Toast.LENGTH_LONG).show();
            startService(new Intent(LocalService.this,RemoteService.class));
            bindService(new Intent(LocalService.this,RemoteService.class),connection, Context.BIND_IMPORTANT);
        }
    };

    public LocalService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this,"LocalService 啟動",Toast.LENGTH_LONG).show();
        startService(new Intent(LocalService.this,RemoteService.class));
        bindService(new Intent(this,RemoteService.class),connection, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        mBinder = new MyBinder();
        return mBinder;
    }

    private class MyBinder extends IMyAidlInterface.Stub{

        @Override
        public String getServiceName() throws RemoteException {
            return LocalService.class.getName();
        }

    }

}

  在另外一個 RemoteService 中也一樣,在與 LocalService 斷開連結的時候,由於監聽到繫結的斷開,說明 RemoteService 還存活著,LocalService 被殺程序,所以要將 LocalService 進行拉起,並重新繫結。方法寫在 onServiceDisconnected 中。

/**
 * @author chaochaowu
 */
public class RemoteService extends Service {

    private MyBinder mBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            try {
                Log.i("RemoteService", "connected with " + iMyAidlInterface.getServiceName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Toast.makeText(RemoteService.this,"連結斷開,重新啟動 LocalService",Toast.LENGTH_LONG).show();
            startService(new Intent(RemoteService.this,LocalService.class));
            bindService(new Intent(RemoteService.this,LocalService.class),connection, Context.BIND_IMPORTANT);
        }
    };

    public RemoteService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this,"RemoteService 啟動",Toast.LENGTH_LONG).show();
        bindService(new Intent(this,LocalService.class),connection,Context.BIND_IMPORTANT);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        mBinder = new MyBinder();
        return mBinder;
    }

    private class MyBinder extends IMyAidlInterface.Stub{

        @Override
        public String getServiceName() throws RemoteException {
            return RemoteService.class.getName();
        }

    }
}

執行效果

  啟動 Activity 點選開啟 LocalService 啟動本地服務,提示中可以看到, LocalService 啟動後 RemotService 守護執行緒也被啟動。此時,兩者已經繫結在了一起。
開啟服務
  點選關閉 LocalService 模擬本地程序被殺,Toast 提示連結斷開,並嘗試重新啟動 LocalService,第二個Toast 提示 LocalService 被重新拉起。
關閉本地服務
  點選關閉 RemoteService 模擬遠端程序被殺,Toast 提示連結斷開,並嘗試重新啟動 RemoteService ,第二個Toast 提示 RemoteService 被重新拉起。
關閉遠端服務
  可以發現,無論我們怎麼殺程序,程序都會被重新拉起,這就達到了 Service 保活,雙程序相互守護的目的。

總結

  在開發的過程中總是有些無法避免的麻煩,但是方法總比困難多,耐心研究研究就行了。關於程序的保活,其實是沒有辦法的辦法,我們應該儘量避免將程序常駐後臺,如果真的需要,在完成後臺工作後,也要及時將他們銷燬。否則後臺程序無端地消耗系統資源,使用者又不知道,咱們的軟體就也就成了流氓軟體。開發人員應該有自己的良心,嗯。

以上便是 Android 中的雙程序守護。