上節(jié)我們學(xué)習(xí)了Service的生命周期,以及兩種啟動(dòng)Service的兩種方法, 本節(jié)繼續(xù)來(lái)深入了解Service中的IntentService,Service的使用實(shí)例: 前臺(tái)服務(wù)與輪詢的實(shí)現(xiàn)!
在上一節(jié)后我們已經(jīng)知道了如何去定義和啟動(dòng)Service,但是如果我們直接把 耗時(shí)線程放到Service中的onStart()方法中,雖然可以這樣做,但是很容易 會(huì)引起ANR異常(Application Not Responding),而Android的官方在介紹 Service有下面這樣一段話:
直接翻譯:
1.Service不是一個(gè)單獨(dú)的進(jìn)程,它和它的應(yīng)用程序在同一個(gè)進(jìn)程中
2.Service不是一個(gè)線程,這樣就意味著我們應(yīng)該避免在Service中進(jìn)行耗時(shí)操作
于是乎,Android給我們提供了解決上述問(wèn)題的替代品,就是下面要講的IntentService; IntentService是繼承與Service并處理異步請(qǐng)求的一個(gè)類,在IntentService中有 一個(gè)工作線程來(lái)處理耗時(shí)操作,請(qǐng)求的Intent記錄會(huì)加入隊(duì)列
工作流程:
客戶端通過(guò)startService(Intent)來(lái)啟動(dòng)IntentService; 我們并不需要手動(dòng)地區(qū)控制IntentService,當(dāng)任務(wù)執(zhí)行完后,IntentService會(huì)自動(dòng)停止; 可以啟動(dòng)IntentService多次,每個(gè)耗時(shí)操作會(huì)以工作隊(duì)列的方式在IntentService的 onHandleIntent回調(diào)方法中執(zhí)行,并且每次只會(huì)執(zhí)行一個(gè)工作線程,執(zhí)行完一,再到二這樣!
再接著是代碼演示,網(wǎng)上大部分的代碼都是比較Service與IntentService的, 定義足夠長(zhǎng)的休眠時(shí)間,演示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";
//必須實(shí)現(xiàn)父類的構(gòu)造方法
public TestService3()
{
super("TestService3");
}
//必須重寫的核心方法
@Override
protected void onHandleIntent(Intent intent) {
//Intent是從Activity發(fā)過(guò)來(lái)的,攜帶識(shí)別參數(shù),根據(jù)參數(shù)不同執(zhí)行不同的任務(wù)
String action = intent.getExtras().getString("param");
if(action.equals("s1"))Log.i(TAG,"啟動(dòng)service1");
else if(action.equals("s2"))Log.i(TAG,"啟動(dòng)service2");
else if(action.equals("s3"))Log.i(TAG,"啟動(dòng)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注冊(cè)下Service
<service android:name=".TestService3" android:exported="false">
<intent-filter >
<action android:name="com.test.intentservice"/>
</intent-filter>
</service>
在MainActivity啟動(dòng)三次服務(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);
//接著啟動(dòng)多次IntentService,每次啟動(dòng),都會(huì)新建一個(gè)工作線程
//但始終只有一個(gè)IntentService實(shí)例
startService(it1);
startService(it2);
startService(it3);
}
}
運(yùn)行截圖:
小結(jié):
當(dāng)一個(gè)后臺(tái)的任務(wù),需要分成幾個(gè)子任務(wù),然后按先后順序執(zhí)行,子任務(wù) (簡(jiǎn)單的說(shuō)就是異步操作),此時(shí)如果我們還是定義一個(gè)普通Service然后 在onStart方法中開(kāi)辟線程,然后又要去控制線程,這樣顯得非常的繁瑣; 此時(shí)應(yīng)該自定義一個(gè)IntentService然后再onHandleIntent()方法中完成相關(guān)任務(wù)!
我們前面的操作都是通過(guò)Activity啟動(dòng)和停止Service,假如我們啟動(dòng)的是一個(gè)下載 的后臺(tái)Service,而我們想知道Service中下載任務(wù)的進(jìn)度!那么這肯定是需要Service 與Activity進(jìn)行通信的,而他們之間交流的媒介就是Service中的onBind()方法! 返回一個(gè)我們自定義的Binder對(duì)象!
基本流程如下:
3.Activity類中實(shí)例化一個(gè)ServiceConnection對(duì)象,重寫onServiceConnected()方法,然后 獲取Binder對(duì)象,然后調(diào)用相關(guān)方法即可!
學(xué)到現(xiàn)在,我們都知道Service一般都是運(yùn)行在后來(lái)的,但是Service的系統(tǒng)優(yōu)先級(jí) 還是比較低的,當(dāng)系統(tǒng)內(nèi)存不足的時(shí)候,就有可能回收正在后臺(tái)運(yùn)行的Service, 對(duì)于這種情況我們可以使用前臺(tái)服務(wù),從而讓Service稍微沒(méi)那么容易被系統(tǒng)殺死, 當(dāng)然還是有可能被殺死的...所謂的前臺(tái)服務(wù)就是狀態(tài)欄顯示的Notification!
實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,最近做的項(xiàng)目剛好用到這個(gè)前臺(tái)服務(wù),就把核心的代碼摳出來(lái) 分享下:
在自定義的Service類中,重寫onCreate(),然后根據(jù)自己的需求定制Notification; 定制完畢后,調(diào)用startForeground(1,notification對(duì)象)即可! 核心代碼如下:
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("正在運(yùn)行...");
startForeground(1, localBuilder.getNotification());
}
運(yùn)行效果截圖:
除了上述的前臺(tái)服務(wù)外,實(shí)際開(kāi)發(fā)中Service還有一種常見(jiàn)的用法,就是執(zhí)行定時(shí)任務(wù), 比如輪詢,就是每間隔一段時(shí)間就請(qǐng)求一次服務(wù)器,確認(rèn)客戶端狀態(tài)或者進(jìn)行信息更新 等!而Android中給我們提供的定時(shí)方式有兩種使用Timer類與Alarm機(jī)制!
前者不適合于需要長(zhǎng)期在后臺(tái)運(yùn)行的定時(shí)任務(wù),CPU一旦休眠,Timer中的定時(shí)任務(wù) 就無(wú)法運(yùn)行;Alarm則不存在這種情況,他具有喚醒CPU的功能,另外,也要區(qū)分CPU 喚醒與屏幕喚醒!
使用流程:
- Step 1:獲得Service: AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
- Step 2:通過(guò)set方法設(shè)置定時(shí)任務(wù) int anHour = 2 * 1000; long triggerAtTime = SystemClock.elapsedRealtime() + anHour; manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,pendingIntent);
- Step 3:定義一個(gè)Service 在onStartCommand中開(kāi)辟一條事務(wù)線程,用于處理一些定時(shí)邏輯
- Step 4:定義一個(gè)Broadcast(廣播),用于啟動(dòng)Service 最后別忘了,在AndroidManifest.xml中對(duì)這Service與Boradcast進(jìn)行注冊(cè)!
參數(shù)詳解: set(int type,long startTime,PendingIntent pi)
①type: 有五個(gè)可選值:
AlarmManager.ELAPSED_REALTIME: 鬧鐘在手機(jī)睡眠狀態(tài)下不可用,該狀態(tài)下鬧鐘使用相對(duì)時(shí)間(相對(duì)于系統(tǒng)啟動(dòng)開(kāi)始),狀態(tài)值為3;
AlarmManager.ELAPSED_REALTIME_WAKEUP 鬧鐘在睡眠狀態(tài)下會(huì)喚醒系統(tǒng)并執(zhí)行提示功能,該狀態(tài)下鬧鐘也使用相對(duì)時(shí)間,狀態(tài)值為2;
AlarmManager.RTC 鬧鐘在睡眠狀態(tài)下不可用,該狀態(tài)下鬧鐘使用絕對(duì)時(shí)間,即當(dāng)前系統(tǒng)時(shí)間,狀態(tài)值為1;
AlarmManager.RTC_WAKEUP 表示鬧鐘在睡眠狀態(tài)下會(huì)喚醒系統(tǒng)并執(zhí)行提示功能,該狀態(tài)下鬧鐘使用絕對(duì)時(shí)間,狀態(tài)值為0;
AlarmManager.POWER_OFF_WAKEUP 表示鬧鐘在手機(jī)關(guān)機(jī)狀態(tài)下也能正常進(jìn)行提示功能,所以是5個(gè)狀態(tài)中用的最多的狀態(tài)之一, 該狀態(tài)下鬧鐘也是用絕對(duì)時(shí)間,狀態(tài)值為4;不過(guò)本狀態(tài)好像受SDK版本影響,某些版本并不支持;
PS:第一個(gè)參數(shù)決定第二個(gè)參數(shù)的類型,如果是REALTIME的話就用: SystemClock.elapsedRealtime( )方法可以獲得系統(tǒng)開(kāi)機(jī)到現(xiàn)在經(jīng)歷的毫秒數(shù) 如果是RTC的就用:System.currentTimeMillis()可獲得從1970.1.1 0點(diǎn)到 現(xiàn)在做經(jīng)歷的毫秒數(shù)
②startTime: 鬧鐘的第一次執(zhí)行時(shí)間,以毫秒為單位,可以自定義時(shí)間,不過(guò)一般使用當(dāng)前時(shí)間。 需要注意的是,本屬性與第一個(gè)屬性(type)密切相關(guān),如果第一個(gè)參數(shù)對(duì)應(yīng)的鬧鐘 使用的是相對(duì)時(shí)間(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本屬 性就得使用相對(duì)時(shí)間(相對(duì)于系統(tǒng)啟動(dòng)時(shí)間來(lái)說(shuō)),比如當(dāng)前時(shí)間就表示為: SystemClock.elapsedRealtime();如果第一個(gè)參數(shù)對(duì)應(yīng)的鬧鐘使用的是絕對(duì)時(shí)間 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那么本屬性就得使用絕對(duì)時(shí)間, 比如當(dāng)前時(shí)間就表示為:System.currentTimeMillis()。
③PendingIntent: 綁定了鬧鐘的執(zhí)行動(dòng)作,比如發(fā)送一個(gè)廣播、給出提示等等。PendingIntent 是Intent的封裝類。
需要注意的是,如果是通過(guò)啟動(dòng)服務(wù)來(lái)實(shí)現(xiàn)鬧鐘提示的話, PendingIntent對(duì)象的獲取就應(yīng)該采用Pending.getService (Context c,int i,Intent intent,int j)方法;
如果是通過(guò)廣播來(lái)實(shí)現(xiàn)鬧鐘提示的話, PendingIntent對(duì)象的獲取就應(yīng)該采用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法;
如果是采用Activity的方式來(lái)實(shí)現(xiàn)鬧鐘提示的話,PendingIntent對(duì)象的獲取 就應(yīng)該采用 PendingIntent.getActivity(Context c,int i,Intent intent,int j) 方法。
如果這三種方法錯(cuò)用了的話,雖然不會(huì)報(bào)錯(cuò),但是看不到鬧鐘提示效果。
另外:
從4.4版本后(API 19),Alarm任務(wù)的觸發(fā)時(shí)間可能變得不準(zhǔn)確,有可能會(huì)延時(shí),是系統(tǒng) 對(duì)于耗電性的優(yōu)化,如果需要準(zhǔn)確無(wú)誤可以調(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) {
//這里開(kāi)辟一條線程,用來(lái)執(zhí)行具體的邏輯操作:
new Thread(new Runnable() {
@Override
public void run() {
Log.d("BackService", new Date().toString());
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
//這里是定時(shí)的,這里設(shè)置的是每隔兩秒打印一次時(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é)我們繼續(xù)對(duì)Service進(jìn)行更深入的學(xué)習(xí),IntentService以及Service 在實(shí)際開(kāi)發(fā)中的兩個(gè)常用的案例:前臺(tái)Service的實(shí)現(xiàn),以及Service后臺(tái) Service的實(shí)現(xiàn)!下一節(jié)中我們會(huì)繼續(xù)研究Service的AIDL,跨進(jìn)程通信, 敬請(qǐng)期待~
更多建議: