1. 程式人生 > >Android Service、IntentService,Service和元件間通訊

Android Service、IntentService,Service和元件間通訊

Service元件

Service 和Activity 一樣同為Android 的四大元件之一,並且他們都有各自的生命週期,要想掌握Service 的用法,那就要了解Service 的生命週期有哪些方法,並且生命週期中各個方法回撥的時機和作用

什麼是service?service的基本概念

Service是Android中實現程式後臺執行的解決方案,非常適合用於去執行哪些不需要和使用者互動而且還要求長期執行的任務。不能執行在一個獨立的程序當中,而是依賴與建立服務時所在的應用程式程序。只能在後臺執行,並且可以和其他元件進行互動。

Service可以在很多場合使用,比如播放多媒體的時候使用者啟動了其他Activity,此時要在後臺繼續播放;比如檢測SD卡上檔案的變化;比如在後臺記錄你的地理資訊位置的改變等等,總之服務是藏在後臺的。

定義(啟動)一個Service

Service 有兩種啟動方式,並且它的兩種啟動方式的生命週期是不一樣的。

1.startService方式啟動Service

當應用元件通過startService方法來啟動Service 時,Service 則會處於啟動狀態,一旦服務啟動,它就會在後臺無限期的執行,生命週期獨立於啟動它的元件,即使啟動它的元件已經銷燬了也不受任何影響,由於啟動的服務長期執行在後臺,這會大量消耗手機的電量,因此,我們應該在任務執行完成之後呼叫stopSelf()來停止服務,或者通過其他應用元件呼叫stopService 來停止服務。

startService 啟動服務後,會執行如下生命週期:onCreate

() -> onStartCommand() -> onStart()(現在已經廢棄) -> onDestroy() 。具體看一下它的幾個生命週期方法:

  • onCreate() :首次啟動服務的時候,系統會呼叫這個方法,在onStartCommand 和 onBind 方法之前,如果服務已經啟動起來了,再次啟動時,則不會呼叫此方法,因此可以在onCreate 方法中做一些初始化的操作,比如要執行耗時的操作,可以在這裡建立執行緒,要播放音樂,可以在這裡初始化音樂播放器。

  • onStartCommand(): 當通過startService 方法來啟動服務的時候,在onCreate 方法之後就會回撥這個方法,此方法呼叫後,服務就啟動起來了,將會在後臺無限期的執行,直到通過stopService 或者 stopSelf 方法來停止服務。

  • onDestroy():當服務不再使用且將被銷燬時,系統將呼叫此方法。服務應該實現此方法來清理所有資源,如執行緒、註冊的偵聽器、接收器等。 這是服務接收的最後一個呼叫。  

瞭解了這幾個生命週期方法後,就來寫一個簡單Service 。

要使用Service 就要通過繼承Service類(或者繼承IntentService ,後文會講)來實現,程式碼如下:

package com.example.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {  
      
    public static final String TAG = "MyService";  
  
    //建立服務時呼叫
    @Override  
    public void onCreate() {  
        super.onCreate();  
        Log.d(TAG, "onCreate");  
    }  
  
    //服務執行的操作
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        Log.d(TAG, "onStartCommand");  
        return super.onStartCommand(intent, flags, startId);  
    }  
      
    //銷燬服務時呼叫
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        Log.d(TAG, "onDestroy");  
    }  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        return null;  
    }  
}

可以看到,我們只是在onCreate()、onStartCommand()和onDestroy()方法中分別列印了一句話,並沒有進行其它任何的操作,注意程式碼註釋中這三個方法的作用。

onBind()方法是Service中唯一的一個抽象方法,所以必須要在子類裡實現。我們知道,Service可以有兩種啟動方式:一種是startService(),另一種是bindService()。第二種啟動方式才會用到onBind()方法。我們這先用第一種方式啟動Service,所以暫時忽略onBind()方法。

(2)在清單檔案中宣告:(和Activity標籤並列)

<service android:name=".MyService">        </service>

(3)修改activity_main.xml程式碼,如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  
  
    <Button  
        android:id="@+id/button1_start_service"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:text="Start Service" />  
  
    <Button  
        android:id="@+id/button2_stop_service"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:text="Stop Service" />  
  
</LinearLayout>

 

我們在佈局檔案中加入了兩個按鈕,一個用於啟動Service,一個用於停止Service。

(4)在MainActivity作為程式的主Activity,在裡面加入啟動Service和停止Service的邏輯,程式碼如下:

package com.example.servicetest;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;


public class MainActivity extends Activity implements OnClickListener {  
      
    private Button button1_start_service;  
  
    private Button button2_stop_service;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        button1_start_service = (Button) findViewById(R.id.button1_start_service);  
        button2_stop_service = (Button) findViewById(R.id.button2_stop_service);  
        button1_start_service.setOnClickListener(this);  
        button2_stop_service.setOnClickListener(this);  
    }  
  
    @Override  
    public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.button1_start_service:  
            Intent startIntent = new Intent(this, MyService.class);  
            startService(startIntent);  
            break;  
        case R.id.button2_stop_service:  
            Intent stopIntent = new Intent(this, MyService.class);  
            stopService(stopIntent);  
            break;  
        default:  
            break;  
        }  
    }  
  
}

程式碼所示,在Start Service按鈕的點選事件裡,我們構建出了一個Intent物件,並呼叫startService()方法來啟動MyService。然後在Stop Serivce按鈕的點選事件裡,我們同樣構建出了一個Intent物件,並呼叫stopService()方法來停止MyService。程式碼的邏輯非常簡單。

小結:通過startService 方式啟動的服務,服務會無限期的在後臺執行,直到通過stopService 或 stopSelf 來終止服務。服務獨立於啟動它的元件,也就是說,當元件啟動服務後,元件和服務就再也沒有關係了,就算啟動它的元件被銷燬了,服務照樣在後臺執行。通過這種方式啟動的服務不好與元件之間通訊。

bindService 方式啟動服務

除了startService 來啟動服務之外,另外一種啟動服務的方式就是通過bindService 方法了,也就是繫結服務,其實通過它的名字就容易理解,繫結即將啟動元件和服務繫結在一起。前面講的通過startService 方式啟動的服務是與元件相獨立的,即使啟動服務的元件被銷燬了,服務仍然在後臺執行不受干擾。但是通過bindSerivce 方式繫結的服務就不一樣了,它與繫結元件的生命週期是有關的。如下:

多個元件可以繫結到同一個服務上,如果只有一個元件繫結服務,當繫結的元件被銷燬時,服務也就會停止了。如果是多個元件繫結到一個服務上,當繫結到該服務的所有元件都被銷燬時,服務才會停止。

bindService 繫結服務 和startService 的生命週期是不一樣,bindServie 的生命週期如下:onCreate -> onBind -> onUnbind ->onDestroy。其中重要的就是onBind 和onUnbind 方法。

  • onBind(): 當其他元件想通過bindService 與服務繫結時,系統將會回撥這個方法,在實現中,你必須返回一個IBinder介面,供客戶端與服務進行通訊,必須實現此方法,這個方法是Service 的一個抽象方法,但是如果你不允許繫結的話,返回null 就可以了。

  • onUnbind(): 當所有與服務繫結的元件都解除繫結時,就會呼叫此方法。

瞭解了這2個方法後,我們來看一下怎麼繫結一個服務。
1,首先,新增一個類 繼承 Binder ,在Binder 類中新增其他元件要與服務互動的方法,並在onBind() 方法中返回IBinder 例項物件:

public class SimpleService extends Service {
    public static final String TAG = "SimpleService";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG,"call onBind...");
        //返回IBinder 介面物件
        return new MyBinder();
    }
 
    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG,"call onUnbind...");
        return super.onUnbind(intent);
    }
 
    @Override
    public void onCreate() {
        Log.i(TAG,"call onCreate...");
    }
 
    @Override
    public void onStart(Intent intent, int startId) {
        Log.i(TAG,"call onStart...");
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG,"call onStartCommand...");
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Override
    public void onDestroy() {
        Log.i(TAG,"call onDestroy...");
    }
 
    // 新增一個類繼承Binder
    public  class MyBinder extends Binder{
        // 新增要與外界互動的方法
        public String  getStringInfo(){
          return "呼叫了服務中的方法";
        }
    }
 
}

2, 繫結服務的時候,需要提供一個ServiceConnection 介面,在介面回撥中獲取Binder 物件,與服務進行通訊。

 private SimpleService.MyBinder mMyBinder;
    // 繫結/解除繫結 Service 回撥介面
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 繫結成功後回撥
            //1 ,獲取Binder介面物件
            mMyBinder = (SimpleService.MyBinder) service;
            //2, 從服務獲取資料
            String content = mMyBinder.getStringInfo();
            // 3,介面提示
            Toast.makeText(ServiceSimpleActivity.this,content,Toast.LENGTH_LONG).show();
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
           // 解除繫結後回撥
            mMyBinder = null;
        }
    };

3,繫結和解除繫結服務

            case R.id.bind_service:
                Intent intent = new Intent(this,SimpleService.class);
                // 繫結服務
                bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                // 解除繫結服務
                unbindService(mConnection);
 
                break;

可以看到,繫結服務的生命週期內依次呼叫了onCreate ,onBind,onUnbind 和 onDestroy 方法,只有中間兩個生命週期方法與startService 啟動服務是不同的。

兩種方式的生命週期的異同:

         Service生命週期.png

started服務與bind服務的區別:

區別一:生命週期

  • 通過started方式的服務會一直執行在後臺,需要由元件本身或外部元件來停止服務才會以結束執行
  • bind方式的服務,生命週期就要依賴繫結的元件

區別二:引數傳遞

  • started服務可以給啟動的服務物件傳遞引數,但無法獲取服務中方法的返回值
  • bind服務可以給啟動的服務物件傳遞引數,也可以通過繫結的業務物件獲取返回結果

實際開發中的技巧;

  • 第一次先使用started方式來啟動一個服務
  • 之後可以使用bind的方式繫結服務,從而可以直接呼叫業務方法獲取返回值

IntentService

IntentService 是Service 的子類,它使用工作執行緒逐一處理所有啟動請求,果您不要求服務同時處理多個請求,這是最好的選擇。 您只需實現 onHandIntent方法即可,該方法會接收每個啟動請求的 Intent,使您能夠執行後臺工作。

IntentService 示例

IntentService 預設為我們開啟了一個工作執行緒,在任務執行完畢後,自動停止服務,因此在我們大多數的工作中,使用IntentService 就夠了,並且IntentService 比較簡單,只要實現一個方法OnHandleIntent,接下來看一下示例:

(1)新建一個MyIntentService類,繼承自IntentService,並重寫父類的onHandleIntent()方法,程式碼如下:

複製程式碼
 1 package com.example.servicetest;
 2 
 3 import android.app.IntentService;
 4 import android.content.Intent;
 5 import android.util.Log;
 6 
 7 public class MyIntentService extends IntentService{
 8 
 9     public MyIntentService() {
10         super("MyIntentService");//呼叫父類有參建構函式。這裡我們手動給服務起個名字為:MyIntentService
11         // TODO Auto-generated constructor stub
12     }
13 
14     //該方法在會在一個單獨的執行緒中執行,來完成工作任務。任務結束後,該Service自動停止
15     @Override
16     protected void onHandleIntent(Intent intent) {
17         // TODO Auto-generated method stub
18         for(int i = 0;i<3;i++) {
19             //列印當前執行緒的id
20             Log.d("MyIntentService","IntentService執行緒的id是:"+Thread.currentThread().getId());
21             try {
22                 Thread.sleep(1000);
23             } catch (InterruptedException e) {
24                 // TODO Auto-generated catch block
25                 e.printStackTrace();
26             }
27         }        
28     }
29 
30     @Override
31     public void onDestroy() {
32         // TODO Auto-generated method stub
33         super.onDestroy();
34         Log.d("MyIntentService","onDestroy");
35     }
36 }
複製程式碼

這裡首先要提供一個無參的構造方法,並且必須在其內部呼叫父類的有參構造方法(9至12行),我們在第10行手動將服務的名字改為“MyIntentService”。

然後在子類中實現onHandleIntent()這個抽象方法,可以在這個方法裡去處理一些具體的邏輯,我們就用三次for迴圈,列印當前執行緒的id,每次延時1秒。

因為這個服務在執行結束後會自動停止,所以我們在onDestroy()方法中列印日誌驗證一下。

(2)在清單檔案中對服務進行註冊服務:

<service android:name=".MyIntentService"> </service>

(3)在activity_main.xml中新增一個按鈕button3_stop_intentservice,用於啟動MyIntentService服務,程式碼略。

(4)在MainActivity裡面加入啟動IntentService的邏輯,核心程式碼如下:

1       case R.id.button3_stop_intentservice:
2             Log.d("MainActivity","主執行緒的id是:"+Thread.currentThread().getId());
3             Intent intentService = new Intent(this,MyIntentService.class);
4             startService(intentService);
5         default:

我們在第02行中,列印主執行緒的id。

執行程式,點選按鈕button3_stop_intentservice,顯示如下:

由此可見,啟動一個IntentService和啟動一個普通的Service,步驟是一樣的。

4、Service和Thread的關係:

不少Android初學者都可能會有這樣的疑惑,Service和Thread到底有什麼關係呢?什麼時候應該用Service,什麼時候又應該用Thread?答案可能會有點讓你吃驚,因為Service和Thread之間沒有任何關係!

之所以有不少人會把它們聯絡起來,主要就是因為Service的後臺概念。Thread我們大家都知道,是用於開啟一個子執行緒,在這裡去執行一些耗時操作就不會阻塞主執行緒的執行。而Service我們最初理解的時候,總會覺得它是用來處理一些後臺任務的,一些比較耗時的操作也可以放在這裡執行,這就會讓人產生混淆了。但是,如果我告訴你Service其實是執行在主執行緒裡的,你還會覺得它和Thread有什麼關係嗎?

其實,後臺和子執行緒是兩個完全不同的概念:

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,就可以寫成本段中第1節的樣子。

IntentService總結:IntentService是Service 的子類,預設給我們開啟了一個工作執行緒執行耗時任務,並且執行完任務後自 動停止服務。擴充套件IntentService比較簡單,提供一個構造方法和實現onHandleIntent 方法就可了,不用重寫父類的其他方法。但是如果要繫結服務的話,還是要重寫onBind 返回一個IBinder 的。使用Service 可以同時執行多個請求,而使用IntentService 只能同時執行一個請求。

Service 與應用元件通訊的幾種方式

1,BroadcastReceiver
通過前文我們知道,startService方式啟動的服務在後臺,無限期地執行,並且與啟動它的元件是獨立的,啟動Service 之後也就與啟動它的元件沒有任何關係了。因此它是不能與啟動它的元件之間相互通訊的。雖然Service 沒有提供這種啟動方式的通訊方法,我們還是可以通過其他方式來解決的,這就用到了BroadcastReceiver。

場景描述:通過startService 啟動一個長期在後臺執行的下載圖片服務,然後在介面上點選下載按鈕,通過intent 傳遞一個下載連結給Service,在下載完成後,通過BroadcastReceiver 通知Activity 介面顯示圖片。看一下程式碼實現:

Service程式碼如下:

public class DownloadService extends Service {
    public static final String IMAGE = "iamge_url";
    public static final String RECEIVER_ACTION = "com.zhouwei.simpleservice";
    private static final String TAG = "DownloadService";
    public static final String ACTION_START_SERVICER = "com.zhouwei.startservice";
    public static final String ACTION_DOWNLOAD = "com.zhouwei.startdownload";
    private Looper mServiceLooper;
    private ServiceHandler mServiceHandler;
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper){
            super(looper);
        }
 
        @Override
        public void handleMessage(Message msg) {
            // 工作執行緒做耗時下載
            String url = (String) msg.obj;
            Bitmap bitmap = null;
            try {
                bitmap = Picasso.with(getApplicationContext()).load(url).get();
                Intent intent = new Intent();
                intent.putExtra("bitmap",bitmap);
                intent.setAction(RECEIVER_ACTION);
                // 通知顯示
                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
            } catch (IOException e) {
                e.printStackTrace();
            }
 
 
            //工作完成之後,停止服務
            stopSelf();
        }
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onCreate() {
        // 開啟一個工作執行緒做耗時工作
        HandlerThread thread = new HandlerThread("ServiceHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        // 獲取工作執行緒的Looper
        mServiceLooper = thread.getLooper();
        // 建立工作執行緒的Handler
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG,"call onStartCommand...");
        if(intent.getAction().equals(ACTION_DOWNLOAD)){
            handleCommand(intent);
        }else if(intent.getAction().equals(ACTION_START_SERVICER)){
            //do nothing
        }
 
        return START_STICKY;
    }
 
    private void handleCommand(Intent intent){
        String url = intent.getStringExtra(IMAGE);
        // 傳送訊息下載
        Message message = mServiceHandler.obtainMessage();
        message.obj = url;
        mServiceHandler.sendMessage(message);
    }
}

新建了一個DownloadService ,在裡面啟動了一個工作執行緒,線上程裡下載圖片,然後通過BroadcastReceiver 通知Activity顯示。

Activity的程式碼很簡單,註冊BroadcastReceiver,在onReceiver中顯示圖片就好了,程式碼如下:

private ImageView mImageView;
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 顯示圖片
            Bitmap bitmap = intent.getParcelableExtra("bitmap");
            mImageView.setImageBitmap(bitmap);
        }
    };

    /**
     * 啟動下載
     */
    private void startDownload(){
        Intent intent = new Intent(this,DownloadService.class);
        // 啟動服務
        intent.putExtra(DownloadService.IMAGE,"http://www.8kmm.com/UploadFiles/2012/8/201208140920132659.jpg");
        intent.setAction(DownloadService.ACTION_DOWNLOAD);
        startService(intent);
    }

 

 

 

 

 

 

 

 

 

 

宣告

https://blog.csdn.net/qq_34115898/article/details/83347882

文章來源