Android 你不知道的Service(服務) & Thread(執行緒)
Service作為Android的四大元件之一,你或許會經常用它。當提到它時,我們都隨口說,它會在後臺執行長時間的任務,但是,這種表述真的對麼?你是否真的瞭解Service,就讓我們來揭開Service的真面目。
Service
Android Developer對於Service如下定義,
A Service is an application component that can perform long-running operations in the background and does not provide a user interface. Another application component can start a service and it will continue to run in the background even if the user switches to another application. Additionally, a component can bind to a service to interact with it and even perform interprocess communication (IPC). For example, a service might handle network transactions, play music, perform file I/O, or interact with a content provider, all from the background.
簡單翻譯一下,一個服務(Service)是一個應用的元件,其可以在後臺執行長時間的操作,而且不提供UI。其他應用元件可以開啟一個服務,該服務將會在後臺一直執行,縱使使用者切換到了其他應用。另外,一個元件可以捆綁到服務上,來與其互動,甚至執行IPC。例如,一個服務可以執行網路連結、播放音樂、執行I/O或者和一個內容提供者(Content Provider)互動,都是在後臺執行。
或許,我們對Service的誤解就來源於這句話,perform long-running operations in the background 不就是可以在後臺執行上時間操作的意思麼。意思,的確是這個意思,但是,我們是否理解錯了呢?
Developer關於Service有個注意事項:
Caution: A service runs in the main thread of its hosting process—the service does not create its own thread and does not run in a separate process (unless you specify otherwise). This means that, if your service is going to do any CPU intensive work or blocking operations (such as MP3 playback or networking), you should create a new thread within the service to do that work. By using a separate thread, you will reduce the risk of Application Not Responding (ANR) errors and the application’s main thread can remain dedicated to user interaction with your activities.
大致的意思是,
一個服務(service)執行在主執行緒中,服務並不建立自己的執行緒,也不在隔離程序中執行(除非你指定)。這意味著,如果你的服務要執行CPU費時操作或阻塞操作,你需要在服務中建立新的執行緒來執行該操作。使用其他執行緒,可以避免ANR錯誤,保證應用的主執行緒可以與使用者互動。
看到了吧。雖然service是在後臺執行,但是還是在主執行緒執行的,而主執行緒是什麼呢?就是UI執行緒,負責螢幕事件的分發和相應,如果在service中執行長時間的操作,就會造成UI執行緒阻塞,螢幕無響應(無法分發螢幕事件),甚至出現ANR(Application Not Responding)現象。
讓我們來測試下,service是否執行在主執行緒,Android提供了Thread.currentThread().getId()來得到當前執行緒的Id,我們可以在主執行緒和service中得到執行緒Id,然後判斷是否一致,程式碼如下
public class MainActivity extends ActionBarActivity {
private String TAG = "MYSERVICE";
private Button button1, button2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.i(TAG, "Main Thread ID; " + Thread.currentThread().getId());
startService(new Intent(MainActivity.this, MyService.class));
}
});
}
}
Activity程式碼很簡單,就是點選按鈕,輸出當前執行緒id,startservice,service程式碼如下,
public class MyService extends Service {
private String TAG = "MYSERVICE";
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
Log.i(TAG, "onBind");
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
Log.i(TAG, "onCreate");
}
@Override
@Deprecated
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
super.onStart(intent, startId);
Log.i(TAG, "onStart");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
Log.i(TAG, "onStartCommand");
Log.i(TAG, "Service Thread ID; " + Thread.currentThread().getId());
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.i(TAG, "onDestroy");
}
@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
return super.onUnbind(intent);
}
}
service的onStartCommand方法中,我們打印出service的thread Id。然後執行,檢視結果是否一致,
11-18 02:36:12.644 6099-6099/paul.example.servicetest I/MYSERVICE: Main Thread ID; 1
11-18 02:36:12.654 6099-6099/paul.example.servicetest I/MYSERVICE: onCreate
11-18 02:36:12.654 6099-6099/paul.example.servicetest I/MYSERVICE: onStartCommand
11-18 02:36:12.654 6099-6099/paul.example.servicetest I/MYSERVICE: Service Thread ID; 1
11-18 02:36:12.654 6099-6099/paul.example.servicetest I/MYSERVICE: onStart
上面的輸出結果,證明了service其實是在主執行緒中執行的。如果我們在service的onStartCommand新增Thread.sleep(5000)語句,讓其休眠5s中,執行程式,開啟service,你會發現,開啟service後,activity的頁面無法相應了。這是由於service執行在主執行緒(UI執行緒)中,service的休眠也會導致UI執行緒的休眠,所以UI就無法相應啦。所以雖然說service執行後臺任務,但是如果該任務是耗時的話,還是得新建執行緒執行的哦。
service & thread
關於何時用service,何時用thread,Developer給出的建議是,
Should you use a service or a thread?
A service is simply a component that can run in the background even when the user is not interacting with your application. Thus, you should create a service only if that is what you need.
If you need to perform work outside your main thread, but only while the user is interacting with your application, then you should probably instead create a new thread and not a service. For example, if you want to play some music, but only while your activity is running, you might create a thread in onCreate(), start running it in onStart(), then stop it in onStop(). Also consider using AsyncTask or HandlerThread, instead of the traditional Thread class. See the Processes and Threading document for more information about threads.
Remember that if you do use a service, it still runs in your application’s main thread by default, so you should still create a new thread within the service if it performs intensive or blocking operations.
簡單總結一下,
1. 如果你僅僅需要執行後臺任務,並不需要和使用者互動,此時你可以使用service;
2. 如果你需要在主執行緒在執行任務,並且當需要和使用者互動的時候,此時你可以選擇新建一個thread而非service,例如,如果你僅僅需要在activity執行的時候播放音樂,你可以選擇在Activity的onCreate方法中新建執行緒,在onStart方法中執行,在onStop方法中停止。或者,可以採用 AsyncTask或Handler執行緒。
或許,你會疑問,既然 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的使用。