Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Service使用全解析

Service使用全解析

編輯:關於Android編程

什麼是Service

Service是Android 的四大組件之一,主要處理一些耗時的後台操作邏輯,或者輪詢操作等需要長期在後台運行的任務。甚至在程序退出之後,可以讓Service繼續在後台運行。

Service的啟動方式有三種:三種方式對應著三種不同的生命周期。

startService啟動服務。(簡單使用) bindService綁定服務的方式啟動服務。 先啟動服務之後綁定服務。

Service 的簡單使用

startService啟動服務是最簡單的一種方式。我們按照以下流程來使用Service

創建MyService類繼承Service 重寫onCreate()onStartCommand(),onDestory()方法。 在Activity啟動Service 清單文件中注冊該Service

創建MyService類並重寫對應的三個方法

public class MyService extends Service {

    private static final  String TAG = "INFO";

    @Override
    public void onCreate() {

        Log.i(TAG,"onCreate");
        super.onCreate();
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

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

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

在繼承Service時,默認必須實現onBind方法,該方法是在綁定服務時用到的,在這裡我們不做任何操作,後面會用到。

對於這三個方法,並沒有做多余的操作,分別在這個三個方法中打印了一句log,看一下他們的調用時機。

布局文件中添加了兩個按鈕,分別是啟動服務和停止服務

MainActivity中實現這兩個方法:

    public void start(View view) {
        Intent intent = new Intent(this, SimpleService.class);
        startService(intent);
    }


    public void stop(View view) {
        Intent intent = new Intent(this, SimpleService.class);
        stopService(intent);
    }

啟動Service的方式和啟動Activity的方式相似,最後使用startServicestopService啟動和停止服務。

最後,一定要在清單文件中進行注冊


    
        
            
                

                
            
        

        
        
    

看一下打印結果


// 點擊start 按鈕
06-20 16:09:24.483 22629-22629/com.example.system4compent I/INFO: onCreate
06-20 16:09:24.485 22629-22629/com.example.system4compent I/INFO: onStartCommand
// 再次點擊start按鈕
06-20 16:09:27.873 22629-22629/com.example.system4compent I/INFO: onStartCommand
// 點擊 stop 按鈕
06-20 16:09:29.442 22629-22629/com.example.system4compent I/INFO: onDestroy

// 退出程序,此時Service 已停止,程序被完全退出

// 重新打開程序,點擊 start 按鈕
06-20 16:09:49.545 22867-22867/com.example.system4compent I/INFO: onCreate
06-20 16:09:49.545 22867-22867/com.example.system4compent I/INFO: onStartCommand

// 退出程序,使用後台干掉。此時Service 未停止,重新調用了onCreate和onStartCommand
06-20 16:09:53.488 22971-22971/com.example.system4compent I/INFO: onCreate
06-20 16:09:53.489 22971-22971/com.example.system4compent I/INFO: onStartCommand


從上面的結果中可以看到,當我們startService()啟動服務時,如果是第一次,則會創建Service實例,並調用了onCreate()onstartCommand(),而如果不是第一次,Service實例已經被創建了,則只會調用onStartCommand()方法。

stopService 方法可停止服務的啟動。銷毀Service實例。

為了對比,分別在Service實例被銷毀和不被銷毀時,通過後台管理直接干掉該程序,發現到銷毀時,程序正常退出。而未銷毀時,在程序退出之後,又調用了onCreate()onStartCommand()方法,這是為什麼呢?

解釋: 當銷毀時退出,一切都ok。但是當Service還存在時,通過後台管理直接干掉程序,對於Service來說,突然被干掉,屬於異常退出,此時會讓系統重新創建一個Service實例,這也是重新調用了兩個初始方法的原因。

有一個沒有演示,在這裡直接說結論,當通過返回的方式退出程序時,Service如果沒有手動調用onDestory(),則Service不會被銷毀和退出,仍在系統中運行。

總結:該方式我們可以通過startService()啟動服務時,通過onStartCommand()方式不斷調用,來操做service,常見的MP3播放器就是使用這種方式。單是,其和Activity的交互比較麻煩,此時,綁定方式更加的利於相互的數據交互。

綁定Service

在上一節中,有一個方法沒有管它,在這裡,我們繼續對之前的MyService進行修改。

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        // 綁定之後會回調此方法
        return myBinder;
    }

    /**
     * 綁定之後回傳給Activity 的對象實例
     */
    public class MyBinder extends Binder{
        public void down(){
            Log.i(TAG,"down.....");
        }
    }

由於onBind()方法返回的是IBinder對象,我們自定義該實現其類。同時自定義下載方法,通過onBind()方法,將實例回傳。

在布局文件中添加兩個按鈕,分別是綁定服務和解綁服務

實現布局中的兩個方法


    public void bind(View view) {
        // 綁定服務

        Intent intent = new Intent(this, MyService.class);

        bindService(intent,conn,BIND_AUTO_CREATE);
    }


    public void unbind(View view){
        //解綁服務

        unbindService(conn);
    }

綁定服務和解綁服務,通過bindService()unbindService()方法。分析bindService()的參數:

bindService(Intent service, ServiceConnection conn,int flags)

service:很好理解,意圖 ServiceConnection: 服務綁定和解綁的回調類,類似xxxListener. flag: 綁定的一些標志。對於當前的標志BIND_AUTO_CREATE的含義是,如果綁定是Service未創建實例,則先創建實例在綁定。

ServiceConnection作為監聽的類,有兩個監聽方法:

public void onServiceConnected(ComponentName name, IBinder service): 綁定成功的回調。 public void onServiceDisconnected(ComponentName name):綁定的服務被異常銷毀時。一般不會回調此方法。

看一下我們的實現:

 private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 建立聯系時回調
            myBinder = (MyService.MyBinder) service;
            myBinder.down();
            Log.i("INFO","onServiceConnected");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 斷開連接時回調
            Log.i("INFO","onServiceDisconnected");
        }
    };

通過onServiceConnected的參數,我們獲取到當綁定時返回的MyBinder對象。

06-20 16:49:06.972 20373-20373/com.example.system4compent I/INFO: onCreate
06-20 16:49:06.977 20373-20373/com.example.system4compent I/INFO: down.....
06-20 16:49:06.977 20373-20373/com.example.system4compent I/INFO: onServiceConnected
06-20 16:49:20.228 20373-20373/com.example.system4compent I/INFO: onDestroy

對於結果,直接總結:

bindService時因為flag的設置,會先創建Service的實例,在調用onBind回傳Binder實例,用以產生交互。 unbindService調用後,當前Service解綁之後會被立即銷毀。 如果沒有解綁服務,當前activity銷毀或者從後台直接干掉程序,都會拋出異常。即綁定之後必須解除綁定。 bindService()之後,再綁定,則無效果,也不會回調,activityService的交互完全交給了Binder對象實例。

bindService()時,第三個參數flags都能夠傳入那些參數呢?

BIND_AUTO_CREATE:綁定的service不存在時,會自動創建 BIND_ADJUST_WITH_ACTIVITY:service的優先級別與根據所綁定的Activity的重要程度有關,Activity處於前台,service的級別高; BIND_NOT_FOREGROUND:Service永遠不擁有運行前台的優先級; BIND_WAIVE_PRIORITY:Service的優先級不會改變; BIND_IMPORTANT: 當你的客戶端在前台,這個標示符下的Service也變得重要性相當於前台的Activity,優先級迅速提升。 BIND_ABOVE_CLIENT:優先級已經超過了Activity,也就是說Activity要比Service先死,當資源不夠的時候。;

補充:在Service中,同樣有一個unBind()方法,當所有與其綁定的都斷開時,會回調此方法。

先啟動再綁定服務

先啟動服務,在綁定服務,則調用方法如下:


06-20 17:02:02.628 21264-21264/com.example.system4compent I/INFO: onCreate
06-20 17:02:02.628 21264-21264/com.example.system4compent I/INFO: onStartCommand
06-20 17:02:05.544 21264-21264/com.example.system4compent I/INFO: down.....
06-20 17:02:05.544 21264-21264/com.example.system4compent I/INFO: onServiceConnected
06-20 17:02:08.502 21264-21264/com.example.system4compent I/INFO: onDestroy

//== 第二次啟動
06-20 17:02:11.615 21264-21264/com.example.system4compent I/INFO: onCreate
06-20 17:02:11.619 21264-21264/com.example.system4compent I/INFO: onStartCommand
06-20 17:02:13.949 21264-21264/com.example.system4compent I/INFO: down.....
06-20 17:02:13.949 21264-21264/com.example.system4compent I/INFO: onServiceConnected
06-20 17:02:15.779 21264-21264/com.example.system4compent I/INFO: onDestroy

在這裡只需要記住關鍵點:只要啟動和綁定了服務,必須停止服務和解除綁定同時調用,Service才能銷毀。

感覺這個沒什麼太大的作用

Service 的生命周期

在這裡只對重要兩種方式進行分析,分別是通過startServicebindService調用Service。第三種方式不做分析。

這裡寫圖片描述

粘性Service

在使用startService啟動Service時,如果程序被後台干掉,同時該Service實例未被銷毀,那麼程序結束之後,會重新啟動Service並調用onCreateonStartCommand

那麼,我們能控制麼,當然可以。

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

該方法,我們之前默認返回其父類的返回參數,點進去會發現

    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
    }

可見就是幾個字段,因此,我們可以修改代碼

    public int onStartCommand(Intent intent,int flags,int startId){

        // 非粘性標示,即被異常干掉之後不會重啟服務。
        return Service.START_NOT_STICKY;
    }

該返回值可取的值

START_STICKY:粘性標志。如果service進程被kill掉,保留service的狀態為開始狀態,但不保留遞送的intent對象。隨後系統會嘗試重新創建service,由於服務狀態為開始狀態,所以創建服務後一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啟動命令被傳遞到service,那麼參數Intent將為null START_NOT_STICKY: 非粘性標志。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啟該服務。 START_REDELIVER_INTENT:帶數據的粘性標識。使用這個返回值時,如果在執行onStartCommand後,服務被異常kill掉,系統會自動重啟該服務,並將Intent的值傳入。 START_STICKY_COMPATIBILITYSTART_STICKY的兼容版本,但不保證服務被kill後一定能重啟。

Service和Thread,Activity的比較

serviceThread 線程之間的比較

serviceThread 沒有任何聯系。 Thread 開啟一個子線程,執行耗時操作,不會阻塞主線程 service 運行在主線程,如果運行耗時操作也會導致程序阻塞,ANR;

serviceActivity 之間的比較

都是運行在主線程 如果在Activity 中啟動線程, 則線程不可控,且不可共享。如果Activity被銷毀,則線程就無法控制。 Service 可以與多個Activity綁定,獲取到Service 對象,便於操作線程。即使Activity被銷毀,其余ActivityService綁定,即可繼續操作。

前台 Serivice

Google 將進程分為以下5個級別(優先級別從高到低)

前台進程:
處於前台的正與用戶交互的activity。 進程中包含與前台綁定的Service。 可視進程:進程中包含未處於前台但仍然可見的activity(調用了activity的onPause()方法, 但沒有調用onStop()方法). 典型的情況是運行activity時彈出對話框, 此時的activity雖然不是前台activity, 但其仍然可見. 服務進程:進程中包含已啟動的activity; 後台進程: 進程中不包含可見的activity; 空進程:不包含任何處於活動狀態的進程是一個空進程。

前台的Service是將Service與一個通知所綁定,類似於酷狗音樂後台播放音樂一樣,當酷狗從進程中被殺死時,音樂依然在播放,同時在通知欄可以看到播放音樂的效果。

MyServiceonCreate()中添加如下代碼

   // 創建一個延時意圖,即點擊通知跳轉到該應用
        Intent notificationIntent = new Intent(this, ServiceMainActivity.class);

        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                notificationIntent, 0);

        // 通知
        Notification notification1 = new Notification.Builder(this)
                .setContentTitle("這是通知的標題")
                .setContentText("這是通知的內容")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(pendingIntent).build();

        // 啟動通知
        NotificationManager nService = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        nService.notify(1,notification1);

        // 使Service 不被殺死。
        startForeground(1,notification1);

關鍵方法startForeground(),使當前通知和Service綁定。這時,即使我們從後台干掉當前應用,Service實例仍然存在,且不會被銷毀。同時通知依然在。

點擊通知之後,跳轉到我們的應用程序,通知不會消失。因為它與我們的Service相綁定。

銷毀Service,則通知會立即消失。

Service不在需要前台的優先級時,stopForeground(true);可以將其移動到後台。並且可以選擇是否移除通知。

音樂播放器使用的都是這種方式

IntentService

IntentService的業務場景

我們或許會碰到這麼一種業務需求,一項任務分成幾個子任務,子任務按順序先後執行,子任務全部執行完後,這項任務才算成功。那麼,利用幾個子線程順序執行是可以達到這個目的的,但是每個線程必須去手動控制,而且得在一個子線程執行完後,再開啟另一個子線程。或者,全部放到一個線程中讓其順序執行。這樣都可以做到,但是,如果這是一個後台任務,就得放到Service裡面,由於ServiceActivity是同級的,所以,要執行耗時任務,就得在Service裡面開子線程來執行。那麼,有沒有一種簡單的方法來處理這個過程呢,答案就是IntentService

什麼是IntentService :

IntentService是繼承於Service並處理異步請求的一個類,在IntentService內有一個工作線程來處理耗時操作,啟動IntentService的方式和啟動傳統Service一樣,同時,當任務執行完後,IntentService會自動停止,而不需要我們去手動控制。另外,可以啟動IntentService多次,而每一個耗時操作會以工作隊列的方式在IntentService的onHandleIntent回調方法中執行,並且,每次只會執行一個工作線程,執行完第一個再執行第二個,以此類推。

IntentService使用方式

使用和實現方式和Service類似,該類需要繼承IntentService.

public class MyIntentService extends IntentService {


    // 必須實現父類的構造方法, 同時傳入的參數代表線程的名字
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        Log.i("info","onCreate");
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.i("info","onStartCommand");
        return super.onStartCommand(intent, flags, startId);


    }

    @Override
    protected void onHandleIntent(Intent intent) {

        // 根據不同的參數啟動不同的服務,執行不同的任務
        String params = intent.getStringExtra("params");

        if(params.equals("1"))
            Log.i("info","run service1");

        if(params.equals("2"))
            Log.i("info","run service2");

        if(params.equals("3"))
            Log.i("info","run service13");


        //讓服務休眠2秒
        try{
            Thread.sleep(2000);
        }catch(InterruptedException e){e.printStackTrace();}
    }


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

        Log.i("info","onDestory");
    }
}

必須實現無參構造方法,同時調用super(name)name代表創建工作線程的名字。 onHandleIntent方法代表啟動服務的工作序列,該方法在子線程中調用。 Log以下不同方法,看看調用的順序。
  // 定義三個不同的後台任務
        Intent intent1 = new Intent(this,MyIntentService.class);
        intent1.putExtra("params","1");

        Intent intent2 = new Intent(this,MyIntentService.class);
        intent2.putExtra("params","2");

        Intent intent3 = new Intent(this,MyIntentService.class);
        intent3.putExtra("params","3");


        // 啟動服務
        startService(intent1);
        startService(intent2);
        startService(intent3);

記得在清單文件中注冊

看一下打印結果:

06-21 10:19:30.546 32076-32076/com.example.system4compent I/info: onCreate
06-21 10:19:30.547 32076-32076/com.example.system4compent I/info: onStartCommand
06-21 10:19:30.548 32076-32076/com.example.system4compent I/info: onStartCommand
06-21 10:19:30.553 32076-32076/com.example.system4compent I/info: onStartCommand
06-21 10:19:30.554 32076-32322/com.example.system4compent I/info: run service1
06-21 10:19:32.555 32076-32322/com.example.system4compent I/info: run service2
06-21 10:19:34.565 32076-32322/com.example.system4compent I/info: run service13
06-21 10:19:36.567 32076-32076/com.example.system4compent I/info: onDestory

對結果進行分析:

onCreate方法只打印一次,可見只創建一次IntentService實例。 onHandleIntent()的執行,按照順序執行。說明是同步的。如果是異步的,則因為睡眠,順序將不可控。 運行完任務之後,調用了onDestory方法,銷毀自己。

Service 實現簡單輪詢功能(推送相關)

Service在開發中還有一種情況,後台定時任務。每隔一段時間請求一下服務器,確認一下服務器狀態或者信息更新等。

實現的兩種方式:

Timer,該類是Java中的線程封裝類,可以執行計時等功能。但Timer存在問題,如果CPU 休眠(例如長時間的熄屏),則Timer就無法執行。此時輪詢功能就失效。可以使用WakeLock讓CPU時刻保持喚醒,但非常耗電。 AlarmManagerAlarmManager 是 Android 系統封裝的用於管理 RTC 的模塊,RTC (Real Time Clock) 是一個獨立的硬件時鐘,可以在 CPU 休眠時正常運行,在預設的時間到達時,通過中斷喚醒 CPU。

推薦使用AlarmMananger,極光推送就是使用該方式。

實現:

定義LongRunningService,實現輪詢。

    public class LongRunningService extends Service {


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


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        new Thread(){
            @Override
            public void run() {
                // 網絡請求等輪詢更新操作
                Log.i("info","更新數據");
            }
        }.start();


        //啟動一個時鐘
        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        //設置提醒的時間,就類似於鬧鐘,設置一個叫醒的實現
        int anHour = 2 * 1000;
        // 該時間獲取的是系統開機到現在的時間,區分 System.currentTimeMillis()
        long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
        //延時意圖
        Intent i = new Intent(this,LongRunningReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);

        //設置鬧鐘
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);

        return super.onStartCommand(intent, flags, startId);
    }
}

onStartCommand中,獲取AlarmManager,開啟鬧鐘。每隔2秒,啟動廣播,在廣播中啟動服務,這樣就產生了一個循環。

public class LongRunningReceiver extends BroadcastReceiver {


    @Override
    public void onReceive(Context context, Intent intent) {
        //啟動服務

        Intent i = new Intent(context,LongRunningService.class);
        context.startService(i);
    }
}

備注:在設置鬧鐘時manager.set()的參數,第二和第三個參數分別為喚醒時間和延遲意圖。第一個參數,可取以下值。

AlarmManager.ELAPSED_REALTIME:使用相對時間(開機時間),不計休眠時間,休眠時間喚起不可用。 AlarmManager.ELAPSED_REALTIME_WAKEUP:相對時間,休眠時間可以喚起。 AlarmManager.RTC: 絕對時間(1970-1-1:0),休眠狀態下不可用。 AlarmManager.RTC_WAKEUP:絕對時間,在休眠狀態下可以喚醒並顯示提示功能等。 AlarmManager.POWER_OFF_WAKEUP:絕對時間,表示在關機狀態下仍然能夠顯示提示功能。

AIDL 進程通信(待完成)

使用Service完成進程間通信涉及較多,會在後面的博客中進行專門講解。

TO DO : 進程間通信的總結(IPC機制)。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved