Service的基本用法
Service概念及用途:
A service is an application component that can perform long-running operations in the background and does not provide a user interface。
通常service用來執行一些耗時操作,或者后臺執行不提供用戶交互界面的操作,例如:下載、播放音樂。
Service分為本地服務(LocalService)和遠程服務(RemoteService):
本地服務依附在主進程上而不是獨立的進程,這樣在一定程度上節約了資源,另外Local服務因為是在同一進程因此不需要IPC,也不需要AIDL。相應bindService會方便很多。主進程被Kill后,服務便會終止。
遠程服務為獨立的進程,對應進程名格式為所在包名加上你指定的android:process字符串。由于是獨立的進程,因此在Activity所在進程被Kill的時候,該服務依然在運行,不受其他進程影響,有利于為多個進程提供服務具有較高的靈活性。該服務是獨立的進程,會占用一定資源,并且使用AIDL進行IPC稍微麻煩一點。
按使用方式可以分為以下三種:
1、startService 啟動的服務:主要用于啟動一個服務執行后臺任務,不進行通信。停止服務使用stopService;
2、bindService 啟動的服務:該方法啟動的服務可以進行通信。停止服務使用unbindService;
3、startService 同時也 bindService 啟動的服務:停止服務應同時使用stepService與unbindService
Service生命周期 :
Service 的啟動方式分為兩種:
-
Context.startService()方式啟動:
步驟:
1.定義一個類繼承Service
2.在Manifest.xml文件中配置該Service
3.使用Context的startService(Intent)方法啟動該Service
4.不再使用時,調用stopService(Intent)方法停止該服務
這里分為啟動時兩種情況:
1.如果Service還沒有運行,則android先調用onCreate()然后調用onStart();
2.如果Service已經運行,則只調用onStart(),所以一個Service的onStart方法可能會重復調用多次。
一旦服務開啟跟調用者(開啟者)就沒有任何關系了。即使開啟者退出了,服務還在后臺長期的運行。開啟者不能調用服務里面的方法。 -
Context.bindService()方式啟動:
步驟:
1.定義一個類繼承Service
2.在Manifest.xml文件中配置該Service
3.使用Context的bindService(Intent, ServiceConnection, int)方法啟動該Service
4.不再使用時,調用unbindService(ServiceConnection)方法停止該服務
bindService() 的調用者與服務綁在一起,調用者一旦退出了,服務也隨即終止掉。
Service生命周期.png
Service和Thread概念區分
之所以有不少人會把它們聯系起來,主要就是因為Service的后臺概念。Thread我們大家都知道,是用于開啟一個子線程,在這里去執行一些耗時操作就不會阻塞主線程的運行。而Service我們最初理解的時候,總會覺得它是用來處理一些后臺任務的,一些比較耗時的操作也可以放在這里運行,這就會讓人產生混淆了。但是,Service其實是運行在主線程里的。。
Android的后臺就是指,它的運行是完全不依賴UI的。即使Activity被銷毀,或者程序被關閉,只要進程還在,Service就可以繼續運行。比如說一些應用程序,始終需要與服務器之間始終保持著心跳連接,就可以使用Service來實現。你可能又會問,前面不是剛剛驗證過Service是運行在主線程里的么?在這里一直執行著心跳連接,難道就不會阻塞主線程的運行嗎?當然會,但是我們可以在Service中再創建一個子線程,然后在這里去處理耗時邏輯就沒問題了。
既然在Service里也要創建一個子線程,那為什么不直接在Activity里創建呢?這是因為Activity很難對Thread進行控制,當Activity被銷毀之后,就沒有任何其它的辦法可以再重新獲取到之前創建的子線程的實例。而且在一個Activity中創建的子線程,另一個Activity無法對其進行操作。但是Service就不同了,所有的Activity都可以與Service進行關聯,然后可以很方便地操作其中的方法,即使Activity被銷毀了,之后只要重新與Service建立關聯,就又能夠獲取到原有的Service中Binder的實例。因此,使用Service來處理后臺任務,Activity就可以放心地finish,完全不需要擔心無法對后臺任務進行控制的情況。
這就是Service存在的意義了,
一個比較標準的Service就可以寫成:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 開始執行后臺任務
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
class MyBinder extends Binder {
public void startDownload() {
new Thread(new Runnable() {
@Override
public void run() {
// 執行具體的下載任務
}
}).start();
}
}
遠程服務端Service的實現(RemoteService)
使用遠程Service甚至可以實現Android跨進程通信的功能。
將一個普通的Service轉換成遠程Service其實非常簡單,只需要在注冊Service的時候將它的android:process屬性指定成:remote就可以了
<service
android:name=".MyService"
android:enabled="true"
android:process=":remote"
android:exported="true"></service>
使用了遠程Service后,MyService已經在另外一個進程當中運行了,所以只會阻塞該進程中的主線程,并不會影響到當前的應用程序。
不過遠程服務端也有一定的弊端:首先將MyService的onCreate()方法中讓線程睡眠的代碼去除掉,然后重新運行程序,并點擊一下Bind Service按鈕,你會發現程序崩潰了!為什么點擊Start Service按鈕程序就不會崩潰,而點擊Bind Service按鈕就會崩潰呢?這是由于在Bind Service按鈕的點擊事件里面我們會讓MainActivity和MyService建立關聯,但是目前MyService已經是一個遠程Service了,Activity和Service運行在兩個不同的進程當中,這時就不能再使用傳統的建立關聯的方式,程序也就崩潰了。
那么如何才能讓Activity與一個遠程Service建立關聯呢?這就要使用AIDL來進行跨進程通信了(IPC)。
接下來,我們一步步實現遠程服務端的步驟:
-
首先需要新建一個AIDL文件,在這個文件中定義好Activity需要與Service進行通信的方法。新建MyAIDLService.aidl文件,代碼如下所示:
aidl.png 修改MyService中的代碼,在里面實現我們剛剛定義好的MyAIDLService接口,如下所示:
這里先是對MyAIDLService.Stub進行了實現,重寫里了toUpperCase()和plus()這兩個方法。這兩個方法的作用分別是將一個字符串全部轉換成大寫格式,以及將兩個傳入的整數進行相加。然后在onBind()方法中將MyAIDLService.Stub的實現返回。
- 接下來修改MainActivity中的代碼,如下所示:
package com.feoul.servicetest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private RemoteServiceTest1 myAIDLService;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
myAIDLService = RemoteServiceTest1.Stub.asInterface(iBinder);
try {
int result = myAIDLService.plus(3, 5);
String upperStr = myAIDLService.toUpperCase("hello world");
Log.d("TAG", "result is " + result);
Log.d("TAG", "upperStr is " + upperStr);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);
}
}
這里首先使用了MyAIDLService.Stub.asInterface()方法將傳入的IBinder對象傳換成了MyAIDLService對象,接下來就可以調用在MyAIDLService.aidl文件中定義的所有接口了。這里我們先是調用了plus()方法,并傳入了3和5作為參數,然后又調用了toUpperCase()方法,并傳入hello world字符串作為參數,最后將調用方法的返回結果打印出來。
我們的跨進程通信功能已經完美實現了。
不過還有一點需要說明的是,由于這是在不同的進程之間傳遞數據,Android對這類數據的格式支持是非常有限的,基本上只能傳遞Java的基本數據類型、字符串、List或Map等。那么如果我想傳遞一個自定義的類該怎么辦呢?這就必須要讓這個類去實現Parcelable接口,并且要給這個類也定義一個同名的AIDL文件。