學習筆記之簡析安卓Android服務Service

前言

平時的開發中,我們用的最多就是在Activity中進行開發。當我們需要處理一個耗時的邏輯的時候,爲防止ANR我們會開啓一個子線程來執行,例如網絡請求等。但是當我們需要一個邏輯在後臺持續運行,並且能和多個活動進行通信,例如後臺播放器,子線程貌似就顯得無能爲力了。這個時候就需要用到服務了。顧名思義,他作爲後臺邏輯,爲前臺的Activity服務。接下來看看什麼是服務。

什麼是service?

簡介

服務Service,是Android四大組件之一。服務顧名思義就是用於爲前臺界面服務的一個組件。當我們把應用放到後臺,微信還是可以接受信息,歌曲還是可以播放,這個就是服務完成的。服務用的最多也是後臺工作。和作爲四大組件的Activity一樣,Service也是擁有類似的啓動模式,生命週期等等。

服務分類

服務分爲前臺服務和後臺服務兩種:前臺服務就像平時我們播放音樂的時候通知欄會有一個常駐的通知,然後後臺播放歌曲,那個就是前臺服務;
後臺服務一般我們感受不到,他在後臺默默爲前臺服務,提供數據運算等等。例如當我們使用微信返回桌面的時候,還是可以接受信息,這個就是服務的作用。

代碼簡析

先看看Service的代碼:

public class MyService extends Service {
    //構造器。一定要有一個空參數的構造器,不然無法啓動成功
    public MyService() {
    }

    //服務被創建的時候會調用。和Activity類似
    @Override
    public void onCreate() {
        super.onCreate();
    }

    //服務啓動的時候會調用
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
    
    //綁定服務的時候會調用這個方法,並返回一個IBinder對象,這裏用了內部類的方法
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
    //內部類,在這裏可以寫方法供給綁定的活動使用。Binder是實現IBinder接口的類
    class MyBinder extends Binder {

    }
}

可以見到只需要建立一個類,並繼承service類,即可創建服務。服務作爲四大組件之一,和活動也是一樣有類似的生命週期。相關的方法我在代碼中註釋了,看不太懂也沒關係,主要先接觸一下服務。然後作爲四大組件之一,當然也是需要註冊的:

<service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"/>

第一參數表示能否被啓動,第二個參數表示能否被隱式啓動。

與線程的區別

很多讀者可能會有一個誤解,就是以爲服務和活動不是在同一個線程,直接在服務中執行耗時任務,然後就會發現出現了ANR。服務不就是在後臺的嗎?實際上,服務是運行在主線程的,他只是整個類在前臺界面退出到桌面甚至被銷燬,他依然可以存在並運行,但是如果要執行耗時邏輯,則必須在服務中開啓一個子線程運行。

啓動服務的兩種方式

啓動服務和啓動Activity是類似,都是通過Intent來啓動對應的Service。有顯式和隱式兩種。這裏主要介紹顯式啓動。除此之外服務還有綁定這一種啓動方式。來看一下吧:

直接啓動

Intent intent = new Intent(this,MyService.class);
startService(intent);

通過代碼可以看到直接啓動一個服務和直接啓動一個活動幾乎是一模一樣的。區別就是調用的方法不同這也可以理解,啓動活動是startActivity,那啓動服務肯定就是startService了。
調用這個方法之後服務就會被創建,然後依次調用服務的onCreate和onStartCommand兩個方法。

這裏需要強調一下就是每個服務只能被創建一次,也就onCreate方法只會執行一次。多次使用startService去啓動同一個服務,只會重複執行onStartCommand方法,而不會再次執行onCreate方法,因爲只能Service只能存在一個實例。

綁定服務

和啓動活動不一樣的是,服務他可以通過綁定來啓動,這也容易理解,因爲服務本來就是爲前臺活動而服務的那麼和活動綁定也是爲了更好的通信。服務與活動綁定之後兩者的關係就更加的密切了,他們之間的通信業變得容易起來。先看看代碼:

private MyService.MyBinder binder;

ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                binder = (MyService.MyBinder)service;
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };

Intent intent = new Intent(this,MyService.class);
bindService(intent, connection, BIND_AUTO_CREATE);

代碼不長我們一個個來看。首先看到最後一行,可以看到綁定服務主要是執行bindService這個方法。這個方法傳入三個參數:Intent,Connection,int。

Intent容易理解,和直接啓動服務一樣。最後的int類型參數,是一個flag標誌,BIND_AUTO_CREATE的數值是1 ,表示綁定的時候順帶創建服務,並調用onCreate方法,一般情況下傳入這個參數即可。
第二個參數Connection是一個ServiceConnection對象,這個對象可以通過匿名類來創建,主要重寫兩個方法。第一個方法是綁定成功之後要做的事情,會返回一個IBinder參數,這個IBinder參數就是服務中的onBinder方法返回的數據。通過這個IBinder就可以調用服務中的方法了。

另外,當一個活動已經綁定了這個服務,另外一個活動櫃再次綁定這個服務的時候,不會再調用服務的onCreate方法,而是會把同一個IBinder參數再返回,也就是兩個活動拿到同一個IBinder對象。

服務的生命週期

先看看服務的生命總週期:
在這裏插入圖片描述
中間那段其實是比較不嚴謹。先從頭理清一下:
服務被啓動或者綁定都會調用onCreate方法,然後如果是直接啓動的話就會接着調用onStartCommand方法,如果是綁定的話就會調用onBinder方法。
多次啓動的話會調用onStartCommand方法,不能進行多次綁定。多個活動綁定同個服務,會拿到同一個IBinder對象,也就是不會重複調用onCreate和onBinder方法。

當服務是直接啓動的時候,在服務中調用stopSelf()或者調用stopService(Intent intent)來停止服務,這個時候服務就會調用onDestroy方法。
當服務是綁定啓動的時候,調用unbindService(ServiceConnection connection)方法就可以解除綁定了。這裏傳入的參數就是前面我們綁定服務的connection。

重點來了,當服務被直接啓動有被綁定時,需要同時調用兩者的方法才能把服務停下來。

服務與活動之間的通信

當使用啓動方法來啓動服務後,貌似服務就與活動沒什麼聯繫,而事實上也確實是這樣,那麼如果需要服務不斷地與活動進行通信,那怎麼辦呢,就用到綁定這種方法了。

前面講到了,綁定會反回一個IBinder對象。我們可以在Service中寫一個內部類來繼承Binder(Binder是實現IBinder接口的類),然後在這個類中就可以寫我們需要的方法。通過這個Binder對象就可以調用Service中的非private方法了,甚至把整個Service都返回也是可以的。這樣就可以實現兩者之間的通信了。

使用服務的重點之一:多線程

前面我們講到,Service並不是運行在子線程的,而是運行在主線程,所以我們不能直接在服務中執行耗時的邏輯,不然就會產生ANR。因而每個耗時的邏輯都應該在子線程中執行。開啓子線程可以參考下面的例子:

new Thread(new Runnable() {
            @Override
            public void run() {
             //這裏寫要執行的耗時邏輯
                }
            }
        }).start();

關於多線程編程這裏不多做解析,只是強調一個問題:耗時邏輯一定要在子線程中運行,Service本身並不是運行在子線程中的,而是運行在主線程,這一點一定要記住。

IntentService

由於上面講的子線程問題,很多時候會忘了開啓線程,又或者在耗時邏輯結束後要關閉服務卻忘了關。這個時候IntentService就出來,先看代碼:

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {

    } 
}

這個IntentService和原來的Service類基本是差不多的,區別就在於兩個:構造方法和onHandleIntent這個方法。構造方法要調用父類的構造器;onHandleIntent這個方法是默認執行在子線程的,不需要我們自己去開闢線程,只需要直接把耗時邏輯寫在裏面就行,而且執行完之後還會自動把服務關閉。除了這兩個其他和普通的Service基本是一模一樣的。

前臺服務

關於前臺服務,相信各位讀者肯定是非常熟悉的。當我們播放音樂的時候,我們拉下通知欄,是不是有一個播放欄在那裏,沒錯那個就是前臺服務。
前臺服務,顧明思義,就是把一個服務放到前臺來,他不再是躲在背後了而是來到了前臺,被用戶所感知。大家終於知道後臺有什麼服務正在運行了,終於知道誰在爲我們默默付出了。那接下來就來看看這個前臺服務究竟怎麼實現。

前臺服務的作用

瞭解怎麼使用之前先來看看他有什麼作用,用一個東西前得先了解爲什麼要用它,有什麼好處。

  1. 首先一個就是像音樂播放器一樣的常駐通知欄,可以提供給用戶一些簡單的操作,例如上下曲。可以在不用切換到對應的app就可以操作後臺服務,這是前臺服務的一個很重要的作用。
  2. 第二個就是避免服務被回收。我們都知道服務一般是在後臺的,用戶看不見的,所以當內存不夠的時候,後臺的任務就會被回收。而當使用了前臺服務的時候,服務就被用戶所感知,就不會被回收了。

如何使用前臺服務

前臺服務的使用非常的簡單,和使用通知Notification是差不多的。只是前臺服務,叫做服務,所以必須依賴於服務而存在,不想通知那樣隨時隨地可以發出。那接下來先看看簡單的代碼實現:

public class MyService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
		Intent intent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
        Notification notification = new NotificationCompat.Builder(this,"default_channel")
                .setSmallIcon(R.mipmap.ic_launcher)//設置小圖標
                .setContentTitle("yinyue")
                .setContentText("qwer")
                .setContentIntent(pendingIntent)//設置點擊intent
                .build();
        startForeground(1,notification);
        }
}

代碼非常簡單,和創建一條通知是差不多,如果你尚未知道如何去創建一條通知可以先去學習一下關於Notification的內容。這裏就不展開敘述了。最主要的區別就是最後調用了startForeground這個方法來啓用一個前臺服務。
若要實現像音樂播放器那樣的自定義標題欄,可以使用remoteView來實現。具體這裏也不展開,感興趣的讀者可以自行了解。

遠程服務

很多讀者可能會對服務有個誤解,就是服務本身是運行在子線程的,但事實上服務本身和活動一樣都是運行在子線程的,在服務中的任何耗時任務都必須開啓一個子線程來完成,否則就會出現ANR。那麼有沒有一種服務是本身就運行在與主線程不同的線程呢?答案是肯定的,也就有了遠程服務的存在。不過遠程服務並不是運行在與別的線程,而是運行在另一個進程。相當於是另一個應用的服務。還是一樣,先來了解一下遠程服務有什麼用處。

遠程服務的作用

  1. 不用開啓子線程,可直接運行耗時任務。因爲遠程服務已經不運行在主線程了,甚至是不在該應用的進程中,所以直接運行耗時任務並不會造成ANR。
  2. 遠程服務爲進程共享。因爲遠程服務是獨立的一個進程,相當於一個獨立的應用,可以服務於其他的應用,所以,遠程服務是可以被多個應用使用。所以當其他的應用需要用到這個服務的時候可以通過隱式啓動的方式來使用這個服務。

如果定義一個遠程服務

定義一個遠程服務可不謂太簡單。只需要在AndroidManifest中指定一個proccess(進程)即可,看代碼:

<service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote"
             />

上面代碼的最後一行爲服務指定了一個新的進程,進程名字是包名+“:remote”

跨進程通信AIDL

由於服務和活動處在不同的進程了,這個時候我們就需要使用到跨進程通信AIDL了。名字看起來高大上,其實也不難,來看看怎麼實現吧。

  1. 首先在項目目錄的main文件下新建一個AIDL文件,然後就會自動生成一個接口文件,如下圖:
    在這裏插入圖片描述
    打開之後會看到代碼:
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

}
  1. 來解釋一下這個AIDL文件。很明顯這是一個接口文件。活動要使用遠程服務就只能通過這個接口來使用遠程服務中的方法。我們都知道常規的綁定服務是返回一個IBinder對象,遠程服務返回的是這個接口的一個實現對象,所以只能使用這個接口裏面的方法。因爲我們需要在這個接口裏定義我們需要用到的方法,然後在服務裏面去實現這個接口。
    這裏面有一個已經生成好的方法,意思是告訴我們哪些數據可以被當成參數。由於屬於跨進程通信,所以只有能被序列化的數據才能傳輸。自定義對象要實現Parcelable接口,再給這個類定義一個同名的AIDL文件。感興趣的讀者可以自行了解,這裏就不展開了。
    所以在這個接口文件中我們可以定義我們需要用到的方法,這裏我增加了一個方法:(注意修改完了要build一下修改纔會生效)
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    int getNum();


}

3.定義好接口之後就是實現這個接口了。我們前面是在服務中自定義了一個實現Binder的類,然後返回這個類的一個對象來給Activity使用。這裏我們不用再去編寫一個類,直接實現我們剛剛的那個接口即可。先來看看代碼:

public class MyService extends Service {
  ...

    IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }


        @Override
        public int getNum() throws RemoteException {
            return getN();
        }
    };
    
	private int getN(){
        return 8;
    }
    ...
  }

相信代碼不難看懂。但是可能會有讀者會疑問這個Stub是什麼鬼?其實這個Stub就是一個繼承了IBinder實現了我們定義AIDL接口的抽象類。這也就是爲什麼我們可以直接給onBind方法返回,因爲這個是繼承了IBinder類的。
4.最後就是Activity中的代碼了。Activity中的代碼也和綁定普通服務差不多,先來看看代碼吧:

private TextView textView;
private IMyAidlInterface binder;
void bindService(){
        ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                binder = IMyAidlInterface.Stub.asInterface(service) ;
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
    }

我們之前直接返回的是一個IBinder對象直接用,這裏我們不能直接使用,這裏返回的是一個IMyAidlInterface.Stub對象,所以我們要把它轉化成IMyAidlInterface對象來使用裏面的方法。Stub類中提供了一個方法來轉化,這樣我們拿到IMyAidlInterface對象之後,就可以調用服務中的方法了。剩下的就和普通的服務一樣了。

遠程服務小結

遠程服務的使用一般用於一個服務提供給多個應用 使用的情況下,別的應用可以通過隱式啓動來使用這個服務。達到一個服務給多個應用同時提供服務的功能。如果僅僅只是想執行耗時任務,在普通服務中開啓子線程是爲更好的解決方法。
跨進程通信傳輸的數據只有基礎數據類型和Lish,Map等。而且無法傳輸引用,只能傳輸數據。所以只能把對象的屬性傳遞過去而無法使用其中的方法。

參考資料

郭霖:《第一行代碼》
Android Service完全解析,關於服務你所需知道的一切(上)
Android Service完全解析,關於服務你所需知道的一切(下)

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