Service進階


本節引言

上節我們學習了Service的生命週期,以及兩種啟動Service的兩種方法, 本節繼續來深入了解Service中的IntentService,Service的使用實例: 前台服務與輪詢的實作!


1.IntentService的使用

在上一節後我們已經知道如何去定義和啟動Service,但是如果我們直接把 耗時執行緒放到Service中的onStart()方法中,雖然可以這樣做,但是很容易 會造成ANR異常(Application Not Responding),而Android的官方在介紹 Service有下面這樣一段話:直接翻譯:

1.Service不是一個單獨的進程,它和它的應用程式在同一個進程中
2. Service不是一個線程,這樣就意味著我們應該避免在Service中進行耗時操作

於是乎,Android給我們提供了解決上述問題的替代品,就是下面要講的IntentService; IntentService是繼承與Service並處理非同步請求的一個類別,在IntentService中有 一個工作執行緒來處理耗時作業,請求的Intent記錄會加入佇列

工作流程:

客戶端透過startService(Intent)來啟動IntentService ; 我們並不需要手動地區控制IntentService,當任務執行完後,IntentService會自動停止; 可以啟動IntentService多次,每個耗時操作會以工作佇列的方式在IntentService的 onHandleIntent回呼方法中執行,並且每次只會執行一個工作線程,執行完一,再到二這樣!

再接著是代碼演示,網上大部分的代碼都是比較Service與IntentService的, 定義足夠長的休眠時間,演示Service的ANR異常,然後引出IntentService有多好! 這裡就不示範Service了,網路上的都是自訂Service,然後在onStart()方法 中Thread.sleep(20000)然後引發ANR異常,有興趣的可以自己寫程式碼試試, 這裡的話只示範下IntentService的用法!

TestService3.java

public class TestService3 extends IntentService {  
    private final String TAG = "hehe";  
    {  
# super("TestService3");  
    }  
  
    //必須重寫的核心方法  
    #        //Intent是從Activity寄來的,攜帶辨識參數,依照參數不同執行不同的任務  
        String action = intent.getExtras().getString("param");  # i(TAG,"啟動service1");  
        else if(action.equals("s2"))Log.i(TAG,"啟動service2");  # ))Log.i(TAG,"啟動service3");  
          
        //讓服務休眠2秒    Thread.sleep(2000);  
        }catch(InterruptedException e ){e.printStackTrace();}          
    }  
  
    //重複使用Bind(Intent intent) {  
        Log.i(TAG,"onBind");  
        return super.onBind(intent);     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 setIntent );  
        Log.i(TAG,"setIntentRedelivery");  
    }  
      
    @Override  
    public void onDestroy(  
        super.onDestroy();  
    }  
      
}

AndroidManifest. Testxml註冊下Service

  
    <意圖過濾器>  
          
      
</服務>

#

在MainActivity啟動三次服務:

public class MainActivityextends Activity{  
  
    @Override  ##  
    @Override  ## #        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
          
        意圖 it1 = new Intent("com.test.intentservice");  
        捆綁b1 = new Bundle();  
        b1.putString("param", "s1");  
        it1.putExtras(b1);  
          
        Intent it2 = new Intent("com.test.intentservice");  
        捆綁b2 = new Bundle();  
        b2.putString("param", "s2");  
        it2.putExtras(b2);  
          
        意圖 it3 = new Intent("com.test.intentservice");  
        捆綁b3 = new Bundle();  
        b3.putString("param", "s3");  
        it3.putExtras(b3);  
          
        //接著啟動多次IntentService,且每次啟動,新一個工作執行緒  
      
        startService(it2);  
        startService(it3);  
    }  
}

#

運行截圖:

1.jpg

#小結:

當一個後台的任務,需要分成幾個子任務,然後按先後順序執行,子任務 (簡單的說就是非同步操作),此時如果我們還是定義一個普通Service然後 在onStart方法中開闢線程,然後又要去控制線程,這樣顯得非常的繁瑣; 此時應該自訂一個IntentService然後再onHandleIntent()方法中完成相關任務!


2.Activity與Service通訊

我們前面的操作都是透過Activity啟動和停止Service,假如我們啟動的是一個下載 的後台Service,而我們想知道Service中下載任務的進度!那這肯定是需要Service 與Activity溝通的,而他們之間溝通的媒介就是Service中的onBind()方法! 傳回一個我們自訂的Binder物件!

基本流程如下:

  • 1.自訂Service中,自訂一個Binder類,然後將需要暴露的方法都寫到該類別中!
  • 2.Service類別中,實例化這個自訂Binder類,然後重寫onBind()方法,將這個Binder物件回傳!
  • 3.Activity類別中實例化一個ServiceConnection對象,重寫onServiceConnected()方法,然後 取得Binder對象,然後呼叫相關方法即可!

3.一個簡單前台服務的實作

學到現在,我們都知道Service一般都是運行在後來的,但是Service的系統優先級 還是比較低的,當系統記憶體不足的時候,就有可能回收正在後台運行的Service, 對於這種情況我們可以使用前台服務,從而讓Service稍微沒那麼容易被系統殺死, 當然還是有可能被殺死的...所謂的前台服務就是狀態列顯示的Notification!

實現起來也很簡單,最近做的專案剛好用到這個前台服務,就把核心的程式碼摳出來 分享下:

在自訂的Service類別中,重寫onCreate(),然後依照自己的需求自訂Notification; 定製完畢後,呼叫startForeground(1,notification物件)即可! 核心程式碼如下:

public void onCreate()
#{
#    super.onCreate();
    Notification.Builder localBuilder = new Notification.#this); .getActivity (this, 0, new Intent(this, MainActivity.class), 0));
    localBuilder.setAutoCancel(false);
    localBuilder.setSmmallIcon(R.p.map setTicker( "前台服務啟動");
    localBuilder.setContentTitle("Socket服務端");
    localBuilder.setContentText("正在運作...");
   )) ;
}

運行效果截圖:

2.png


#4.簡單定時後台執行緒的實作

除了上述的前台服務外,實際開發中Service還有一種常見的用法,就是執行定時任務, 例如輪詢,就是每間隔一段時間就請求一次伺服器,確認客戶端狀態或是進行資訊更新 等!而Android中提供給我們的定時方式有兩種使用Timer類別與Alarm機制!

前者不適合需要長期在背景執行的定時任務,CPU一旦休眠,Timer中的定時任務 就無法運作;Alarm則不存在這種情況,他有喚醒CPU的功能,另外,也要區分CPU 喚醒與螢幕喚醒!

使用流程:

  • #Step 1:取得Service:AlarmManager manager = (AlarmManager) getSystemService( ALARM_SERVICE);
  • Step 2:透過set方法設定定時任務int anHour = 2 * 1000; long triggerAtTime = SystemClock.elapsedRealtime() + anHour; manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,pendingIntent);
  • Step 3:定義一個Service在onStartCommand中開闢一條事務執行緒,用來處理一些定時邏輯
  • Step 4:定義一個Broadcast(廣播),用於啟動Service最後別忘了,在AndroidManifest.xml中對這Service與Boradcast進行註冊!

參數詳解:set(int type,long startTime,PendingIntent pi)

①type:有五個可選值:
AlarmManager.ELAPSED_REALTIME:鬧鐘在手機睡眠狀態下不可用,該狀態下鬧鐘使用相對時間(相對於系統啟動開始),狀態值為3;
AlarmManager.ELAPSED_REALTIME_WAKEUP鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,在該狀態下鬧鐘也使用相對時間,狀態值為2;
AlarmManager .RTC鬧鐘在睡眠狀態下不可用,該狀態下鬧鐘使用絕對時間,即當前系統時間,狀態值為1;
AlarmManager.RTC_WAKEUP表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,此狀態下鬧鐘使用絕對時間,狀態值為0;
AlarmManager.POWER_OFF_WAKEUP表示鬧鐘在手機關機狀態下也能正常進行提示功能,所以是5個狀態中用的最多的狀態之一, 此狀態下鬧鐘也是用絕對時間,狀態值為4;不過本狀態好像受SDK版本影響,某些版本並不支援;

PS:第一個參數決定第二個參數的類型,如果是REALTIME的話就用: SystemClock.elapsedRealtime( )方法可以取得系統開機到現在經歷的毫秒數 如果是RTC的就用:System.currentTimeMillis()可取得從1970.1.1 0點到 現在做經歷的毫秒數

②startTime:鬧鐘的第一次執行時間,以毫秒為單位,可以自訂時間,不過一般使用目前時間。 需要注意的是,本屬性與第一個屬性(type)密切相關,如果第一個參數對應的鬧鐘 使用的是相對時間(ELAPSED_REALTIMEELAPSED_REALTIME_WAKEUP),那麼本屬 性就得使用相對時間(相對於系統啟動時間來說),例如當前時間就表示為: SystemClock.elapsedRealtime();如果第一個參數對應的鬧鐘使用的是絕對時間 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那麼本屬性就得使用絕對時間, 例如當前時間就表示為:System.currentTimeMillis()。

③PendingIntent:綁定了鬧鐘的執行動作,例如發送一個廣播、給出提示等等。 PendingIntent 是Intent的封裝類別。
要注意的是,如果是透過啟動服務來實現鬧鐘提示的話, PendingIntent物件的取得就應該採用Pending.getService (Context c,int i,Intent intent,int j)方法;
如果是透過廣播來實現鬧鐘提示的話, PendingIntent物件的取得就應該採用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法;
如果是採用Activity的方式來實現鬧鐘提示的話,PendingIntent物件的獲取 就應該採用 PendingIntent.getActivity(Context c,int i,Intent intent,int j) 方法。
如果這三種方法都錯用了的話,雖然不會報錯,但是看不到鬧鐘提示效果。

另外:

從4.4版本後(API 19),Alarm任務的觸發時間可能變得不準確,有可能會延時,是系統 對於耗電性的最佳化,如果需要準確無誤可以呼叫setExtra()方法~

核心程式碼:

public class LongRunningService extends Service {
#    @Override
    public IBinder onBind(Intent int    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //這裡開闢一條執行緒,用來執行特定的邏輯作業:#       @Override


public void run() {
                Log.d("BackService", new Date().tocon }# ).start();
        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        //這裡是定時的,這裡設定的是每隔兩秒列印一次時間=-=,自己改
        int gerAtTime = SystemClock. elapsedRealtime() + anHour;
        Intent i = new Intent(this,AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
        manager.set(AlarmManager. ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
        return super.onStartCommand(intent, flags, startId);


public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public  . new Intent(context,LongRunningService.class) ;
        context.startService(i);
    }
}
#

本節小結:

本節我們持續對Service進行更深入的學習,IntentService以及Service 在實際開發中的兩個常用的案例:前台Service的實現,以及Service後台 Service的實作!下一節我們會繼續研究Service的AIDL,跨進程通信, 敬請期待~


參考文獻:《第一行程式碼 Android》— 郭霖:很好的一本Android入門書!