1. 程式人生 > >Android四大元件-Service

Android四大元件-Service

概念:

android 四大元件之一,提供在後臺執行的服務,屬於計算型元件。

特點:

在後臺執行,無使用者介面,生命週期長。

啟動方式

startservice:

不與Activity繫結,啟動之後會無限期的執行下去,除非遇到記憶體低情況被回收,需要呼叫stopService或stopSelf才會停止。

  • **生命週期 :**onCreate(只執行一次)-onStartCommand-服務執行-onDestory()
    • onCreate 只調用一次,onStartCommand可以呼叫多次(呼叫的次數是startService的次數),其他方法只能呼叫一次。
    • onStartCommand必須返回一個整數= 描述系統在殺死服務後如何繼續執行。
      • START_STICK:重建服務,呼叫onStartCommand,但不會傳入上次未傳送完的intent,而是使用null intent (所以需要檢查)。除非還有啟動服務的intent未傳送完會繼續傳送。適用於媒體播放器,不需要執行命令但需要一直執行並隨時待命。
      • START_NOT_STICK:不會重建服務,除非還存在未傳送的intent。但服務不再必需的時候,這個是避免重啟服務的最安全的方法。
      • START_REDELIVER_INTENT:重建服務,並且任何未傳入的intent的都會被依次送入。 適用於需要立即回覆工作的活躍服務,比如下載檔案。
  • **操作:**建立一個Service繼承自service,在onStartCommand操作。在context中通過intent方式是啟動服務。
  • 只能開啟或停止服務,無法操作服務。
  • 呼叫者退出後,服務仍然存在。
bindservice

與Activity繫結,繫結之後在後臺執行,除非呼叫unBindService或繫結的Context被銷燬。

  • **生命週期:**onCreate(只執行一次)-onBind-onUnbind-onDestory ,如果先呼叫了startservice,已經onCreate,也不會再次呼叫。

  • **操作:**建立一個Binder繼承Binder,通過onBind返回Binder物件,在context中通過serviceConnection取到binder物件並呼叫bindner的方法,bindService中傳入ServiceConnection建立連線。

    //在service中自定義Binder
     class MyBinder extends Binder{
            public void startDownload(){
                Log.d(TAG, "startDownload: ");
            }
        }
        
        //在onBind方法中返回Binder
         private MyBinder myBinder = new MyBinder();
         @Override
        public IBinder onBind(Intent intent) {
            Log.d(TAG, "onBind: ");
            // TODO: Return the communication channel to the service.
    
            return myBinder;
        }
    
    //在Activity 中建立serviceconnection
      private  ServiceConnection connection = new ServiceConnection() {
    
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
               myBinder = (MyService.MyBinder) service;
               myBinder.startDownload();
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
        //繫結服務
           bindService(new Intent(MainActivity.this,MyService.class),connection,BIND_AUTO_CREATE);
    
  • 除了可以開啟或停止服務,還可以獲得Service物件,對Service進行操作。

  • 呼叫者退出後Service隨著呼叫者退出而銷燬。

服務的銷燬方式:
  • 如果是startservice啟動的呼叫stop service就可以銷燬,或者在記憶體極低的情況下,被回收銷燬。
  • 如果是通過bindservice啟動的服務呼叫unbindservice 銷燬服務;但是同時startservice和bindservice需要unbindservice及再次呼叫stopservice才會銷燬服務,即當service與activity繫結的情況下,service不再繫結且service處於靜止狀態時。 另外當service的呼叫者推出時也會銷燬服務。
前臺服務:

如果需要service一直保持執行狀態(service保活),則可以考慮前臺service。效果類似於通知。在service的onStartCommand方法中修改.(需要新增FOREGROUND_SERVICE許可權)

        //8.0 適配通知欄
        if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
            NotificationChannel channel = new NotificationChannel("service","test", NotificationManager.IMPORTANCE_DEFAULT);
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            assert manager != null;
            manager.createNotificationChannel(channel);
            NotificationCompat.Builder service = new NotificationCompat.Builder(this, "service");
            service.setContentTitle("執行前臺服務的通知");
            service.setContentText("執行前臺服務的內容");
            service.setSmallIcon(R.mipmap.ic_launcher);
            notification = service.getNotification();
        }else {
            Intent intent = new Intent(this,MainActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
            Notification.Builder builder = new Notification.Builder(this);
            builder.setContentTitle("執行前臺服務的通知");
            builder.setContentText("執行前臺服務的內容");
            builder.setSmallIcon(R.mipmap.ic_launcher);
            builder.setContentIntent(pendingIntent);
            notification = builder.getNotification();
        }
		//設定為前臺服務:引數1:唯一的通知標識。引數二:通知
        startForeground(1,notification);

service與Thread的區別

兩者無聯絡。雖然都是在後臺執行一下耗時的操作,但是service是執行在主執行緒的,Thread是開啟的子執行緒執行。

遠端服務跨程序通訊

遠端服務的建立

在註冊服務的地方新增屬性:

android:process=":remote"

遠端服務是 執行在另一個程序。此時服務需要與Activity繫結的話需要使用AIDL

同一個工程下使用:

  • 建立一個AIDL檔案,在此檔案中定義方法,構建專案會自動生成一個介面檔案。改檔案是IBinder的子類。

  • 在service檔案獲取該子類,並在onBinder方法返回。(程序S)

      @Override
        public IBinder onBind(Intent intent) {
            Log.d(TAG, "onBind: ");
            // TODO: Return the communication channel to the service.
            return mBinder;
        }
        IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
            @Override
            public String toUppercase(String aString) throws RemoteException {
                if (!TextUtils.isEmpty(aString)){
                   return aString.toUpperCase();
                }
                return null;
            }
        };
    
  • 在activity中(程序C)修改serviceconnection

  • private IMyAidlInterface iMyAidlInterface;  //是IBinder的子類 ,AIDL ,mainactivity 和myservice 是不同的程序,此時實現了跨程序通訊
      private  ServiceConnection connection = new ServiceConnection() {
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              iMyAidlInterface  =  IMyAidlInterface.Stub.asInterface(service);
              try {
                  String hello_world = iMyAidlInterface.toUppercase("hello world");
                  Log.d(TAG, "onServiceConnected: "+hello_world);
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
          }
      
          @Override
          public void onServiceDisconnected(ComponentName name) {
      
          }
      };
    

    因此實現了跨程序通訊。

    不同工程下使用:

    把AIDL檔案和Activity的內容移至另一個工程即可。使用隱式跳轉。在service中新增過濾器,在

Intent intent = new Intent(定義的action);
bindService(intent, connection, BIND_AUTO_CREATE);

Binder機制

概念:

Binder實現IBinder介面,Android 中實現跨程序通訊的機制。

跨程序通訊的原因:

為了資料的獨立性和安全性,一個程序不能訪問另一個程序的資料,即Android的程序是相互獨立、隔離的。如果需要讀取另一個程序的資料就需要IPC機制。

IPC機制基本原理
  • 程序的空間分為使用者空間及系統空間,系統空間是全部程序公用的,使用者空間是每個程序私有的,當需要跨程序通訊時,程序1通過系統呼叫,將需要傳遞的資料複製到系統空間,由系統空間喚醒程序2的接收執行緒,通過系統呼叫將資料傳送到程序2的使用者空間(第二次複製),從而完成跨程序通訊。
Binder機制優點:

傳統的跨程序(socket)通訊缺點:1)複製兩次,費時間。 2)接收資料的快取有接收方提供,但接收方不知道需要提供多大合適。

而Binder機制呼叫系統函式mmap()記憶體對映,只需要複製一次即可。

Binder機制原理

利用Binder驅動建立接收快取區並實現地址對映關係:根據需對映的接收程序資訊,實現核心快取區接收程序使用者空間地址同時對映到同1個共享接收快取區中。

Binder機制模型步驟
  • 向驅動申請SM,驅動同意後成為SM,管理service。
  • Client與Server與SM的通訊都是通過Binder驅動,他們不可以直接與SM互動。
    • 註冊服務:
      • Server程序向Binder驅動發起註冊服務請求。
      • Binder驅動將註冊請求傳送給service manager程序。
      • service manager程序新增該service程序,即註冊服務。
    • 獲取服務:
      • client程序傳遞需要獲取的服務名稱,向Binder驅動發起獲取服務請求。
      • Binder驅動將請求轉發給SM。
      • SM查詢到client需要的Server對應的服務資訊。
      • 通過Binder驅動將上述資訊返回給client程序。
    • 使用服務:
      • Binder驅動為實現跨程序做準備(呼叫系統mmap()函式)實現記憶體對映。
        • Binder驅動建立一塊接收快取區
        • 實現地址對映關係:通過SM程序裡的server資訊找到server程序,實現核心快取區 和 server程序使用者空間地址 同時對映到同一接收快取中。
      • client程序將引數資料傳送到server程序:
        • client程序通過系統呼叫將資料傳送到核心快取區。 (存在記憶體對映關係,相當於也傳送到了server程序的使用者空間地址)
        • Binder驅動通知server程序進行解包。
      • server程序根據client程序要求呼叫目標方法:
        • 收到Binder驅動通知後,server程序從執行緒池中取出執行緒,進行資料解包和呼叫目標方法。
        • 將最終執行結果寫入到自己的共享記憶體中。
      • server程序將目標方法結果返回給client程序:
        • 由於存在記憶體對映關係,當server將結果寫入自己的記憶體中,Binder驅動通知client程序獲取返回結果(沒有起用新執行緒,之前傳送資料的執行緒被掛起)
        • client程序通過系統呼叫從核心快取區接收server程序返回的資料。

服務的保活方式

  • 在onStartCommand方法中返回START_STICK,在服務被殺死的時候會重新啟動。
  • 把service的優先順序(1000)是最高優先順序,也可以把服務改為前臺服務,在系統記憶體不足時不會被回收。
  • 使用AIDL跨程序機制雙程序保護。
  • 使用JobService
  • 使用自定義廣播,在應用退出時(傳送廣播啟動服務)
  • 使用系統廣播,比如開機的時候,點選home鍵的時候啟動廣播。

IntentService

定義:
  • Intent Service是繼承自service並處理非同步請求的服務。內部有一個工作執行緒處理耗時操作。
  • 啟動Intent Service 執行完成後會自動停止,不需要呼叫stopself。
  • 可以多次啟動Intent service,每一個耗時操作會以工作佇列的方式在intentservice的onHandleIntent回撥中執行,並且是依次執行。
  • 內部封裝了Handler Thread 和Handler實現的。
使用場景:
  • 一項任務需要幾個子任務進行,幾個子任務按順序進行才算完成。如在後臺默默進行耗時的上傳和下載操作。
問題
  • 啟動intent service不需要建立新的執行緒?

    在onCreate方法裡建立 了HandlerThread,這是一個繼承自Thread的類,在onCreate中也獲取了Looper進行工作。本來有一個執行緒,無需建立執行緒。

  • 為什麼不建議通過 bindService() 啟動 IntentService?

    intent service原始碼中的onBind方法預設返回null,不會回撥到onHanldeIntent方法中,沒有使用到intent service的優點,與普通service無區別。

  • 為什麼多次啟動 IntentService 會順序執行事件,停止服務後,後續的事件得不到執行?

    內部使用的是handler機制,多次啟動intent service不會重新建立新的執行緒和服務,而是把訊息加到訊息佇列裡,訊息佇列是一個單鏈表,訊息入列時的操作是根據時間入列,所以會按順序執行。停止服務後,會將訊息佇列的訊息清空,因此後續的事件得不到執行。