android四大組件之Service
最近因?yàn)檫€沒找到工作所以也趁著現(xiàn)在有時(shí)間,將以前的只是整理下,要不然總?cè)菀走z忘,今天就來講解下Service的用法。作為Android的四大組件之一,其重要性可想而知。在應(yīng)用中我們主要是用來進(jìn)行一些后臺(tái)操作,不需與應(yīng)用UI進(jìn)行交互,執(zhí)行耗時(shí)任務(wù)等。
官方文檔中這樣說:
Service 是一個(gè)可以在后臺(tái)執(zhí)行長(zhǎng)時(shí)間運(yùn)行操作而不提供用戶界面的應(yīng)用組件。服務(wù)可由其他應(yīng)用組件啟動(dòng),而且即使用戶切換到其他應(yīng)用,服務(wù)仍將在后臺(tái)繼續(xù)運(yùn)行。
此外,組件可以綁定到服務(wù),以與之進(jìn)行交互,甚至是執(zhí)行進(jìn)程間通信 (IPC)。 例如,服務(wù)可以處理網(wǎng)絡(luò)事務(wù)、播放音樂,執(zhí)行文件 I/O
或與內(nèi)容提供程序交互,而所有這一切均可在后臺(tái)進(jìn)行。
Service的用途:
1.在后臺(tái)執(zhí)行耗時(shí)操作,但不需要與用戶進(jìn)行交互。2.一個(gè)應(yīng)用暴露出來的一些供其他應(yīng)用使用的功能。
這里需要聲明一點(diǎn),Service是運(yùn)行在主線程中,因而如果需要進(jìn)行耗時(shí)操作或者訪問網(wǎng)絡(luò)等操作,需要在Service中再開啟一個(gè)線程來執(zhí)行(使用IntentService的話則不需要在自己手動(dòng)開啟線程)。
啟動(dòng)Service
啟動(dòng)一個(gè)Service有兩種方式:
- Context.startService()
- Context.bindService()
(圖片截取自官方文檔:https://developer.android.com...)
startService()方式啟動(dòng)Service,我們啟動(dòng)之后是沒有辦法再對(duì)Service進(jìn)行控制的,而且啟動(dòng)之后該Service是一直在后臺(tái)運(yùn)行的,即使它里面的一些代碼執(zhí)行完畢,我們要想終止該Service,就需要在他的代碼里面調(diào)用stopSelf()方法或者直接調(diào)用stopService() 方法。而通過bindService()方法啟動(dòng)的Service,客戶端將獲得一個(gè)到Service的持久連接,客戶端會(huì)獲取到一個(gè)由Service的onBind(Intent)方法返回來的IBinder對(duì)象,用來供客戶端回調(diào)Service中的回調(diào)方法。
我們無論使用那種方法,都需要定義一個(gè)類,讓它繼承Service類,并重寫其中的幾個(gè)方法,如果我們是采用startService()方式啟動(dòng)的話,只需要重寫onCreate() 、onStartCommand(Intent intent, int flags, int startId)、onDestroy()方法即可(其實(shí)我們也可以重寫),而如果采用的是bindService()方法啟動(dòng)的話,我們就需要重寫onCreate() 、onBind(Intent intent)、 onUnbind(Intent intent)方法.注意,作為四大組件之一,Service使用之前要在清單文件中進(jìn)行配置。
- <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;
- }
- }
- }
主界面就兩個(gè)按鈕,一個(gè)用來啟動(dòng)Service,一個(gè)用來停止Service:
下面我們先點(diǎn)擊START按鈕,Log信息如下:
可以看出,onCreate()方法先執(zhí)行,然后onStartCommand()方法緊接著執(zhí)行,那么如果我們?cè)俅吸c(diǎn)擊啟動(dòng)按鈕呢?結(jié)果如下圖:
我們可以看到,這次onCreate()方法沒有再執(zhí)行,而是直接執(zhí)行了onStartCommand()方法,這是因?yàn)镾ervice只在***次創(chuàng)建的時(shí)候才執(zhí)行onCreate()方法,如果已經(jīng)創(chuàng)建了,那之后再次調(diào)用startService()啟動(dòng)該Service的時(shí)候,只會(huì)去執(zhí)行onStartCommand()方法方法,而不會(huì)再執(zhí)行onCreate()方法。
接下來我們點(diǎn)擊停止按鈕,可以看到,onDestroy()方法被執(zhí)行了:
注意,如果我們不點(diǎn)擊停止按鈕手動(dòng)停止該Service的話,該Service會(huì)一直在后臺(tái)運(yùn)行,即使它的onStartCommand()方法中的代碼已經(jīng)執(zhí)行完畢,在下圖中我們可以看到:
這時(shí)候我們的這個(gè)Service是一直在后臺(tái)執(zhí)行的,即使它的onStartCommand()方法中的代碼已經(jīng)執(zhí)行完了。如果我們想要它自動(dòng)停止的話,可以將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()
采用該方法的代碼就稍微比以前的多了,因?yàn)槲覀冃枰诳蛻舳藢?duì)Service進(jìn)行控制,因而會(huì)在MainActivity中創(chuàng)建一個(gè)匿名內(nèi)部類ServiceConnection,然后會(huì)在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ù),需開啟一個(gè)子線程,在其中執(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;
- }
- }
- }
點(diǎn)擊綁定按鈕;
點(diǎn)擊取消綁定按鈕:
注意,如果我們沒有先點(diǎn)擊綁定,而是直接點(diǎn)擊的取消綁定,程序會(huì)直接crash,報(bào)以下錯(cuò)誤:
- 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)
細(xì)心的你也許早就發(fā)現(xiàn)了,Log中并沒有打印"onServiceDisconnected executed !"這句,也就是說沒有調(diào)用onServiceDisconnected()方法?從字面理解,onServiceConnected()方法是在Service建立連接的時(shí)候調(diào)用的,onServiceDisconnected()不就應(yīng)該是在Service斷開連接的時(shí)候調(diào)用的嗎?其實(shí)不然,我們查看該方法的文檔就知道了:
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的連接丟失的時(shí)候,該方法會(huì)被調(diào)用,典型的情況就是持有該Service的進(jìn)程crash掉了,或者被殺死了。但是這并不會(huì)移除ServiceConnection 自身--它仍然是保持活躍狀態(tài),當(dāng)Service下次被執(zhí)行的時(shí)候,onServiceConnected(ComponentName, IBinder) 方法仍然會(huì)被調(diào)用。
但是要注意,如果我們按照剛才說的,不是先點(diǎn)擊 bindService()方法,而是直接點(diǎn)擊unbindService()方法,程序雖然也是crash掉了,但onServiceDisconnected()方法并不會(huì)被調(diào)用,這個(gè)很容易理解,畢竟都沒有建立連接呢,談何斷開連接啊。但是如果我們已經(jīng)綁定了Service,然后在后臺(tái)直接終止該Service呢?結(jié)果會(huì)怎樣?答案是onServiceDisconnected()方法仍然不會(huì)調(diào)用。這里我覺得應(yīng)該是只有在意外的情況下進(jìn)程結(jié)束,是由系統(tǒng)自動(dòng)調(diào)用的,而非我們手動(dòng)停止的。我們可以查看該方法內(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í)行的場(chǎng)景:異常情況下導(dǎo)致斷開了連接。也就是進(jìn)程crash掉了。因?yàn)樗\(yùn)行在我們應(yīng)用程序所在的進(jìn)程中,因而我們將永遠(yuǎn)不希望看到這種情況發(fā)生。
Context.startService()和Context.bindService()同時(shí)使用
這兩種方式是可以同時(shí)使用的,但是要注意,startService()和stopService()方法是對(duì)應(yīng)的,而bindService()和unBind()方法是對(duì)應(yīng)的,也就是說如果我們先調(diào)用startService()之后調(diào)用bindService()方法,或者相反,那么我們?nèi)绻徽{(diào)用stopService()或者只調(diào)用bindService()都無法停止該Service,只有同時(shí)調(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:
- // 啟動(dòng)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.下面是依次點(diǎn)擊start、bind、stop、unBind 按鈕的輸出結(jié)果:
b.下面是依次點(diǎn)擊start、bind、unbind、stop 按鈕時(shí)的輸出結(jié)果:
在前臺(tái)運(yùn)行服務(wù)
我們上面一直說Service一般是用來在后臺(tái)執(zhí)行耗時(shí)操作,但是要知道,Service也是可以運(yùn)行在前臺(tái)的。后臺(tái)Service的優(yōu)先級(jí)比較低,容在內(nèi)存不足等情況下被系統(tǒng)殺死,通過將其設(shè)置為前臺(tái),可以大大降低其被殺死的機(jī)會(huì)。前臺(tái)Service會(huì)在系統(tǒng)通知欄顯示一個(gè)圖標(biāo),我們可以在這里進(jìn)行一些操作。前臺(tái)Service比較常見的場(chǎng)景有音樂播放器和天氣預(yù)報(bào)等:
那么接下來我們就直接上代碼:
- @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。
- // 要從前臺(tái)移除服務(wù),請(qǐng)調(diào)用 stopForeground()。此方法采用一個(gè)布爾值,指示是否也移除狀態(tài)欄通知。
- // 然而stopForeground()不會(huì)停止服務(wù)。 但是,如果您在服務(wù)正在前臺(tái)運(yùn)行時(shí)將其停止,則通知也會(huì)被移除。
- startForeground(1, mNotification);
- }
其實(shí)這里的實(shí)現(xiàn)很簡(jiǎn)單,就是將一個(gè)Notification通過startForeground(1, mNotification);傳進(jìn)去,從而將Notification與 Service建立起關(guān)聯(lián)。我們點(diǎn)擊這個(gè)通知,就會(huì)跳轉(zhuǎn)到第二個(gè)Activity(但是該Notification并不會(huì)消失),截圖如下: