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實現的。
    **生命週期:**onCreate(只執行一次)—onStartCommand—onStart–onHandleIntent–onDestory
優勢與作用:
  • 內部有一個繼承於Thread的HandleThread線程,綁定了一個Looper,支持串行執行消息隊列中的消息。
  • IntentService比一般線程優先級高,適用於處理優先級較高的耗時操作。也因爲具有較高的優先級,不容易被殺死,可靠性強。
  • 省去了在service裏開啓線程的麻煩,也不用手動關閉服務。
使用場景:
  • 一項任務需要幾個子任務進行,幾個子任務按順序進行纔算完成。如在後臺默默進行耗時的上傳和下載操作。
源碼分析

當調用IntentService時,會回調到onCreate方法中,在該方法中創建了一個HandleThread,並啓動這個線程,也創建了一個servicehandler。

HandleThread,繼承於Thread類,是一個線程,當線程處於執行狀態時,創建了一個looper,並調用loop方法開始無限消息輪詢過程。

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
   //....省略部分代碼....
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }

//核心代碼
    @Override
    public void run() {
        mTid = Process.myTid();
        //創建looper
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        //開始無限循環消息
        Looper.loop();
        mTid = -1;
    }
    //....省略部分代碼....
}

創建了一個servicehandler處理消息

  private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
        	//調用onHandleIntent
            onHandleIntent((Intent)msg.obj);
            //並停止服務---因此不用手動停止服務
            stopSelf(msg.arg1);
        }
    }

接着調用onStartCommand 喝onStart,在自定義的intentservice中不用重寫這兩個方法。

 @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
    //獲取到intent,調用handler的發送消息
     @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

發送消息之後,loop輪詢到消息並分發給目標處理消息,調用onHandleIntent.是一個抽象方法,必須重寫處理耗時任務。

問題
  • 啓動intent service不需要創建新的線程?

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

  • 爲什麼不建議通過 bindService() 啓動 IntentService?

    intent service源碼中的onBind方法默認返回null,不會回調到onHanldeIntent方法中,沒有使用到intent service的優點,與普通service無區別。

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

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

  • 如何實現讓IntentService任務可以並行執行?

    創建多個intent service。

demo地址祥見:github:https://github.com/MarinaTsang/sqliteAndContentprovider/tree/master/sevicetest

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章