Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 後台服務簡要概述

Android 後台服務簡要概述

編輯:關於Android編程

本篇文章主要講述android servivce相關知識,其中會穿插一些其他的知識點,作為初學者的教程。老鳥繞路

本文會講述如下內容:
- 為什麼要用Service
- Service及其繼承者IntentService
- 一個後台計數器的例子來講述Service
- Service如何與UI組件通信


為什麼要用Service

我們接觸android的時候,大部分時候是在和activity打交道,但是有些比如網絡下載、大文件讀取、解析等耗時卻又不需要界面對象的操作。一旦退出界面,那麼可能就會變得不可控(比如界面退出後,線程通知UI顯示進度,但是由於View已經被銷毀導致報錯,或者界面退出後下載中斷,就算你寫得非常完美,什麼異常狀態都考慮到了,還是保證不了系統由於內存緊張把你這個後台的activity給干掉,依附於於它的下載線程也中斷。)

這時候Service就有它的用武之地了,不依賴界面,消耗資源少,優先級比後台activity高,不會輕易被系統干掉(就算被干掉,也有標志位設置可以讓它自動重啟,這也是一些流氓軟件牛皮鮮的招數)、

Service及其繼承者IntentService

service的生命周期

service的生命周期相對activity要簡單不少。
service生命周期

可以看出service有兩條生命線,一條是調用startService,一條是調用bindService
,兩條生命線相互獨立。本文只講startService。

一道選擇題,解釋service生命周期的所有問題:

android通過startService的方式開啟服務,關於service生命周期的onCreate()和onStart() 說法正確的是哪兩項
A.當第一次啟動的時候先後調用 onCreate()和 onStart()方法
B.當第一次啟動的時候只會調用 onCreate()方法
C.如果 service 已經啟動,將先後調用 onCreate()和 onStart()方法
D.如果 service 已經啟動,只會執行 onStart()方法,不在執行 onCreate()方法

答案自己想下,結尾公布

IntentService

一些容易被忽略的基礎知識:Service運行的代碼是在主線程上的,也就是說,直接在上面運行會卡住UI,這時就Service的繼承者(繼承於Service的子類)IntentService就應運而生。android studio的新建裡面直接就有IntentService的模板,足見其應用之廣。
那麼Service與IntentService的區別在哪呢?
詳見這裡 Android之Service與IntentService的比較

簡單來說就是

IntentService內部有個工作線程(Worker Thread),會將startService傳入的intent通過Handler-Message機制傳入工作線程,開發者通過重載onHandleIntent進行服務的具體實現。 IntentService在跑完onHandleIntent後,如果Handler隊列裡沒有其他消息,就會自動結束服務,有點像Thread中run函數一樣,跑完run函數之後,線程就結束了。而service需要自己去停止。

一個後台計數器的例子來講述Service

實戰環節,本文通過一個計數器的例子模擬下載文件的耗時操作。

public void startService(View view){
    Intent intent = new Intent(this,BackgroundService.class);
    intent.setAction("com.example.administrator.servicestudy.action.counter");
    intent.putExtra("duration",10);
    intent.putExtra("interval",1.0f);
    startService(intent);
}

上述代碼就是一個啟動service的例子,action相當於做什麼操作(適用於一個service處理多種請求的情況。),extra就是參數。參數中duration代表總時間10秒,interval代碼每隔一秒。

private static final String ACTION_COUNTER = "com.example.administrator.servicestudy.action.counter";

@Override
protected void onHandleIntent(Intent intent) {
    if (intent != null) {
        final String action = intent.getAction();
        if (ACTION_COUNTER.equals(action)) {
            final int duration = intent.getIntExtra(EXTRA_DURATION,0);
            final float interval = intent.getFloatExtra(EXTRA_INTERVAL,0);
            handleActionCounter(duration, interval);
        }
    }
}

private void handleActionCounter(int duration, float interval) {
    for(int i=0; i

可以看到重載onHandleIntent處理事件,handleActionCounter表示具體服務。根據傳入的參數決定循環時間和sleep間隔。

當然別忘了在manifest文件中聲明該Service

以上就是最基本的IntentService的用法了,不過為了代碼獨立性更好,可以將代碼寫成這樣。
Activity

public void startService(View view){
     BackgroundService.startCounterService(this,1,10);
}

Service

public static void startCounterService(@NonNull Context context, int interval, int duration) {
        Intent intent = new Intent(context, BackgroundService.class);
        intent.setAction(ACTION_COUNTER);
        intent.putExtra(EXTRA_DURATION, duration);
        intent.putExtra(EXTRA_INTERVAL, interval);
        context.startService(intent);
    }

在Service裡寫個靜態方法,只將參數傳入,剩余的全都在Service內實現。雖然代碼寫的位置變了,但是代碼運行的位置沒變(靜態方法依然還是運行在activity端),這樣做將EXTRA_DURATION、EXTRA_INTERVAL等參數也不暴露給外部。做到更好的封裝性和模塊化,推薦這種做法。

Service如何與UI組件通信

那麼Service在後台努力干活的時候,如何將當前進度通知給用戶呢,因為Service不依賴任何界面,所以自身沒辦法操作界面(除非用Toast)。所以Service就要與其他組件進行通信(主要就是activity和通知欄了,但不限於上述兩者)。

android組件間的通信(還記得android四大組件是哪四個不?)。 大部分通過android四大組件之一的Broadcast來通信。
那麼簡要說下Broadcast

Broadcast

生命周期:
\
就這麼簡單,一旦處理完廣播就被銷毀,沒有onCreate,也沒有onDestory
最重要的一點就是receiver裡不能處理耗時操作,超過5秒(好像是)系統就會報錯

Service

 private void updateUI(int current,int total){
    Intent intent = new Intent(BROADCAST_UPDATE_UI);
    intent.putExtra(EXTRA_CURRENT,current);
    intent.putExtra(EXTRA_TOTAL,total);

    sendBroadcast(intent);
}

可以看到,發個廣播就這麼簡單,把參數填入intent,自定義一個action,send!好了。

Activity

@Override
protected void onResume() {
    super.onResume();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BackgroundService.BROADCAST_UPDATE_UI);
    registerReceiver(mBackgroundServiceReceiver,intentFilter);
}

@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(mBackgroundServiceReceiver);    
}

private BroadcastReceiver mBackgroundServiceReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG,"receive:"+intent.getAction());
        if(intent.getAction() == BackgroundService.BROADCAST_UPDATE_UI){
            int current = intent.getIntExtra(BackgroundService.EXTRA_CURRENT,0);
            int total = intent.getIntExtra(BackgroundService.EXTRA_TOTAL,0);
            mHint.setText(current+"/"+total);
        }
    }
};

Activity在resume的時候注冊一個廣播接收器,pasue的時候注銷掉。在receiver裡處理更新UI的操作。就這麼簡單

同樣的,為了代碼更具有封裝性。在Activity中將recevier去掉。放在Service中,看代碼:


   
       
   
public static class BackgroundServiceReceiver extends BroadcastReceiver {
    private static List mHandlers = new ArrayList<>();

    @Override
    public void onReceive(Context context, Intent intent) {
        if(intent.getAction().equals(BROADCAST_UPDATE_UI)){
            int current = intent.getIntExtra(BackgroundService.EXTRA_CURRENT,0);
            int total = intent.getIntExtra(BackgroundService.EXTRA_TOTAL,0);
            for (UIHandler handler : mHandlers) {
                handler.onUpdateUI(current,total);
            }
        }
    }
}

public interface UIHandler {
    void onUpdateUI(int current,int total);
}

public static void registerUIHandler(UIHandler handler){
    if(handler != null){
        BackgroundServiceReceiver.mHandlers.add(handler);
    }

}

public static void unregisterUIHandler(UIHandler handler){
    BackgroundServiceReceiver.mHandlers.remove(handler);
}

這裡代碼有點多,一點一點說,

首先在manifest裡注冊一個靜態廣播接收器,靜態就是表示一直都會接收的,不需要手動register和unregister。一般的receiver都是單獨一個文件,這裡為了更好地封裝性,寫在Service裡作為靜態內部類。所以在manifest裡的注冊名字也寫成了.BackgroundService$BackgroundServiceReceiver,注意中間一個美元符號,那就是表示公共靜態內部類的標志。 在Service內部實現一個Receiver,具體和Activity裡面的一樣。 然後寫一個interface,代表具體的UI處理 寫一個注冊函數和反注冊函數,用以界面組件注冊UI更新事件。 由於該Service可能不止只更新一個界面組件,所以注冊的Handler是一個列表。在收到廣播後,將所有注冊過的組件都通知更新一遍。

然後在Activity中注冊一下。替換掉注冊廣播的地方。

@Override
protected void onResume() {
    super.onResume();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BackgroundService.BROADCAST_UPDATE_UI);
//        registerReceiver(mBackgroundServiceReceiver,intentFilter);
    BackgroundService.registerUIHandler(mServiceUIHandler);
}

@Override
protected void onPause() {
    super.onPause();
    BackgroundService.unregisterUIHandler(mServiceUIHandler);
//        unregisterReceiver(mBackgroundServiceReceiver);
}

private BackgroundService.UIHandler mServiceUIHandler = new BackgroundService.UIHandler() {
    @Override
    public void onUpdateUI(int current, int total) {
        Log.d(TAG,"receive: service broadcast");
        mHint.setText(current+"/"+total);
    }
};

這樣就完成了一個Service的封裝,簡化Activity的代碼,我的思想一直都是Activity中,應該只處理和界面有關的代碼。就像C語言的main函數一樣,你不可能把所有代碼都寫在main函數裡吧。或者把所有的函數寫在同一個文件裡吧。

這裡寫圖片描述
這裡需要注意的是,由於之前提過IntentService內部其實是一個Worker Thread,所以多按幾次start,其實是多發了幾次消息,導致會計數完成後,重新計數。這個自己感受下就知道了。

那麼我們加一個stop Service的函數吧。

Service

public static void stopCounterService(@NonNull Context context){
    Intent intent = new Intent(context, BackgroundService.class);
    intent.setAction(ACTION_COUNTER);
    context.stopService(intent);
}

Activity

public void stopService(View view){
//        Intent intent = new Intent(this,BackgroundService.class);
//        intent.setAction("com.example.administrator.servicestudy.action.counter");
//        stopService(intent);
    BackgroundService.stopCounterService(this);
}

IntentService是以Message為單位來停止的,也就是說,一定要等到當前消息處理完才能完全stop掉,為此我們可以加一個標志位,一旦Service停止,強制循環退出。

Service

@Override
public void onCreate() {
    super.onCreate();
    Log.d(TAG,"onCreate");
    mServiceFinished = false;
}

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

private void handleActionCounter(int duration, float interval) {
   for(int i=0; i

Service與通知欄的通信

至此我們已經完成了Service與Activity的通信,Service與Activity之間通過廣播進行通信。Service負責邏輯處理,Activity負責更新界面顯示。但是到這邊還沒發現Service的獨特之處,就是這個這些代碼完全也可以寫在Activity裡面的,寫在Service裡面無非就是結構更好看點,如果你那麼認為就錯了。你可以在Activity中退出再進入,可以發現計數器並沒有因為Activity的退出而終止或者暫停。依然跟著時間走。這點是寫在Activity中完全做不到的。當然你也可以通過一些小技巧來達到同樣的效果,不過我們這個例子是為了模擬後台下載用的。所以不扯這些了。

下面進入真正的後台下載。Service與通知欄的通信。
我們這樣設計一個程序,當Activity退出後,通知欄繼續顯示計數器進度,點擊通知或者再次進入Activity,通知欄取消顯示進度(為了不重復顯示,也為了演示代碼)。

為此我們新建一個新的Service,並在Activity添加如下代碼

NotificationService

public class NotificationService extends Service {
    private static final String TAG = NotificationService.class.getSimpleName();

    public NotificationService() {
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        BackgroundService.registerUIHandler(mUIHandler);
    }

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

這裡我們新建的是一個普通的service,而不是IntentService,因為這邊我們不需要耗時操作,我們甚至連onStartCommand都沒有重載,因為我們只需要在啟動服務的時候注冊一個UI更新的回調就可以了,然後在銷毀服務的時候注銷掉。

Activity

@Override
protected void onResume() {
    super.onResume();
    ...
    stopService(new Intent(this,NotificationService.class));
}

@Override
protected void onPause() {
    super.onPause();
    ...
    startService(new Intent(this,NotificationService.class));
}

我們在Activity Resume的時候關閉通知欄通知服務,在Pause的時候開啟該服務,這樣就能做到我們的設計初衷。

接下來就是通知欄的UI更新操作了,都是通知欄的接口,聽說2.3和4.0以上的接口很不一樣,我們這邊用的是4.0以上的接口。

private BackgroundService.UIHandler mUIHandler = new BackgroundService.UIHandler() {
    @Override
    public void onUpdateUI(int current, int total) {
        Log.d(TAG,"Notification onUpdateUI");
        //點擊通知後,啟動Activity,最後的FLAG_ONE_SHOT,表示只執行一次,具體自行百度。
        PendingIntent pendingIntent = PendingIntent.getActivity(NotificationService.this,
                0,
                new Intent(NotificationService.this,MainActivity.class),
                PendingIntent.FLAG_ONE_SHOT);

        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Notification.Builder builder = new Notification.Builder(getApplicationContext());
        Notification notification = builder.setContentTitle("Background Service")
                .setTicker("Counting...")//狀態欄上滾動的字符串
                .setContentText("Ongoing")//設置通知的正文
                .setProgress(total, current, false)//設置通知欄的進度條,android真貼心,終於可以不用自定義進度條了。
                .setOngoing(true)//設置可不可以取消該通知
                .setContentIntent(pendingIntent)//點擊該通知後的操作。
                .setDefaults(Notification.DEFAULT_ALL)//通知的音效、震動、呼吸燈全都隨系統設置,當然你也可以自定義
                .setAutoCancel(true)//是不是點擊之後自動取消,否則的話,可能你需要手動調用接口來取消
                .setOnlyAlertOnce(true)//音效震動呼吸燈是否只提醒一下,專門給進度條之類,頻繁更新的通知用的,不設置這個,你可以試試,那鬼畜的音效
                .setSmallIcon(R.mipmap.ic_launcher)//這個不解釋了
                .build();
        //第一個參數為ID,APP內全局唯一,相同的ID表示相同的通知,不會在通知欄新增一條通知,不同的話,則在通知欄插入一條新的通知。第二個參數就是剛才配置的通知。
        nm.notify(1234,notification);
    }
};

最後提醒一句,通知不配置PendingIntent是不會顯示的哦

這裡寫圖片描述

為了完美模擬後台下載,我們在下載完成後(服務被銷毀後),發送一個結束廣播,通知UI層。
Service

public interface UIHandler {
    void onUpdateUI(int current,int total);
    void onFinish();
}

新增一個結束時的回調

@Override
public void onDestroy() {
    ....
    Intent intent = new Intent(BROADCAST_FINISH);
    sendBroadcast(intent);
}

在被銷毀時發送廣播

@Override
public void onReceive(Context context, Intent intent) {
    if(intent.getAction().equals(BROADCAST_UPDATE_UI)){
       ....
    }else if(intent.getAction().equals(BROADCAST_FINISH)){
        for (UIHandler handler : mHandlers) {
            handler.onFinish();
        }
    }
}

在onReceive中發送onFinish的回調


  
      
      
  

最重要的是別忘了在manifest中聲明這個廣播,因為Service中的是靜態廣播接收器

而在Activity和Notification中就簡單多了,只要實現相應的onFinish回調就可以了

@Override
public void onFinish() {
    Log.d(TAG,"receive: service finish");
    mHint.setText("Finished");
}
@Override
public void onFinish() {
    Log.d(TAG,"Notification onFinish");

    NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Notification.Builder builder = new Notification.Builder(getApplicationContext());
    Notification notification = builder.setContentTitle("Background Service")
            .setContentText("Finished")
            .setOngoing(false)
            .setContentIntent(null)//這裡PendingIntent設置為null,只是為了演示代碼,這樣這個通知點上去就不會有反應
            .setDefaults(Notification.DEFAULT_ALL)
            .setAutoCancel(true)
            .setOnlyAlertOnce(true)
            .setSmallIcon(R.mipmap.ic_launcher)
            .build();

    //設置兩個不同的notification ID,為了演示兩個不同通知,並且演示如何取消一個通知
    nm.notify(1232,notification);
    nm.cancel(1234);
}

教程到此結束。謝謝

最後公布,文中一道問題的答案,A和D。很簡單吧
源碼點這裡下載

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