android四大組件之Service
最近因為還沒找到工作所以也趁著現(xiàn)在有時間,將以前的只是整理下,要不然總?cè)菀走z忘,今天就來講解下Service的用法。作為Android的四大組件之一,其重要性可想而知。在應(yīng)用中我們主要是用來進行一些后臺操作,不需與應(yīng)用UI進行交互,執(zhí)行耗時任務(wù)等。
官方文檔中這樣說:
Service 是一個可以在后臺執(zhí)行長時間運行操作而不提供用戶界面的應(yīng)用組件。服務(wù)可由其他應(yīng)用組件啟動,而且即使用戶切換到其他應(yīng)用,服務(wù)仍將在后臺繼續(xù)運行。
此外,組件可以綁定到服務(wù),以與之進行交互,甚至是執(zhí)行進程間通信 (IPC)。 例如,服務(wù)可以處理網(wǎng)絡(luò)事務(wù)、播放音樂,執(zhí)行文件 I/O
或與內(nèi)容提供程序交互,而所有這一切均可在后臺進行。
Service的用途:
1.在后臺執(zhí)行耗時操作,但不需要與用戶進行交互。2.一個應(yīng)用暴露出來的一些供其他應(yīng)用使用的功能。
這里需要聲明一點,Service是運行在主線程中,因而如果需要進行耗時操作或者訪問網(wǎng)絡(luò)等操作,需要在Service中再開啟一個線程來執(zhí)行(使用IntentService的話則不需要在自己手動開啟線程)。
啟動Service
啟動一個Service有兩種方式:
- Context.startService()
- Context.bindService()
(圖片截取自官方文檔:https://developer.android.com...)
startService()方式啟動Service,我們啟動之后是沒有辦法再對Service進行控制的,而且啟動之后該Service是一直在后臺運行的,即使它里面的一些代碼執(zhí)行完畢,我們要想終止該Service,就需要在他的代碼里面調(diào)用stopSelf()方法或者直接調(diào)用stopService() 方法。而通過bindService()方法啟動的Service,客戶端將獲得一個到Service的持久連接,客戶端會獲取到一個由Service的onBind(Intent)方法返回來的IBinder對象,用來供客戶端回調(diào)Service中的回調(diào)方法。
我們無論使用那種方法,都需要定義一個類,讓它繼承Service類,并重寫其中的幾個方法,如果我們是采用startService()方式啟動的話,只需要重寫onCreate() 、onStartCommand(Intent intent, int flags, int startId)、onDestroy()方法即可(其實我們也可以重寫),而如果采用的是bindService()方法啟動的話,我們就需要重寫onCreate() 、onBind(Intent intent)、 onUnbind(Intent intent)方法.注意,作為四大組件之一,Service使用之前要在清單文件中進行配置。
- <application>
- ......
- <service
- android:name=".MyService">
- </service>
- </application>
Context.startService()
MyService.java的代碼:
- public class MyService extends Service {
- public MyService() {
- }
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("test","onCrete executed !");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("test","onStartComand executed !");
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.i("test","onDestroy executed !");
- }
- }
MainActivity.java的代碼如下:
- public class MainActivity extends AppCompatActivity implements View.OnClickListener{
- Button btnStart,btnStop;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- btnStart = (Button) findViewById(R.id.btn_start);
- btnStop = (Button) findViewById(R.id.btn_stop);
- btnStart.setOnClickListener(this);
- btnStop.setOnClickListener(this);
- }
- @Override
- public void onClick(View view) {
- Intent mIntent = new Intent(MainActivity.this,MyService.class);
- switch (view.getId()){
- case R.id.btn_start:
- startService(mIntent);
- break;
- case R.id.btn_stop:
- stopService(mIntent);
- break;
- }
- }
- }
主界面就兩個按鈕,一個用來啟動Service,一個用來停止Service:
下面我們先點擊START按鈕,Log信息如下:
可以看出,onCreate()方法先執(zhí)行,然后onStartCommand()方法緊接著執(zhí)行,那么如果我們再次點擊啟動按鈕呢?結(jié)果如下圖:
我們可以看到,這次onCreate()方法沒有再執(zhí)行,而是直接執(zhí)行了onStartCommand()方法,這是因為Service只在***次創(chuàng)建的時候才執(zhí)行onCreate()方法,如果已經(jīng)創(chuàng)建了,那之后再次調(diào)用startService()啟動該Service的時候,只會去執(zhí)行onStartCommand()方法方法,而不會再執(zhí)行onCreate()方法。
接下來我們點擊停止按鈕,可以看到,onDestroy()方法被執(zhí)行了:
注意,如果我們不點擊停止按鈕手動停止該Service的話,該Service會一直在后臺運行,即使它的onStartCommand()方法中的代碼已經(jīng)執(zhí)行完畢,在下圖中我們可以看到:
這時候我們的這個Service是一直在后臺執(zhí)行的,即使它的onStartCommand()方法中的代碼已經(jīng)執(zhí)行完了。如果我們想要它自動停止的話,可以將onStartCommand()方法中的代碼修改如下:
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("test","onStartComand() executed !");
- stopSelf();
- return super.onStartCommand(intent, flags, startId);
- }
Context.bindService()
采用該方法的代碼就稍微比以前的多了,因為我們需要在客戶端對Service進行控制,因而會在MainActivity中創(chuàng)建一個匿名內(nèi)部類ServiceConnection,然后會在bindService()方法和unbindService()方法中將其傳入。MyService.java 中的代碼如下:
- public class MyService extends Service {
- private MyBinder myBinder = new MyBinder();
- public MyService() {
- }
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("test","onCreate() executed !");
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.i("test","onDestroy() executed !");
- }
- @Override
- public boolean onUnbind(Intent intent) {
- Log.i("test","onUnbind executed !");
- return super.onUnbind(intent);
- }
- @Override
- public IBinder onBind(Intent intent) {
- Log.i("test","onBind() executed !");
- return myBinder;
- }
- class MyBinder extends Binder{
- public void startDownload(){
- Log.i("test", "MyBinder中的startDownload() executed !");
- // 執(zhí)行具體的下載任務(wù),需開啟一個子線程,在其中執(zhí)行具體代碼
- }
- }
- }
MainActivity.java 的代碼如下:
- public class MainActivity extends AppCompatActivity implements View.OnClickListener{
- Button btnBind,btnUnBind;
- MyService.MyBinder myBinder ;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- btnBind = (Button) findViewById(R.id.bind);
- btnUnBind = (Button) findViewById(R.id.btn_unBind);
- btnBind.setOnClickListener(this);
- btnUnBind.setOnClickListener(this);
- }
- ServiceConnection mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- // 將IBinder向下轉(zhuǎn)型為我們的內(nèi)部類MyBinder
- myBinder = (MyService.MyBinder) iBinder;
- // 執(zhí)行下載任務(wù)
- myBinder.startDownload();
- }
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- }
- };
- @Override
- public void onClick(View view) {
- Intent mIntent = new Intent(MainActivity.this,MyService.class);
- switch (view.getId()){
- case R.id.bind:
- // 綁定Service
- bindService(mIntent,mServiceConnection,BIND_AUTO_CREATE);
- break;
- case R.id.btn_unBind:
- // 取消綁定Service
- unbindService(mServiceConnection);
- break;
- }
- }
- }
點擊綁定按鈕;
點擊取消綁定按鈕:
注意,如果我們沒有先點擊綁定,而是直接點擊的取消綁定,程序會直接crash,報以下錯誤:
- java.lang.IllegalArgumentException: Service not registered: com.qc.admin.myserializableparceabledemo.MainActivity$1@8860e28
- at android.app.LoadedApk.forgetServiceDispatcher(LoadedApk.java:1120)
- at android.app.ContextImpl.unbindService(ContextImpl.java:1494)
- at android.content.ContextWrapper.unbindService(ContextWrapper.java:616)
- at com.qc.admin.myserializableparceabledemo.MainActivity.onClick(MainActivity.java:71)
細心的你也許早就發(fā)現(xiàn)了,Log中并沒有打印"onServiceDisconnected executed !"這句,也就是說沒有調(diào)用onServiceDisconnected()方法?從字面理解,onServiceConnected()方法是在Service建立連接的時候調(diào)用的,onServiceDisconnected()不就應(yīng)該是在Service斷開連接的時候調(diào)用的嗎?其實不然,我們查看該方法的文檔就知道了:
Called when a connection to the Service has been lost. This typically happens when the process hosting the service has crashed or been killed. This does not remove the ServiceConnection itself -- this binding to the service will remain active, and you will receive a call to onServiceConnected(ComponentName, IBinder) when the Service is next running.
意思就是:當(dāng)綁定到該Service的連接丟失的時候,該方法會被調(diào)用,典型的情況就是持有該Service的進程crash掉了,或者被殺死了。但是這并不會移除ServiceConnection 自身--它仍然是保持活躍狀態(tài),當(dāng)Service下次被執(zhí)行的時候,onServiceConnected(ComponentName, IBinder) 方法仍然會被調(diào)用。
但是要注意,如果我們按照剛才說的,不是先點擊 bindService()方法,而是直接點擊unbindService()方法,程序雖然也是crash掉了,但onServiceDisconnected()方法并不會被調(diào)用,這個很容易理解,畢竟都沒有建立連接呢,談何斷開連接啊。但是如果我們已經(jīng)綁定了Service,然后在后臺直接終止該Service呢?結(jié)果會怎樣?答案是onServiceDisconnected()方法仍然不會調(diào)用。這里我覺得應(yīng)該是只有在意外的情況下進程結(jié)束,是由系統(tǒng)自動調(diào)用的,而非我們手動停止的。我們可以查看該方法內(nèi)部的注釋:
This is called when the connection with the service has been
unexpectedly disconnected -- that is, its process crashed.Because it
is running in our same process, we should never see this happen.
這段文字清楚的說明了該方法執(zhí)行的場景:異常情況下導(dǎo)致斷開了連接。也就是進程crash掉了。因為它運行在我們應(yīng)用程序所在的進程中,因而我們將永遠不希望看到這種情況發(fā)生。
Context.startService()和Context.bindService()同時使用
這兩種方式是可以同時使用的,但是要注意,startService()和stopService()方法是對應(yīng)的,而bindService()和unBind()方法是對應(yīng)的,也就是說如果我們先調(diào)用startService()之后調(diào)用bindService()方法,或者相反,那么我們?nèi)绻徽{(diào)用stopService()或者只調(diào)用bindService()都無法停止該Service,只有同時調(diào)用才可以。
下面來看下具體代碼:
MainActivity.java
- public class MainActivity extends AppCompatActivity implements View.OnClickListener{
- Button btnStart,btnStop,btnBind,btnUnBind;
- MyService.MyBinder myBinder ;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- btnStart = (Button) findViewById(R.id.btn_start);
- btnStop = (Button) findViewById(R.id.btn_stop);
- btnBind = (Button) findViewById(R.id.btn_bind);
- btnUnBind = (Button) findViewById(R.id.btn_unBind);
- btnStart.setOnClickListener(this);
- btnStop.setOnClickListener(this);
- btnBind.setOnClickListener(this);
- btnUnBind.setOnClickListener(this);
- }
- ServiceConnection mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- // 將IBinder向下轉(zhuǎn)型為我們的內(nèi)部類MyBinder
- myBinder = (MyService.MyBinder) iBinder;
- // 執(zhí)行下載任務(wù)
- myBinder.startDownload();
- }
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- Log.i("test","onServiceDisconnected executed !");
- }
- };
- @Override
- public void onClick(View view) {
- Intent mIntent = new Intent(MainActivity.this,MyService.class);
- switch (view.getId()){
- case R.id.btn_start:
- // 啟動Service
- startService(mIntent);
- break;
- case R.id.btn_stop:
- // 終止Service
- stopService(mIntent);
- break;
- case R.id.btn_bind:
- // 綁定Service
- bindService(mIntent,mServiceConnection,BIND_AUTO_CREATE);
- break;
- case R.id.btn_unBind:
- // 取消綁定Service
- unbindService(mServiceConnection);
- break;
- }
- }
- }
MyService.java的代碼:
- public class MyService extends Service {
- private MyBinder myBinder = new MyBinder();
- public MyService() {
- }
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("test","onCreate() executed !");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("test","onStartComand() executed !");
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.i("test","onDestroy() executed !");
- }
- @Override
- public boolean onUnbind(Intent intent) {
- Log.i("test","onUnbind executed !");
- return super.onUnbind(intent);
- }
- @Override
- public IBinder onBind(Intent intent) {
- Log.i("test","onBind() executed !");
- return myBinder;
- }
- class MyBinder extends Binder{
- public void startDownload(){
- Log.i("test", "MyBinder中的startDownload() executed !");
- // 執(zhí)行具體的下載任務(wù)
- }
- }
- }
a.下面是依次點擊start、bind、stop、unBind 按鈕的輸出結(jié)果:
b.下面是依次點擊start、bind、unbind、stop 按鈕時的輸出結(jié)果:
在前臺運行服務(wù)
我們上面一直說Service一般是用來在后臺執(zhí)行耗時操作,但是要知道,Service也是可以運行在前臺的。后臺Service的優(yōu)先級比較低,容在內(nèi)存不足等情況下被系統(tǒng)殺死,通過將其設(shè)置為前臺,可以大大降低其被殺死的機會。前臺Service會在系統(tǒng)通知欄顯示一個圖標,我們可以在這里進行一些操作。前臺Service比較常見的場景有音樂播放器和天氣預(yù)報等:
那么接下來我們就直接上代碼:
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("test", "onCreate() executed !");
- Intent mIntent = new Intent(this, SecondActivity.class);
- PendingIntent mPendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0);
- Notification mNotification = new NotificationCompat.Builder(this)
- .setSmallIcon(R.mipmap.ic_launcher)
- .setContentTitle("My Notification ")
- .setContentText("Hello World ! ")
- .setContentIntent(mPendingIntent)
- .build();
- // 注意:提供給 startForeground() 的整型 ID 不得為 0。
- // 要從前臺移除服務(wù),請調(diào)用 stopForeground()。此方法采用一個布爾值,指示是否也移除狀態(tài)欄通知。
- // 然而stopForeground()不會停止服務(wù)。 但是,如果您在服務(wù)正在前臺運行時將其停止,則通知也會被移除。
- startForeground(1, mNotification);
- }
其實這里的實現(xiàn)很簡單,就是將一個Notification通過startForeground(1, mNotification);傳進去,從而將Notification與 Service建立起關(guān)聯(lián)。我們點擊這個通知,就會跳轉(zhuǎn)到第二個Activity(但是該Notification并不會消失),截圖如下: