Android Service進階

2023-03-31 13:45 更新

本節(jié)引言

上節(jié)我們學(xué)習(xí)了Service的生命周期,以及兩種啟動Service的兩種方法, 本節(jié)繼續(xù)來深入了解Service中的IntentService,Service的使用實例: 前臺服務(wù)與輪詢的實現(xiàn)!


1.IntentService的使用

在上一節(jié)后我們已經(jīng)知道了如何去定義和啟動Service,但是如果我們直接把 耗時線程放到Service中的onStart()方法中,雖然可以這樣做,但是很容易 會引起ANR異常(Application Not Responding),而Android的官方在介紹 Service有下面這樣一段話:

直接翻譯:

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

于是乎,Android給我們提供了解決上述問題的替代品,就是下面要講的IntentService; IntentService是繼承與Service并處理異步請求的一個類,在IntentService中有 一個工作線程來處理耗時操作,請求的Intent記錄會加入隊列

工作流程:

客戶端通過startService(Intent)來啟動IntentService; 我們并不需要手動地區(qū)控制IntentService,當(dāng)任務(wù)執(zhí)行完后,IntentService會自動停止; 可以啟動IntentService多次,每個耗時操作會以工作隊列的方式在IntentService的 onHandleIntent回調(diào)方法中執(zhí)行,并且每次只會執(zhí)行一個工作線程,執(zhí)行完一,再到二這樣!

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

TestService3.java

public class TestService3 extends IntentService {  
    private final String TAG = "hehe";  
    //必須實現(xiàn)父類的構(gòu)造方法  
    public TestService3()  
    {  
        super("TestService3");  
    }  

    //必須重寫的核心方法  
    @Override  
    protected void onHandleIntent(Intent intent) {  
        //Intent是從Activity發(fā)過來的,攜帶識別參數(shù),根據(jù)參數(shù)不同執(zhí)行不同的任務(wù)  
        String action = intent.getExtras().getString("param");  
        if(action.equals("s1"))Log.i(TAG,"啟動service1");  
        else if(action.equals("s2"))Log.i(TAG,"啟動service2");  
        else if(action.equals("s3"))Log.i(TAG,"啟動service3");  

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

    //重寫其他方法,用于查看方法的調(diào)用順序  
    @Override  
    public IBinder onBind(Intent intent) {  
        Log.i(TAG,"onBind");  
        return super.onBind(intent);  
    }  

    @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 setIntentRedelivery(boolean enabled) {  
        super.setIntentRedelivery(enabled);  
        Log.i(TAG,"setIntentRedelivery");  
    }  

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

} 

AndroidManifest.xml注冊下Service

<service android:name=".TestService3" android:exported="false">  
    <intent-filter >  
        <action android:name="com.test.intentservice"/>  
    </intent-filter>  
</service>  

在MainActivity啟動三次服務(wù):

public class MainActivity extends Activity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        Intent it1 = new Intent("com.test.intentservice");  
        Bundle b1 = new Bundle();  
        b1.putString("param", "s1");  
        it1.putExtras(b1);  

        Intent it2 = new Intent("com.test.intentservice");  
        Bundle b2 = new Bundle();  
        b2.putString("param", "s2");  
        it2.putExtras(b2);  

        Intent it3 = new Intent("com.test.intentservice");  
        Bundle b3 = new Bundle();  
        b3.putString("param", "s3");  
        it3.putExtras(b3);  

        //接著啟動多次IntentService,每次啟動,都會新建一個工作線程  
        //但始終只有一個IntentService實例  
        startService(it1);  
        startService(it2);  
        startService(it3);  
    }  
} 

運行截圖:

小結(jié):

當(dāng)一個后臺的任務(wù),需要分成幾個子任務(wù),然后按先后順序執(zhí)行,子任務(wù) (簡單的說就是異步操作),此時如果我們還是定義一個普通Service然后 在onStart方法中開辟線程,然后又要去控制線程,這樣顯得非常的繁瑣; 此時應(yīng)該自定義一個IntentService然后再onHandleIntent()方法中完成相關(guān)任務(wù)!


2.Activity與Service通信

我們前面的操作都是通過Activity啟動和停止Service,假如我們啟動的是一個下載 的后臺Service,而我們想知道Service中下載任務(wù)的進度!那么這肯定是需要Service 與Activity進行通信的,而他們之間交流的媒介就是Service中的onBind()方法! 返回一個我們自定義的Binder對象!

基本流程如下:

  • 1.自定義Service中,自定義一個Binder類,然后將需要暴露的方法都寫到該類中!
  • 2.Service類中,實例化這個自定義Binder類,然后重寫onBind()方法,將這個Binder對象返回!
  • 3.Activity類中實例化一個ServiceConnection對象,重寫onServiceConnected()方法,然后 獲取Binder對象,然后調(diào)用相關(guān)方法即可!

    • *

3.一個簡單前臺服務(wù)的實現(xiàn)

學(xué)到現(xiàn)在,我們都知道Service一般都是運行在后來的,但是Service的系統(tǒng)優(yōu)先級 還是比較低的,當(dāng)系統(tǒng)內(nèi)存不足的時候,就有可能回收正在后臺運行的Service, 對于這種情況我們可以使用前臺服務(wù),從而讓Service稍微沒那么容易被系統(tǒng)殺死, 當(dāng)然還是有可能被殺死的...所謂的前臺服務(wù)就是狀態(tài)欄顯示的Notification!

實現(xiàn)起來也很簡單,最近做的項目剛好用到這個前臺服務(wù),就把核心的代碼摳出來 分享下:

在自定義的Service類中,重寫onCreate(),然后根據(jù)自己的需求定制Notification; 定制完畢后,調(diào)用startForeground(1,notification對象)即可! 核心代碼如下:

public void onCreate()
{
    super.onCreate();
    Notification.Builder localBuilder = new Notification.Builder(this);
    localBuilder.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0));
    localBuilder.setAutoCancel(false);
    localBuilder.setSmallIcon(R.mipmap.ic_cow_icon);
    localBuilder.setTicker("Foreground Service Start");
    localBuilder.setContentTitle("Socket服務(wù)端");
    localBuilder.setContentText("正在運行...");
    startForeground(1, localBuilder.getNotification());
}

運行效果截圖:


4.簡單定時后臺線程的實現(xiàn)

除了上述的前臺服務(wù)外,實際開發(fā)中Service還有一種常見的用法,就是執(zhí)行定時任務(wù), 比如輪詢,就是每間隔一段時間就請求一次服務(wù)器,確認(rèn)客戶端狀態(tài)或者進行信息更新 等!而Android中給我們提供的定時方式有兩種使用Timer類與Alarm機制!

前者不適合于需要長期在后臺運行的定時任務(wù),CPU一旦休眠,Timer中的定時任務(wù) 就無法運行;Alarm則不存在這種情況,他具有喚醒CPU的功能,另外,也要區(qū)分CPU 喚醒與屏幕喚醒!

使用流程:

  • Step 1:獲得Service: AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
  • Step 2:通過set方法設(shè)置定時任務(wù) int anHour = 2 * 1000; long triggerAtTime = SystemClock.elapsedRealtime() + anHour; manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,pendingIntent);
  • Step 3:定義一個Service 在onStartCommand中開辟一條事務(wù)線程,用于處理一些定時邏輯
  • Step 4:定義一個Broadcast(廣播),用于啟動Service 最后別忘了,在AndroidManifest.xml中對這Service與Boradcast進行注冊!

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

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

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

②startTime: 鬧鐘的第一次執(zhí)行時間,以毫秒為單位,可以自定義時間,不過一般使用當(dāng)前時間。 需要注意的是,本屬性與第一個屬性(type)密切相關(guān),如果第一個參數(shù)對應(yīng)的鬧鐘 使用的是相對時間(ELAPSED_REALTIMEELAPSED_REALTIME_WAKEUP),那么本屬 性就得使用相對時間(相對于系統(tǒng)啟動時間來說),比如當(dāng)前時間就表示為: SystemClock.elapsedRealtime();如果第一個參數(shù)對應(yīng)的鬧鐘使用的是絕對時間 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那么本屬性就得使用絕對時間, 比如當(dāng)前時間就表示為:System.currentTimeMillis()。

③PendingIntent: 綁定了鬧鐘的執(zhí)行動作,比如發(fā)送一個廣播、給出提示等等。PendingIntent 是Intent的封裝類。
需要注意的是,如果是通過啟動服務(wù)來實現(xiàn)鬧鐘提示的話, PendingIntent對象的獲取就應(yīng)該采用Pending.getService (Context c,int i,Intent intent,int j)方法;
如果是通過廣播來實現(xiàn)鬧鐘提示的話, PendingIntent對象的獲取就應(yīng)該采用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法;
如果是采用Activity的方式來實現(xiàn)鬧鐘提示的話,PendingIntent對象的獲取 就應(yīng)該采用 PendingIntent.getActivity(Context c,int i,Intent intent,int j) 方法。
如果這三種方法錯用了的話,雖然不會報錯,但是看不到鬧鐘提示效果。

另外:

從4.4版本后(API 19),Alarm任務(wù)的觸發(fā)時間可能變得不準(zhǔn)確,有可能會延時,是系統(tǒng) 對于耗電性的優(yōu)化,如果需要準(zhǔn)確無誤可以調(diào)用setExtra()方法~

核心代碼:

public class LongRunningService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //這里開辟一條線程,用來執(zhí)行具體的邏輯操作:
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d("BackService", new Date().toString());
            }
        }).start();
        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        //這里是定時的,這里設(shè)置的是每隔兩秒打印一次時間=-=,自己改
        int anHour = 2 * 1000;
        long triggerAtTime = 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);
    }
}

AlarmReceiver.java

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent i = new Intent(context,LongRunningService.class);
        context.startService(i);
    }
}

本節(jié)小結(jié):

本節(jié)我們繼續(xù)對Service進行更深入的學(xué)習(xí),IntentService以及Service 在實際開發(fā)中的兩個常用的案例:前臺Service的實現(xiàn),以及Service后臺 Service的實現(xiàn)!下一節(jié)中我們會繼續(xù)研究Service的AIDL,跨進程通信, 敬請期待~

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號