對于 Service, 你真的都懂了嗎?

加油.png

android 入行也大半年了,一直都沒有對基礎知識做一個梳理,今后就對基礎知識逐個總結,先從 Service 開始。

一、Servive 概念

我們一般把 Service 稱做 "后臺默默的勞動者"。現在的 智能手機應用程序都能在后臺運行,這就是 得益于 Service 。在
android 開發中,Service 就是用來解決程序后臺運行的解決方案,適合去執行一些不需要跟用戶交互而且要求長期運行的任務。不依賴任何用戶界面,即使程序切換到后臺,或者打開新的應用程序。但是需要注意以下兩點:

  • Service 不是運行在一個獨立的進程中,而是依賴于創建該Service的 應用程序進程,一旦該程序被 kill ,Service 也隨之停止運行
  • 不要以為 Service 是工作在后臺,就理所當然的認為 Service 會自動開啟線程。Service 默認是工作在 UI 線程的,一旦有耗時操作,必須開啟新線程,防止 ANR 異常。

二、Service 的定義

使用 android studio 快速定義一個 MyService 繼承自 Service.Service中 onBind() 為抽象方法,我們暫且先必須實現它,接著依次實現 onCreate() ,onStartCommand,onDestroy 生命周期方法。如下所示:

public class MyService extends Service {
    private static final String TAG = "MyService";

    public MyService() {
    }
    /**
     * 跟其他組件綁定時使用
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
      /**
     * Service 第一次創建時回調
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: excuted");
    }
    /**
     * 每次啟動 Service 時回調
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: excuted");
        return super.onStartCommand(intent, flags, startId);
    }
    /**
     * 銷毀 Service 時回調
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy: excuted");
    }
}

注意:身為 四大組件之一的 Service 也必須在 Manifest 里面注冊,不然會報錯,但是這里智能的 as 已經幫我們注冊好啦~

        <service
            android:name=".app.service.MyService"
            android:enabled="true"
            android:exported="true">
        </service>
  • enabled表示是否啟用該 服務
  • exported表示是否允許除了當前應用程序之外的其他程序訪問這個服務。

三、Service 的啟動與停止

我們在 activity 中添加兩個 Button ,用來觸發 Service 的啟動與停止。這里使用黃油刀直接注入。啟動與停止的邏輯相當簡單,一般我們組件間通信都是用 intent ,這里也不例外,startService(),stopService() 傳入對應的 intent 即可。

 @OnClick({R.id.start_service, R.id.stop_service})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.start_service:
                Intent startIntent = new Intent(this, MyService.class);
              //啟動service
                startService(startIntent);
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this, MyService.class);
               //停止service
                stopService(stopIntent);
                break;
        }

直接 run 程序跑到手機上,來看看 Service 具體的啟動過程,我們事先已經在對應的生命周期方法中打好 log 了。現在點擊 start 效果如下:

05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted

log 很清晰明了。當我們點擊 start按鈕啟動 Service 時,系統回調了onCreate(),onStartCommand() 方法。這時候我們大膽猜測 Service 已經啟動了,打開手機應用管理查看,如圖


service 啟動.png

從此圖明顯看出 Service 確實啟動成功了,同時也可以印證我們之前的觀點:

** Service 不是運行在一個獨立的進程中,而是依賴于創建該 Service 的 應用程序進程,一旦該程序被 kill ,Service 也隨之停止運行**

這個時候我們再點擊幾次 starService 按鈕,看看啟動流程。

05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted

啊?當我再次點擊三次 startService ,從 log 可以清晰的看出。這時候 onCreate() 已經不再回調了,而是重復回調了三次 onStartCommand()。 這時查看手機應用管理查看該 Service 也確實還在。我們抱著懷疑的態度繼續做下實驗。

點擊 stopService 執行停止 Service 的邏輯。

05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:58.970 32095-32095/? E/MyService: onDestroy: excuted

看看 log 知道系統回調了 onDestroy() 方法。此時我們看下手機,啊?MyService 已經沒了。這樣我們銷毀 Service成功。我們繼續~

點擊 starService

05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:58.970 32095-32095/? E/MyService: onDestroy: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted

看看 log 。納尼?又從頭開始回調一遍,再看看手機中運行程序,臥槽?MyService 又回來了。ok,結論來了~

  • 當我們第一次啟動 Service 時,Service 還不存在,系統會回調 onCreate() 創建Service ,同時回調 onStartCommand() 啟動 Service。
  • Service 一旦被創建,假如你沒有 手動調用 stopService() 去銷毀它,或者 kill 程序。它就一直會處于后臺進程中,當我們再次啟動時,系統不會再去創建,而是直接啟動,即回調 onStartCommand() 方法。

四、Service 與 Activity 通信

之前的 Service 啟動方式似乎不那么靈活,我們新建一個服務是要他給我們干活的,然而現在我們只能 start 、stop 去啟動、停止 Servive 。start 完之后 Service 就在那邊不停的干活。怎么干活、干了多少 、做得如何 我們不得而知。那么我們就有必要換一種方式來啟動 Service 了。讓我們能夠擁有主動權。

假設一個場景:我們需要啟動一個 Service 去執行下載功能,我們要控制什么時候下載,知道下載進度。

仔細看看 MyService 中的代碼 、好像一開始就有一個回調方法在那邊沒用呢?哈哈,就從這個方法進去搞事情,修改 MyService 中的代碼。

public class MyService extends Service {
    private static final String TAG = "MyService";
    private DownloadBinder mBinder = new DownloadBinder();
    /**
     * 跟其他組件綁定時使用
     *
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }
  /**
     * Binder---IBinder的實現類,綁定Service 的時候,返回實例給調用方使用
     */
   public class DownloadBinder extends Binder {

        public void startDownload() {
            Log.e(TAG, "startDownload: excuted");
        }

        public int getProgress() {
            Log.e(TAG, "getProgress: excuted");
            return 0;
        }
    }

}

Binder 是 IBinder的實現類,綁定 Service 的時候,返回實例給調用方使用。我們自定義 Binder 類,在類中定義 public 方法,最終將 Binder 實例通過 onBind 返回給調用方,調用方只要有 Binder 實例就能隨便調用它的公共方法。

那么 Activity (調用方) 需要拿到 Binder 實例。定義全局 DownloadBinder 。實例化
ServiceConnection 回調 onServiceConnected,onServiceDisconnected方法。仔細一看,onServiceConnected 攜帶 IBinder 參數。這個就是一旦我們綁定成功,Service 回傳回來的實例,這時候通過向下轉型成 DownloadBinder 即可。之后就可以隨意控制方法調用啦~

private MyService.DownloadBinder mDownloadBinder;

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

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

修改布局 Button.點擊 bindService 執行 bindService() 傳入對應的 Intent,ServiceConnection實例,標志位(這里表示綁定之后自動創建Service)。

@OnClick({R.id.bind_service, R.id.unbind_service})
   public void onClick(View view) {
       switch (view.getId()) {
           case R.id.bind_service:
               Intent bindIntent = new Intent(this, MyService.class);
               bindService(bindIntent, mConnection, BIND_AUTO_CREATE);//綁定服務
               break;
           case R.id.unbind_service:
               unbindService(mConnection);//解除綁定
               break;
       }
   }

運行效果如下:

05-18 17:59:43.440 23371-23371/com.sunnada.jpushdemo E/MyService: onCreate: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: startDownload: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: getProgress: excuted

我們看到創建了 Service ,但是不會回調 onStartCommand 方法了。同時執行了下載的方法。
此時點擊 unBinderService 按鈕,看下log

05-18 17:59:43.440 23371-23371/com.sunnada.jpushdemo E/MyService: onCreate: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: startDownload: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: getProgress: excuted
05-18 18:00:13.051 23371-23371/com.sunnada.jpushdemo E/MyService: onDestroy: excuted

此時回調 onDestroy() 方法 證明 Service 已經銷毀。

注意

  • 我們一般在 activity 的 onDestroy() 生命周期函數中 unBinderServise 否則有內存泄漏的風險。
  • 假如 使用 starService 啟動了 Service ,并 通過 bindService 又綁定啟動 Service 這時必須同時調用 stopService,unBindService 才會銷毀 Service。

五、前臺 Service

前面我們講的 Service 都是默默的在后臺運行的,系統優先級較低,在內存吃緊的情況下,有可能被系統回收,而前臺 Service 會一直有一個正在運行的圖標在系統的狀態欄顯示,類似一個通知。
修改 MyService 代碼:

/*
    * Service 第一次創建時回調
    */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: excuted");
        Intent intent = new Intent(this, LoginActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat
                .Builder(this)
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pendingIntent)
                .build();
        startForeground(1,notification);
    }

這里使用 Builder 模式構造一個通知,最后調用 startForeground ,傳入通知的 id ,Notification 實例化對象。讓 MyService 變成一個前臺服務,并且在狀態欄顯示。重新 run 程序,點擊 BindService

前臺服務.png

over~下拉通知欄可以看到明顯的通知。

六、IntentService

Service 默認是執行在主線程的,如果需要執行耗時操作,必須開啟子線程,我們一般的做法是在 onStartCommand 中開啟子線程去執行。

 @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);
    }

但是 Service 一旦啟動就會一直在后臺運行,不斷的執行耗時操作,這是相當消耗資源的,在這種情況下,通常在執行完具體邏輯之后,添加 stopSelf() 讓 Service 停止。但是這種做法有時候會讓我們忘記開線程,或者忘記 stopSelf ,為了解決這種情況,Android 特地提供了 IntentService 。
新建一個MyIntentService 繼承 IntentService,注意別忘了注冊

public class MyIntentService extends IntentService {

    private static final String TAG = "MyIntentService";

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


    @Override
    protected void onHandleIntent(Intent intent) {
        Log.e(TAG, "onHandleIntent: " + Thread.currentThread().getName());
    }

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

一個空構造 調用父類有參構造,復寫 onHandleIntent 抽象方法,直接就是子線程,可以直接執行耗時操作,而且 執行完之后
IntentService 會自動停止。下面修改 activity 按鈕來測試看看。
run 程序,點擊 starService,看 log

05-18 22:34:42.568 22842-24560/com.sunnada.jpushdemo E/MyIntentService: onHandleIntent: IntentService[MyIntentService]
05-18 22:34:42.569 22842-22842/com.sunnada.jpushdemo E/MyIntentService: onDestroy: excute

從 log 可以看出我們啟動 IntentService 之后并沒有調用 stop ,而
onDestroy 已經回調了,并且 onHandleIntent 確實是工作在子線程的。

over~由于篇幅的原因,Service 梳理就先到這了,后續會總結一些 Service 的實戰應用。

聲明:以上只是本人對于基礎知識的梳理總結,部分參考自第一行代碼第二版,如有不足之處,還望指出。

更多原創文章會在公眾號第一時間推送,歡迎掃碼關注 張少林同學

張少林同學.jpg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容