android四大組件之Service
最近因為還沒找到工作所以也趁著現在有時間,將以前的只是整理下,要不然總容易遺忘,今天就來講解下Service的用法。作為Android的四大組件之一,其重要性可想而知。在應用中我們主要是用來進行一些后臺操作,不需與應用UI進行交互,執行耗時任務等。
官方文檔中這樣說:
Service 是一個可以在后臺執行長時間運行操作而不提供用戶界面的應用組件。服務可由其他應用組件啟動,而且即使用戶切換到其他應用,服務仍將在后臺繼續運行。
此外,組件可以綁定到服務,以與之進行交互,甚至是執行進程間通信 (IPC)。 例如,服務可以處理網絡事務、播放音樂,執行文件 I/O
或與內容提供程序交互,而所有這一切均可在后臺進行。
Service的用途:
1.在后臺執行耗時操作,但不需要與用戶進行交互。2.一個應用暴露出來的一些供其他應用使用的功能。
這里需要聲明一點,Service是運行在主線程中,因而如果需要進行耗時操作或者訪問網絡等操作,需要在Service中再開啟一個線程來執行(使用IntentService的話則不需要在自己手動開啟線程)。
啟動Service
啟動一個Service有兩種方式:
- Context.startService()
- Context.bindService()
(圖片截取自官方文檔:https://developer.android.com...)
startService()方式啟動Service,我們啟動之后是沒有辦法再對Service進行控制的,而且啟動之后該Service是一直在后臺運行的,即使它里面的一些代碼執行完畢,我們要想終止該Service,就需要在他的代碼里面調用stopSelf()方法或者直接調用stopService() 方法。而通過bindService()方法啟動的Service,客戶端將獲得一個到Service的持久連接,客戶端會獲取到一個由Service的onBind(Intent)方法返回來的IBinder對象,用來供客戶端回調Service中的回調方法。
我們無論使用那種方法,都需要定義一個類,讓它繼承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()方法先執行,然后onStartCommand()方法緊接著執行,那么如果我們再次點擊啟動按鈕呢?結果如下圖:
我們可以看到,這次onCreate()方法沒有再執行,而是直接執行了onStartCommand()方法,這是因為Service只在***次創建的時候才執行onCreate()方法,如果已經創建了,那之后再次調用startService()啟動該Service的時候,只會去執行onStartCommand()方法方法,而不會再執行onCreate()方法。
接下來我們點擊停止按鈕,可以看到,onDestroy()方法被執行了:
注意,如果我們不點擊停止按鈕手動停止該Service的話,該Service會一直在后臺運行,即使它的onStartCommand()方法中的代碼已經執行完畢,在下圖中我們可以看到:
這時候我們的這個Service是一直在后臺執行的,即使它的onStartCommand()方法中的代碼已經執行完了。如果我們想要它自動停止的話,可以將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中創建一個匿名內部類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 !");
- // 執行具體的下載任務,需開啟一個子線程,在其中執行具體代碼
- }
- }
- }
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向下轉型為我們的內部類MyBinder
- myBinder = (MyService.MyBinder) iBinder;
- // 執行下載任務
- 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)
細心的你也許早就發現了,Log中并沒有打印"onServiceDisconnected executed !"這句,也就是說沒有調用onServiceDisconnected()方法?從字面理解,onServiceConnected()方法是在Service建立連接的時候調用的,onServiceDisconnected()不就應該是在Service斷開連接的時候調用的嗎?其實不然,我們查看該方法的文檔就知道了:
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.
意思就是:當綁定到該Service的連接丟失的時候,該方法會被調用,典型的情況就是持有該Service的進程crash掉了,或者被殺死了。但是這并不會移除ServiceConnection 自身--它仍然是保持活躍狀態,當Service下次被執行的時候,onServiceConnected(ComponentName, IBinder) 方法仍然會被調用。
但是要注意,如果我們按照剛才說的,不是先點擊 bindService()方法,而是直接點擊unbindService()方法,程序雖然也是crash掉了,但onServiceDisconnected()方法并不會被調用,這個很容易理解,畢竟都沒有建立連接呢,談何斷開連接啊。但是如果我們已經綁定了Service,然后在后臺直接終止該Service呢?結果會怎樣?答案是onServiceDisconnected()方法仍然不會調用。這里我覺得應該是只有在意外的情況下進程結束,是由系統自動調用的,而非我們手動停止的。我們可以查看該方法內部的注釋:
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.
這段文字清楚的說明了該方法執行的場景:異常情況下導致斷開了連接。也就是進程crash掉了。因為它運行在我們應用程序所在的進程中,因而我們將永遠不希望看到這種情況發生。
Context.startService()和Context.bindService()同時使用
這兩種方式是可以同時使用的,但是要注意,startService()和stopService()方法是對應的,而bindService()和unBind()方法是對應的,也就是說如果我們先調用startService()之后調用bindService()方法,或者相反,那么我們如果只調用stopService()或者只調用bindService()都無法停止該Service,只有同時調用才可以。
下面來看下具體代碼:
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向下轉型為我們的內部類MyBinder
- myBinder = (MyService.MyBinder) iBinder;
- // 執行下載任務
- 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 !");
- // 執行具體的下載任務
- }
- }
- }
a.下面是依次點擊start、bind、stop、unBind 按鈕的輸出結果:
b.下面是依次點擊start、bind、unbind、stop 按鈕時的輸出結果:
在前臺運行服務
我們上面一直說Service一般是用來在后臺執行耗時操作,但是要知道,Service也是可以運行在前臺的。后臺Service的優先級比較低,容在內存不足等情況下被系統殺死,通過將其設置為前臺,可以大大降低其被殺死的機會。前臺Service會在系統通知欄顯示一個圖標,我們可以在這里進行一些操作。前臺Service比較常見的場景有音樂播放器和天氣預報等:
那么接下來我們就直接上代碼:
- @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。
- // 要從前臺移除服務,請調用 stopForeground()。此方法采用一個布爾值,指示是否也移除狀態欄通知。
- // 然而stopForeground()不會停止服務。 但是,如果您在服務正在前臺運行時將其停止,則通知也會被移除。
- startForeground(1, mNotification);
- }
其實這里的實現很簡單,就是將一個Notification通過startForeground(1, mNotification);傳進去,從而將Notification與 Service建立起關聯。我們點擊這個通知,就會跳轉到第二個Activity(但是該Notification并不會消失),截圖如下:




































