android 四大組件之Service

文章參考自:
1.郭神博客:http://blog.csdn.net/guolin_b...
2.Android開發文檔:
https://developer.android.com...
https://developer.android.com...html


最近趁着有時間,將之前的知識整理下,要否則總容易遺忘,今天就來說解下Service的用法。
做爲Android的四大組件之一,其重要性可想而知。在應用中咱們主要是用來進行一些後臺操做,不需與應用UI進行交互,執行耗時任務等。java

官方文檔中這樣說:android

Service 是一個能夠在後臺執行長時間運行操做而不提供用戶界面的應用組件。服務可由其餘應用組件啓動,並且即便用戶切換到其餘應用,服務仍將在後臺繼續運行。
此外,組件能夠綁定到服務,以與之進行交互,甚至是執行進程間通訊 (IPC)。 例如,服務能夠處理網絡事務、播放音樂,執行文件 I/O
或與內容提供程序交互,而全部這一切都可在後臺進行。網絡

Service的用途:

1.在後臺執行耗時操做,但不須要與用戶進行交互。
2.一個應用暴露出來的一些供其餘應用使用的功能。app

這裏須要聲明一點,Service是運行在主線程中,於是若是須要進行耗時操做或者訪問網絡等操做,須要在Service中再開啓一個線程來執行(使用IntentService的話則不須要在本身手動開啓線程)。ide

啓動Service

啓動一個Service有兩種方式:ui

Context.startService()
Context.bindService()

clipboard.png

(圖片截取自官方文檔:https://developer.android.com...this

startService()方式啓動Service,咱們啓動以後是沒有辦法再對Service進行控制的,並且啓動以後該Service是一直在後臺運行的,即便它裏面的一些代碼執行完畢,咱們要想終止該Service,就須要在他的代碼裏面調用stopSelf()方法或者直接調用stopService() 方法。而經過bindService()方法啓動的Service,客戶端將得到一個到Service的持久鏈接,客戶端會獲取到一個由Service的onBind(Intent)方法返回來的IBinder對象,用來供客戶端回調Service中的回調方法。spa

咱們不管使用那種方法,都須要定義一個類,讓它繼承Service類,並重寫其中的幾個方法,若是咱們是採用startService()方式啓動的話,只須要重寫onCreate() 、onStartCommand(Intent intent, int flags, int startId)、onDestroy()方法便可(其實咱們也能夠重寫),而若是採用的是bindService()方法啓動的話,咱們就須要重寫onCreate() 、onBind(Intent intent)、 onUnbind(Intent intent)方法.注意,做爲四大組件之一,Service使用以前要在清單文件中進行配置。.net

<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:

clipboard.png

下面咱們先點擊START按鈕,Log信息以下:

clipboard.png
能夠看出,onCreate()方法先執行,而後onStartCommand()方法緊接着執行,那麼若是咱們再次點擊啓動按鈕呢?結果以下圖:

clipboard.png
咱們能夠看到,此次onCreate()方法沒有再執行,而是直接執行了onStartCommand()方法,這是由於Service只在第一次建立的時候才執行onCreate()方法,若是已經建立了,那以後再次調用startService()啓動該Service的時候,只會去執行onStartCommand()方法方法,而不會再執行onCreate()方法。
接下來咱們點擊中止按鈕,能夠看到,onDestroy()方法被執行了:

clipboard.png

注意,若是咱們不點擊中止按鈕手動中止該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;
        }
    }
}

點擊綁定按鈕;

clipboard.png
點擊取消綁定按鈕:

clipboard.png
注意,若是咱們沒有先點擊綁定,而是直接點擊的取消綁定,程序會直接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)

clipboard.png

細心的你也許早就發現了,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 按鈕的輸出結果:

clipboard.png

b.下面是依次點擊start、bind、unbind、stop 按鈕時的輸出結果:

clipboard.png

在前臺運行服務

咱們上面一直說Service通常是用來在後臺執行耗時操做,可是要知道,Service也是能夠運行在前臺的。後臺Service的優先級比較低,容在內存不足等狀況下被系統殺死,經過將其設置爲前臺,能夠大大下降其被殺死的機會。前臺Service會在系統通知欄顯示一個圖標,咱們能夠在這裏進行一些操做。前臺Service比較常見的場景有音樂播放器和天氣預報等:

clipboard.png

那麼接下來咱們就直接上代碼:

@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並不會消失),截圖以下:

clipboard.png

相關文章
相關標籤/搜索