android學習筆記——Service服務初探

文章轉載自:http://android.jobbole.com/80853/    Service服務只是本篇博文的一小部分。
也有一些筆者自己的理解。

service是四大組件之一比較重要的一部分,它一般在後臺運行,不予用戶交互,主要執行一些耗時操作。當然,如果你需要的話,也可以讓它變成前臺運行。

服務的種類(按啓動方式來分)

根據服務的啓動方式來分,可分爲兩種
1.通過startService啓動
2.通過bindService啓動

簡單的服務StartService

通過這種方式啓動的服務屬於比較標準的一類。一般來說,從定義一個服務到使用一個服務,要遵循一下幾個步驟:

  1. 自己實現一個類,該類繼承service類
  2. 在清單文件中註冊自己的服務(實際上,四大組件的使用,都需要在清單文件中進行註冊)
  3. 調用startservice來初始化並且啓動自己的服務
  4. 調用stopservice來停止我們已經運行的服務。

通過上面的圖,我們也可以觀察到,這種服務的生命週期是怎樣的:


首先,通過startservice來啓動服務,如果我們是第一次啓動服務的話,需要調用oncreate函數來做一些初始化的操作,並且調用 start方法來開始執行服務的核心代碼(新版本中start已經被替換成onstartcommand函數)。如果我們多次調用 startservice方法的話,那麼oncreate也不會再被調用,重複調用的只有onstart方法,它會處理傳遞給startvice的 intent.如果服務中有大量的耗時操作,比如在網絡上下載圖片等,則可以在Onstart函數裏面單獨開一個線程來執行這些操作,這樣做可以不讓我們 的主線程阻塞。最後,如果我們想停止這個服務,可以調用stopservice,這個函數第一次被調用的時候,會執行ondestory函數來釋放程序在 oncreate函數中所創建的資源等等。當然,你也可以在服務的內部調用stopself函數來停止服務的運行。
下面來看看代碼:
MyService.java
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}

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/start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service" />
<Button
android:id="@+id/stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service" />
</LinearLayout>

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

千萬記得在註冊清單中對服務進行註冊
<service android:name=".MyService" >
</service>

BindService與Binder
之前的服務,只要在活動中調用startservice之後,服務就自己去運行了,而服務 具體做了什麼,在活動內是不知道的,在活動內,除了調用兩個API來啓動和停止服務之外,就不能再對服務有所幹涉了。如果我們想獲取到服務執行過程中的一 些情況的話,就需要服務和活動進行通信,這也就需要用到我們進程間通訊機制的一種—Binder.

在之前實現service類的時候,我們可以看到有一個抽象方法被重新實現—onBind()。service和activity的通信大致是通過這樣的一種過程來實現的:

  1. 首先在service的類中,實現一個類,這個類繼承於Binder,這個類中你可以定義一些你自己的方法,這些方法就是以後要在服務中執行的方 法,這個類是服務類的子類。然後,創建一個這個binder類的實例,在Onbind函數中返回這個binder對象。這個對象就像是一個開着的接口,向 外提供了子類內部將要執行的操作。
  2. 服務對外通信的接口做好之後,想讓活動和服務建立連接的話,就要在兩者之間創建一個連接點。這個連接點的工作由 ServiceConnection類的對象來實現,我們要在activity中創建一個ServiceConnection的匿名類,類中要重寫兩個方 法: onServiceDisconnected,onServiceConnected。之後在創建一個這個類的對象,到此爲止,連接點也做好了,那麼如何 讓服務和活動通過這個連接點連接起來進行通訊工作呢?
  3. 因爲在實現服務的時候,給我們返回了一個Binder的對象作爲接口,可以看見在重寫onServiceConnected方法的時候,裏面有一 個參數就是Binder類型的參數,通過把這個Binder對象強制類型轉換成我們在服務中自己實現binder類的那個對象的類類型之後,就可以通過它 來調用我們服務內部的一些操作。這就爲活動來指揮服務內部的運行,提供了良好的接口和條件。
  4. 而對於acitivity來說的話,我們可以通過bindservice這個啓動服務的函數,在參數中把要啓動服務的intent和創建的連接點 ServiceConnection對象傳遞過去,如此一來,在綁定成功之後就可以創建服務並且連接在一起,然後自動調用連接點中 onServiceconnected方法,在這個方法的內部就可以通過參數中的binder對象來指揮服務。在服務和活動解除綁定的時候,自然也會調用 連接點處的onServiceDisconnected方法。
  5. 我們在活動中,現在通過Binder對象所能執行的是service類的子類所提供的方法,如果想執行service類內部的方法,可以在這個接口中再定義一個函數,返回服務類的一個對象,便可以執行服務類中的成員方法了。

那麼BinderService的生命週期就由以下幾個部分構成:


onbindservice方法來創建一個持久的連接,它會在第一次調用的時候,調用oncreate來做一些服務的初始化的操作。之後自動調用 onbind函數,向服務的外部返回Binder對象,這算是一個服務面向外部的接口。之後,通過serviceconnection對象的作用,活動和 服務便能夠成功綁定並且可以執行相應的操作,如果想要停止服務,可以調用unbindservice函數來解除之前建立的連接,這會直接調用服務內部的 ondestory函數。

下面來看一下代碼:
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    public static final int UPDATE_TEXT 1;

    private Button start_Service;
    private Button stopService;
    private Button bindService;
    private Button unbindService;
    private Button changeUI;
    private TextView tvContent;
    private MyService.DownloadBinder downloadBinder;

    private ServiceConnection connection new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    start_Service = (Button) findViewById(R.id.start_service);
    stopService = (Button) findViewById(R.id.stop_service);
    bindService = (Button) findViewById(R.id.bind_service);
    unbindService = (Button) findViewById(R.id.unbind_service);
    changeUI = (Button) findViewById(R.id.changeUI);
    tvContent = (TextView) findViewById(R.id.tv_content);

    start_Service.setOnClickListener(this);
    stopService.setOnClickListener(this);
    bindService.setOnClickListener(this);
    unbindService.setOnClickListener(this);
    changeUI.setOnClickListener(this);
}
 @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start_service:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent);
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);
                break;
            case R.id.bind_service:
                Intent bindIntent = new Intent(this, MyService.class);
                bindService(bindIntent, connectionBIND_AUTO_CREATE);//綁定服務
                break;
            case R.id.unbind_service:
                unbindService(connection);//解綁服務
                break;
            default:
                break;
        }
    }
}

MyService.java
public class MyService extends Service {

    private DownloadBinder mBinder new DownloadBinder();

    class DownloadBinder extends Binder {
        public void startDownload() {
            Log.d("MyService""startDownload executed");
        }

        public void stopDownload() {
            Log.d("MyService""stopDownload executed");
        }

        public int getProgress() {
            Log.d("MyService""getProgress executed");
            return 0;
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyService""onCreate executed");
        Notification notification = new Notification.Builder(this)
                .setContentTitle("This is Title")
                .setContentText("This is content")
                .setSmallIcon(R.mipmap.ic_launcher)
                .build();
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this0, notificationIntent, 0);
        startForeground(1, notification);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService""onStartCommand executed");

        new Thread(new Runnable() {
            @Override
            public void run() {
                //線程邏輯
                Log.d("MyService Thread","Thread executed");
                stopSelf();//終止自己的線程
            }
        }).start();//開啓線程
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyService""onDestory executed");
    }
}

佈局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/start_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Staet Service" />

    <Button
        android:id="@+id/stop_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop Service" />

    <Button
        android:id="@+id/bind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bind Service" />

    <Button
        android:id="@+id/unbind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="UnBind Service" />
    <Button
        android:id="@+id/changeUI"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Change UI"/>
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="30sp"
        android:text="Hello World"/>

</LinearLayout>



還是要記得在註冊文件中對服務進行註冊
<service android:name=".MyService" />


關於服務的生命週期
一旦在項目的任何位置調用了 Context startService()方法,相應的服務就會啓動起來,並回調 onStartCommand()方法。如果這個服務之前還沒有創建過,onCreate()方法會先於onStartCommand()方法執行。服務啓動了之後會一直保持運行狀態,直到 stopService()或stopSelf()方法被調用。注意雖然每調用一次 startService()方法,onStartCommand()就會執行一次,但實際上每個服務都只會存在一個實例。所以不管你調用了多少次 startService()方法,只需調用一次 stopService() stopSelf()方法,服務就會停止下來了。
另外,還可以調用 Context bindService()來獲取一個服務的持久連接,這時就會回調服務中的 onBind()方法。類似地,如果這個服務之前還沒有創建過,onCreate()方法會先於onBind()方法執行。之後,調用方可以獲取到 onBind()方法裏返回的 IBinder 對象的實例,這樣就能自由地和服務進行通信了。只要調用方和服務之間的連接沒有斷開,服務就會一直保
持運行狀態。
當調用了 startService()方法後,又去調用 stopService()方法,這時服務中的 onDestroy()方法就會執行,表示服務已經銷燬了。類似地,當調用了 bindService()方法後,又去調用unbindService()方法,onDestroy()方法也會執行,這兩種情況都很好理解。但是需要注意我們是完全有可能對一個服務既調用了 startService()方法,又調用了 bindService()方法的,這種情況下該如何才能讓服務銷燬掉呢?根據 Android 系統的機制,一個服務只要被啓動或者被綁定了之後,就會一直處於運行狀態,必須要讓以上兩種條件同時不滿足,服務才能被銷燬。所以,這種情況下要同時調用 stopService()unbindService()方法,onDestroy()方法才會執行。


服務的更多技巧
可以將服務放到前臺來(像墨跡天氣、音樂播放器在通知欄的用例)
(在服務開啓時onCreate()方法中創建一個通知到前臺)

也可以將服務設置爲開機自啓(其實是使用了廣播方式,利用廣播接收器來開啓服務or應用)

IntentService自動開啓和關閉服務

服務的運行一般來說關心兩個問題:

  1. 如果處理服務中的耗時操作
  2. 如果在服務中的邏輯運行完畢之後,自動關閉該服務。
對於第一個問題來說,我們可以在服務的邏輯方法中新建一個子線程,讓它來完成耗時操作。
對於第二個問題來說,我們可以在服務的運行邏輯的末尾,加上stopself以便讓它在邏輯完成時,結束服務。

那麼能否有一種service可以不用我們自己手動單獨開闢線程,自動的來完成我們的耗時操作呢,並且在服務的邏輯處理完成之後,可以自動的幫我們結束服務,不用我們自己再調用stopself。這種服務就是intentservice。

intentservice的使用方法和普通的service是一樣的,但是在intentservice的實現中,我們需要繼承自intentservice的類,並且要爲這個類重載一個沒有參數的構造方法。具體的實現步驟有以下幾個比較重要的:

  1. 繼承Intentservice並提供一個無參數的構造函數,在方法內部調用父類的構造函數。
  2. 重寫父類當中onhandleintent這個方法,把我們要在服務當中處理的邏輯過程都放在這個方法內完成
  3. 啓動這個service。 這個服務的特殊之處就在於onhandleintent這個方法,這個方法能夠代替我們手動進行的兩個工作:這個方法內部的代碼都是在子線程中運行的,並 且在這個方法執行到末尾的時候,會自動的幫我們關閉service。這樣一來,就不用我們再手動的開子線程和關閉服務了。其實在 intentservice中也是通過開啓子線程來完成服務內的邏輯過程的執行的。
來看一下示例代碼:
MyIntentService.java
public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d("MyIntentService","onHandleIntent executed ,Thread id is "+Thread.currentThread());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyIntentService","onDestroy executed");
    }
}
MainActivity.java
case R.id.start_intent_service:
    Log.d("MainActivity","MainActivity Thread id is "+Thread.currentThread());
    Intent intentService = new Intent(this,MyIntentService.class);
    startService(intentService);
    break;
增加了一個Button,點擊時觸發MyIntentService服務。
別忘記在註冊表中註冊服務。

StartService和BindService的區別
  1. 如果是使用startservice的方式來啓動服務的話,即使啓動這個服務的activity可能處於destory狀態,但是服務本身還是在繼續運行的,除非這個服務所在進程被銷燬,那麼這個服務也會停止。
  2. 如果是使用bindservice來啓動服務的話,一旦啓動這個服務的activity變爲destory的狀態的時候,那麼這個服務也隨之會銷燬,因爲這個服務是和特定的activity進行綁定的。
  3. startservice一旦啓動之後,activity就不能再插手服務運行過程中的事情,比如我想了解服務運行的狀態等等,都不能夠實現,也就是說,activity只能是給服務發一個信號來啓動他,但是並不能和它進行交互。
  4. bindservice就不一樣了,它是依靠binder進程間通訊的機制,在服務本身內部向外提供接口,以便讓activity能夠和服務進行交互操作,這樣一來,activity就可以在服務運行的過程當中來查看服務運行的情況。
  5. bindservice雖然向外部提供了一個binder的對象,通過這個對象可以調用service內部的一些方法,但是在activity 中,通過binder調用的函數並不是直接一步到位調用了服務中的相關成員函數,而只是得到一個類似指針和映射的東西,通過這些再去找在服務內部真正存在 的方法。總的來說把,bindservice向外部提供的,只是內部信息的一個映射,並不是真正的把內部的函數地址等等信息提供出去。
  6. 如果有一個service和多個activity同時綁定的話,那麼即使一個activity解除了service的綁定,但是並不會回調 unbind和ondestory的方法,只不過在綁定多個actiity的時候要注意綁定的順序。> 其實bind和start兩種服務最本質的區別就是,Bind是一種服務器/客戶端模型,能夠讓服務的啓動者和服務的執行者交互,但是start不行。

Service到底是什麼?進程 ?線程?

這個問題也是自己剛剛遇到的,一開始有點模糊不清,包括不明白service爲什麼和調用他的activity是在同一個主線程中的。網上的資料, 大家還是最好有一點甄別能力,尤其是論壇上面的,有些明顯的錯誤來誤導提問者。當然,我寫的東西可能也是有錯的,如果有不同意見,大家可以一起討論。 我對這個問題的理解是這樣的:<br> 首先,service不是一線程,也不是一個進程,它是一個組件。它的生存運行,和線程的生命週期沒有一點關係。也就是說,線程的死活和它是沒有關係的。 但是,service的運行,畢竟需要一些條件,比如內存,CPU這些資源。然而,這些資源,操作系統只會分配給進程,這也就是爲什麼,不管我們用什麼種 類的service,只要我們把這個進程幹掉,服務神馬的也就都是浮雲了。所以,服務雖然是一個組件,但是它的運行還是要依賴於進程。

服務分爲遠程服務和本地服務,本地服務就是在我們這個應用進程之內啓動並運行的。一般來說,服務的運行並不會新開一個線程,無論是哪一種service他們都是運行在進程中的主線程中。這也就是爲什麼不能在服務中直接執行大量的耗時操作,要單獨新開一個子線程完成的原因。

還有一個問題:startservice在啓動之後,其實activity銷燬了,但是它還是繼續在運行,直到我們顯式的調用 stopservice或者stopself,他纔會停止。而且,即使我們在服務運行的過程中stopservice,但是服務還是會完成這一次的邏輯, 也就是說,我們在一個服務執行的過程中,不能中斷本次執行的這個過程。這一點對於Bindservice和startservice都是一樣。


但是bindeservice在activity銷燬之後,它自己也會跟着銷燬,我們可能會覺得這是因爲進程或者線程的生命週期的關係,其實,這是 不正確的一個是,按下back鍵使activity銷燬,進程還有被殺死,還有一個就是,線程的生命週期和服務壓根一點關係沒有。之所以 bindservice被銷燬了,是因爲它綁定的是activity,而不是什線程,activity銷燬了,那麼綁定它上面的service也就會銷燬 了。startservice沒有銷燬是因爲它沒有和人和activity綁定。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章