1. 程式人生 > 其它 >Android Service基本用法

Android Service基本用法

Service的概念

  • 1.Service作為安卓的四大元件之一,固然是每一位安卓開發者必須掌握的一個知識點。雖然它沒有Activity的使用頻繁,但也是日常開發經常用到的。

  • 2.通過名字我們知道,它是服務的意思。而且通常是"默默"的為我們服務的。為什麼說是默默,因為它並不像Activity一樣,能夠被我們看到。通常,它用於在後臺為我們執行一些耗時,或者需要長時間執行的一些操作的。下面讓我們來看看它的基本用法。

Service的建立

  • 1.任何一個物件,想要發揮其作用,那麼就應該首先創建出來。Service的建立和Activity類似,也是通過Intent來實現的。而且既然是安卓四大元件之一,那麼它也需要在清單檔案中進行註冊的。下面,看一下一個簡單的建立Service的例子:
public class SimpleService extends Service {

    public static final String TAG = "SimpleService";

    @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;
    }
}
  • 2.首先,建立一個類SimpleService繼承自Service,然後重寫它的onCreate,onStartCommand,onDestroy方法,並分別在它們的方法體中打入Log日誌。其中onBind方法是預設實現的,具體作用後面會講到。然後呢,千萬不要忘記要在清單檔案中註冊它,其實如果你是通過Android Studio直接new了一個Service的話,Android Studio會預設幫助你在清單檔案中新增對該Service的註冊,程式碼如下:
<service android:name=".ui.main.SimpleService"
            android:enabled="true"
            android:exported="true"/>

我直接在我之前做的一個專案中新建的,所以不要在意包路徑的命名,就是包名點類名。細心的朋友可能看到了下面還有兩個屬性。其中enabled屬性,是指該服務是否能夠被例項化。如果設定為true,則能夠被例項化,否則不能被例項化,預設值是true。一般情況下,我們都會需要例項化,所以也可以選擇不設定。而exported屬性用於指示該服務是否能夠被其他應用程式元件呼叫或跟它互動。如果設定為true,則能夠被呼叫或互動(通常如果一個服務需要跨程序使用需要這麼設定),否則不能。設定為false時,只有同一個應用程式的元件或帶有相同使用者ID的應用程式才能啟動或繫結該服務。

  • 3.接下來建立一個StartActivity,用於在其中建立SimpleService物件。程式碼如下:
public class StartActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startBtn, stopBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);

        startBtn = (Button) findViewById(R.id.btn_start_service);
        stopBtn = (Button) findViewById(R.id.btn_stop_service);
     
        startBtn.setOnClickListener(this);
        stopBtn.setOnClickListener(this);  
    }

    @Override
    public void onClick(View v) {
        if (v != null) {
            switch (v.getId()) {
                case R.id.btn_start_service:
                    Intent startIntent = new Intent(this, SimpleService.class);
                    startService(startIntent);
                    break;
                case R.id.btn_stop_service:
                    Intent stopIntent = new Intent(this, SimpleService.class);
                    stopService(stopIntent);
                    break;          
            }
        }
    }
}

StartActivity的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"
    tools:context="com.kitty.android.ui.main.StartActivity">

    <Button
        android:id="@+id/btn_start_service"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="Start Service"/>
    <Button
        android:id="@+id/btn_destroy_service"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="Destroy Service"/>
</LinearLayout>
  • 4.很簡單,就是新增兩個按鈕,分別用於啟動Service和停止Service。可以看到,建立一個Service的方法非常簡單,就是和建立Activity類似,建立一個Intent物件,然後通過startService方法開啟一個服務。然後同樣的,通過stopService方法來停止一個服務。為了證明方法的正確性,我們先點選startBtn,看一下Logcat中的日誌截圖,如下所示:

servicestart.png

由此可以看出,當通過startService方法開啟一個服務的時候,會執行Service的onCreate和onStartCommand方法。接下來,我們再次點選一下 start按鈕,再來看一下Loacat的日誌:

restartservice.png

這一次,只執行了onStartCommand方法,由此我們可以得出結論,當一個Service被建立以後,再次呼叫startService方法,Service是不會被重新建立的,而是會重新執行onStartCommand方法。無論我們點選多少次start按鈕,始終只會執行onStartCommand方法。

以上是服務的建立。接下來,我們點選stop按鈕,logcat日誌如下:

stopservice.png

可以看到,Service執行了onDestroy方法,這時服務就已經停止了。
以上就是簡單的建立一個服務的流程。然而,在我們日常開發中,我們經常需要在服務中做一些邏輯操作,然後將結果返回給一個Activity,即要實現Service和Activity的通訊,接下來,讓我們看看如何讓二者建立起聯絡。

Service與Activity之間的通訊

在上面的介紹中,我們只是實現了在Activity中開啟一個服務,然而服務開啟了就和這個Activity沒什麼聯絡了。其實二者是可以繼續保持聯絡的,還記得前面提到的一個onBind方法吧,其實它就是Service與Activity之間建立通訊的橋樑。現在我們修改一下前面的程式碼,SimpleService程式碼修改如下:

public class SimpleService extends Service {

    public static final String TAG = "SimpleService";

    private SimpleBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        mBinder = new SimpleBinder();
    }

    @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) {
        if (mBinder != null) {
            return mBinder;
        }
        return null;
    }

    class SimpleBinder extends Binder {

        public void doTask() {
            Log.d(TAG, "doTask");
        }
    }
}

現在,我們在SimpleService中建立了一個SimpleBinder類,繼承自Binder。然後,在裡面建立了一個doTask方法,模擬執行一個任務。然後,我們再在StartActivity中加入兩個按鈕,分別用於繫結服務和解綁服務,XML檔案程式碼我就不貼了,就是添兩個按鈕,下面是StartActivity的更改後的程式碼:

public class StartActivity extends AppCompatActivity implements View.OnClickListener {

    public static final String TAG = "SimpleService";

    private Button startBtn, stopBtn, bindBtn, unBindBtn;

    private SimpleService.SimpleBinder mBinder;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, name.toString());
            mBinder = (SimpleService.SimpleBinder) service;
            mBinder.doTask();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, name.toString());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);

        startBtn = (Button) findViewById(R.id.btn_start_service);
        stopBtn = (Button) findViewById(R.id.btn_stop_service);
        bindBtn = (Button) findViewById(R.id.btn_bind_service);
        unBindBtn = (Button) findViewById(R.id.btn_un_bind_service);

        startBtn.setOnClickListener(this);
        stopBtn.setOnClickListener(this);
        bindBtn.setOnClickListener(this);
        unBindBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v != null) {
            switch (v.getId()) {
                case R.id.btn_start_service:
                    Intent startIntent = new Intent(this, SimpleService.class);
                    startService(startIntent);
                    break;
                case R.id.btn_stop_service:
                    Intent stopIntent = new Intent(this, SimpleService.class);
                    stopService(stopIntent);
                    break;
                case R.id.btn_bind_service:
                    Intent bindIntent = new Intent(this, SimpleService.class);
                    bindService(bindIntent, mConnection, BIND_AUTO_CREATE);
                    break;
                case R.id.btn_un_bind_service:
                    unbindService(mConnection);
                    break;
            }
        }
    }
}

這裡,我們建立了一個ServiceConnection的匿名內部類,並實現了onServiceConnected和onServiceDisconnected兩個方法。ServiceConnection可以看做是一個由Activity操作的代表,負責與Service進行連線,當Activity與Service連線成功時,會執行onServiceConnected方法,相反的,當二者斷開連線的時候,會執行onServiceDisconnected方法。當二者連線成功時,在onServiceConnected方法中,我們可以獲取到SimpleService中的SimpleBinder的例項物件,然後我們就可以呼叫其所有的公共方法來實現我們想要做的事了。

現在我們來看一下繫結一個服務的方法,也是先建立一個Intent,然後將其作為bindService的第一個引數,第二個引數就是我們剛才說到的那個代表ServiceConnection,而第三個引數是一個標記,這裡我們傳入BIND_AUTO_CREATE標記,此標記表示在Activity和Service建立關聯後自動建立Service,並執行Service中的onCreate方法,並不會執行onStartCommand方法。(標記不只這一個,可以自行去查閱其他flag的含義)

為了驗證剛剛我所說的,我們點選bind按鈕,並檢視logcat日誌,如下:

bindservice.png

可以看到,確實只執行了onCreate方法,並且在ServiceConnection的onServiceConnected中的第一個引數返回的其實就是SimpleService的具體包名路徑。然後接著就執行了SimpleBinder中的doTask方法。當我們再次點選bind按鈕,會發現,並沒有執行任何的方法,說明了服務如果一旦與一個Activity繫結後,如果沒有解綁的話,它是不會重新與這個Activity進行繫結的。

繫結完了,最終我們肯定是需要解綁的,來看一下unBind按鈕的操作方法,只有一個unbindService方法,需要傳入一個ServiceConnection引數,即我們前面建立的mConnection例項。點選unbind按鈕,看到logcat日誌如下:

unbindservice.png

執行了onDestroy方法,這是剛剛通過bindService方法建立的Service就已經被摧毀了。

Service建立與摧毀方式的混淆

通過剛剛上述的描述,我們現在知道建立一個Service的方式有兩種,即通過startService和bindService的方式。而二者對Service的摧毀方式分別為stopService和unBindService。那麼可能很多人會疑問如果我選擇startService的方式建立,然後選擇unbindService的方式摧毀。或者說我採用bindService方式建立,stopService方式摧毀呢。那麼我們來對每種情況分別驗證一下會發生什麼樣的結果。

我們首先現將SimpleService中的程式碼還原成如下狀態。StartActivity中還是四個按鈕分別對應四個方法。

public class SimpleService extends Service {

    public static final String TAG = "SimpleService";

    private SimpleBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        mBinder = new SimpleBinder();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 任務邏輯
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }

    @Override
    public IBinder onBind(Intent intent) {
        if (mBinder != null) {
            return mBinder;
        }
        return null;
    }

    class SimpleBinder extends Binder {

        public void doTask() {
            Log.d(TAG, "doTask");
        }
    }
}
  • 1.我們首先點選一下start按鈕,然後點選unbind按鈕,結果會發現程式崩了。

notregister.png

崩潰日誌如下,提示非法狀態異常,接著後面又說,Service沒有被註冊。其實,很容易理解,就是不看崩潰日誌,估計很多人也猜到了,Service根本就沒有與任何東西繫結,又何談解綁呢。所以,這裡需要注意,在使用unbindService方法關閉一個服務時,為防止出現以上的情況,我們需要在呼叫比方法前判斷一下當前的Service是否已經被繫結。

針對這一判斷谷歌並沒有提供專門的Api,而我們可以通過在本地建立一個變數,當服務被繫結的時候,在ServiceConnection的onServiceConnected方法中將其設為true, 在onServiceDisconnected方法中將其設為false。這樣的話,如果服務未被繫結的話,這個值會永遠為false,這樣我們就直接在呼叫unbindService方法前做出相應的提示,以防止崩潰產生。

  • 2.點選bind按鈕後,再點選stop按鈕。同樣,看一下logcat日誌:

bindstop.png

請相信我,我真的點選了stop按鈕,而且不止點選了一次。可以看到,Service並沒有被摧毀。而當我再次點選unbind按鈕時,Service才被摧毀。其實這也很好解釋,舉個例子吧,施瓦星格拍的終結者(第二部)不知道大家有沒有看過,施瓦星格扮演的終結者的使命是保護男主,他的程式一旦被啟動,就會一直以保護男主(完成使命)為目的,只有消滅了敵人,確保男主可以平安無事了,他才會停止保護男主的行為。Service就相當於施瓦星格,它一旦被繫結,必須等到一個結果來告訴它"使命"完成了(解綁了),它才會停止下來。

  • 3.點選start按鈕後,接著點選bind按鈕,然後呢,我們分別單獨點選stop和unbind按鈕,發現Service都不會被摧毀,只有在我們兩個按鈕都點選了以後(兩個按鈕的點選順序無所謂),Service才會被摧毀。通過這一樣一個結論,我們可以得出,一個Service,只有在即沒有和任何Activity繫結又處於停止狀態下的時候,才可以被摧毀。

  • 4.最後,還要大家清楚一個問題就是。當你通過start按鈕,開啟一個Service後,再次點選bind按鈕,只會執行服務的doTask方法,也就是Service繫結的方法回撥,而不會執行onCreate方法再次建立一個Service。而同樣的當通過點選bind按鈕開啟一個服務後,再點選start按鈕,也不會執行onCreate方法,只會每次點選都執行onstartCommand方法,這裡在前面也提到過。

Service的執行執行緒

很多對Service瞭解的不是很透徹的同學,當被問到Service執行在什麼執行緒中,很多人都會第一反應是子執行緒。原因呢,因為大多數人都知道Service通常用來執行一些比較耗時的後臺任務,既然提到了耗時,那麼肯定不會執行在主執行緒啊,因為那樣的話會阻塞執行緒的啊。其實呢,並不是這樣的,Service其實是執行在主執行緒的。為了證明我所說的,我分別在StartActivity的onCreate方法和SimpleService的onCreate方法中獲取當前執行的執行緒的名字和id,程式碼如下:

// Activity的onCreate方法
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);
        Log.d(TAG, "StartActivity-----" + Thread.currentThread().getName() + "--" + Thread.currentThread().getId());  
    }

// Service的onCreate方法
   @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "SimpleService-----" + Thread.currentThread().getName() + "--"Thread.currentThread().getId());     
    }

分別啟動Activity和Service,Logcat日誌如下:

servicethread.png

可以看到,Service真的和Activity一樣,是執行在主執行緒的。到這估計好多人就懵逼了,這到底是個什麼鬼啊,明明是用來執行一些後臺的耗時任務的啊,怎麼還執行在主執行緒啊。
其實,這就是不會融會貫通的表現。Service確實執行在主執行緒,但是我們如果執行耗時操作,我們可以在Service裡面開啟一個子執行緒來執行耗時任務啊。那麼問題又來了,我們在Activity中就可以建立子執行緒何必還費二遍事開始一個服務來建立一個子執行緒呢。這裡就不得不說出Service的一個特性了,那就是不管建立Service的Activity是否被摧毀,或者說即使退出了應用程式,但只要應用的程序沒有被殺死,這個Service就還會在執行著。而對於Activity來說,一旦Activity被摧毀,那麼在它裡面建立的執行緒也就不存在,這樣一來執行的任務也就停止了。而如果放在Service中執行,即便是關聯的Activity被摧毀了,那麼只要重新與Service進行關聯,那麼它還是會獲得原有Service中的Binder例項的。舉出這樣一個需求,假設應用需要在程序未被殺死的情況下時刻保持著從伺服器讀取一個狀態,很明顯這時候就可以用Service來實現了啊。

現在,是不是明白了為什麼通常在Service中執行一些後臺的耗時操作了吧。下面我列舉出一個例子來掩飾在Service中開啟一個執行緒來執行操作:

public class SimpleService extends Service {
    
    private SimpleBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        mBinder = new SimpleBinder();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 任務邏輯
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        if (mBinder != null) {
            return mBinder;
        }
        return null;
    }

    class SimpleBinder extends Binder {

        public void doTask() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 任務邏輯
                }
            }).start();
        }
    }
}

看到了吧,是不是很簡單!

以上就是Service的一些基礎用法,只有掌握了這些基本用法以後,我們才能更好的利用Service這一元件來完成我們平時日常中的開發需求。特別提醒的是,要根據場景恰當的選擇使用Service,不要拿出一個任務就用Service來實現,以免造成不必要的開銷。

轉載:https://www.jianshu.com/p/738105190328