# 4.2.2 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";
//必須實現父類的構造方法
public TestService3()
{
super("TestService3");
}
//必須重寫的核心方法
@Override
protected void onHandleIntent(Intent intent) {
//Intent是從Activity發過來的,攜帶識別參數,根據參數不同執行不同的任務
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");
//讓服務休眠2秒
try{
Thread.sleep(2000);
}catch(InterruptedException e){e.printStackTrace();}
}
//重寫其他方法,用于查看方法的調用順序
@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啟動三次服務:**
```
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);
}
}
```
**運行截圖:**

**小結:**
> 當一個后臺的任務,需要分成幾個子任務,然后按先后順序執行,子任務 (簡單的說就是異步操作),此時如果我們還是定義一個普通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.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服務端");
localBuilder.setContentText("正在運行...");
startForeground(1, localBuilder.getNotification());
}
```
**運行效果截圖:**

## 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_REALTIME**和**ELAPSED_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 intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//這里開辟一條線程,用來執行具體的邏輯操作:
new Thread(new Runnable() {
@Override
public void run() {
Log.d("BackService", new Date().toString());
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
//這里是定時的,這里設置的是每隔兩秒打印一次時間=-=,自己改
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);
}
}
```
## 本節小結:
本節我們繼續對Service進行更深入的學習,IntentService以及Service 在實際開發中的兩個常用的案例:前臺Service的實現,以及Service后臺 Service的實現!下一節中我們會繼續研究Service的AIDL,跨進程通信, 敬請期待~
**參考文獻:** 《第一行代碼 Android》—— 郭霖:很好的一本Android入門書!
- 1.0 Android基礎入門教程
- 1.0.1 2015年最新Android基礎入門教程目錄
- 1.1 背景相關與系統架構分析
- 1.2 開發環境搭建
- 1.2.1 使用Eclipse + ADT + SDK開發Android APP
- 1.2.2 使用Android Studio開發Android APP
- 1.3 SDK更新不了問題解決
- 1.4 Genymotion模擬器安裝
- 1.5.1 Git使用教程之本地倉庫的基本操作
- 1.5.2 Git之使用GitHub搭建遠程倉庫
- 1.6 .9(九妹)圖片怎么玩
- 1.7 界面原型設計
- 1.8 工程相關解析(各種文件,資源訪問)
- 1.9 Android程序簽名打包
- 1.11 反編譯APK獲取代碼&資源
- 2.1 View與ViewGroup的概念
- 2.2.1 LinearLayout(線性布局)
- 2.2.2 RelativeLayout(相對布局)
- 2.2.3 TableLayout(表格布局)
- 2.2.4 FrameLayout(幀布局)
- 2.2.5 GridLayout(網格布局)
- 2.2.6 AbsoluteLayout(絕對布局)
- 2.3.1 TextView(文本框)詳解
- 2.3.2 EditText(輸入框)詳解
- 2.3.3 Button(按鈕)與ImageButton(圖像按鈕)
- 2.3.4 ImageView(圖像視圖)
- 2.3.5.RadioButton(單選按鈕)&Checkbox(復選框)
- 2.3.6 開關按鈕ToggleButton和開關Switch
- 2.3.7 ProgressBar(進度條)
- 2.3.8 SeekBar(拖動條)
- 2.3.9 RatingBar(星級評分條)
- 2.4.1 ScrollView(滾動條)
- 2.4.2 Date & Time組件(上)
- 2.4.3 Date & Time組件(下)
- 2.4.4 Adapter基礎講解
- 2.4.5 ListView簡單實用
- 2.4.6 BaseAdapter優化
- 2.4.7ListView的焦點問題
- 2.4.8 ListView之checkbox錯位問題解決
- 2.4.9 ListView的數據更新問題
- 2.5.0 構建一個可復用的自定義BaseAdapter
- 2.5.1 ListView Item多布局的實現
- 2.5.2 GridView(網格視圖)的基本使用
- 2.5.3 Spinner(列表選項框)的基本使用
- 2.5.4 AutoCompleteTextView(自動完成文本框)的基本使用
- 2.5.5 ExpandableListView(可折疊列表)的基本使用
- 2.5.6 ViewFlipper(翻轉視圖)的基本使用
- 2.5.7 Toast(吐司)的基本使用
- 2.5.8 Notification(狀態欄通知)詳解
- 2.5.9 AlertDialog(對話框)詳解
- 2.6.0 其他幾種常用對話框基本使用
- 2.6.1 PopupWindow(懸浮框)的基本使用
- 2.6.2 菜單(Menu)
- 2.6.3 ViewPager的簡單使用
- 2.6.4 DrawerLayout(官方側滑菜單)的簡單使用
- 3.1.1 基于監聽的事件處理機制
- 3.2 基于回調的事件處理機制
- 3.3 Handler消息傳遞機制淺析
- 3.4 TouchListener PK OnTouchEvent + 多點觸碰
- 3.5 監聽EditText的內容變化
- 3.6 響應系統設置的事件(Configuration類)
- 3.7 AnsyncTask異步任務
- 3.8 Gestures(手勢)
- 4.1.1 Activity初學乍練
- 4.1.2 Activity初窺門徑
- 4.1.3 Activity登堂入室
- 4.2.1 Service初涉
- 4.2.2 Service進階
- 4.2.3 Service精通
- 4.3.1 BroadcastReceiver牛刀小試
- 4.3.2 BroadcastReceiver庖丁解牛
- 4.4.2 ContentProvider再探——Document Provider
- 4.5.1 Intent的基本使用
- 4.5.2 Intent之復雜數據的傳遞
- 5.1 Fragment基本概述
- 5.2.1 Fragment實例精講——底部導航欄的實現(方法1)
- 5.2.2 Fragment實例精講——底部導航欄的實現(方法2)
- 5.2.3 Fragment實例精講——底部導航欄的實現(方法3)
- 5.2.4 Fragment實例精講——底部導航欄+ViewPager滑動切換頁面
- 5.2.5 Fragment實例精講——新聞(購物)類App列表Fragment的簡單實現
- 6.1 數據存儲與訪問之——文件存儲讀寫
- 6.2 數據存儲與訪問之——SharedPreferences保存用戶偏好參數
- 6.3.1 數據存儲與訪問之——初見SQLite數據庫
- 6.3.2 數據存儲與訪問之——又見SQLite數據庫
- 7.1.1 Android網絡編程要學的東西與Http協議學習
- 7.1.2 Android Http請求頭與響應頭的學習
- 7.1.3 Android HTTP請求方式:HttpURLConnection
- 7.1.4 Android HTTP請求方式:HttpClient
- 7.2.1 Android XML數據解析
- 7.2.2 Android JSON數據解析
- 7.3.1 Android 文件上傳
- 7.3.2 Android 文件下載(1)
- 7.3.3 Android 文件下載(2)
- 7.4 Android 調用 WebService
- 7.5.1 WebView(網頁視圖)基本用法
- 7.5.2 WebView和JavaScrip交互基礎
- 7.5.3 Android 4.4后WebView的一些注意事項
- 7.5.4 WebView文件下載
- 7.5.5 WebView緩存問題
- 7.5.6 WebView處理網頁返回的錯誤碼信息
- 7.6.1 Socket學習網絡基礎準備
- 7.6.2 基于TCP協議的Socket通信(1)
- 7.6.3 基于TCP協議的Socket通信(2)
- 7.6.4 基于UDP協議的Socket通信
- 8.1.1 Android中的13種Drawable小結 Part 1
- 8.1.2 Android中的13種Drawable小結 Part 2
- 8.1.3 Android中的13種Drawable小結 Part 3
- 8.2.1 Bitmap(位圖)全解析 Part 1
- 8.2.2 Bitmap引起的OOM問題
- 8.3.1 三個繪圖工具類詳解
- 8.3.2 繪圖類實戰示例
- 8.3.3 Paint API之—— MaskFilter(面具)
- 8.3.4 Paint API之—— Xfermode與PorterDuff詳解(一)
- 8.3.5 Paint API之—— Xfermode與PorterDuff詳解(二)
- 8.3.6 Paint API之—— Xfermode與PorterDuff詳解(三)
- 8.3.7 Paint API之—— Xfermode與PorterDuff詳解(四)
- 8.3.8 Paint API之—— Xfermode與PorterDuff詳解(五)
- 8.3.9 Paint API之—— ColorFilter(顏色過濾器)(1/3)
- 8.3.10 Paint API之—— ColorFilter(顏色過濾器)(2-3)
- 8.3.11 Paint API之—— ColorFilter(顏色過濾器)(3-3)
- 8.3.12 Paint API之—— PathEffect(路徑效果)
- 8.3.13 Paint API之—— Shader(圖像渲染)
- 8.3.14 Paint幾個枚舉/常量值以及ShadowLayer陰影效果
- 8.3.15 Paint API之——Typeface(字型)
- 8.3.16 Canvas API詳解(Part 1)
- 8.3.17 Canvas API詳解(Part 2)剪切方法合集
- 8.3.18 Canvas API詳解(Part 3)Matrix和drawBitmapMash
- 8.4.1 Android動畫合集之幀動畫
- 8.4.2 Android動畫合集之補間動畫
- 8.4.3 Android動畫合集之屬性動畫-初見
- 8.4.4 Android動畫合集之屬性動畫-又見
- 9.1 使用SoundPool播放音效(Duang~)
- 9.2 MediaPlayer播放音頻與視頻
- 9.3 使用Camera拍照
- 9.4 使用MediaRecord錄音
- 10.1 TelephonyManager(電話管理器)
- 10.2 SmsManager(短信管理器)
- 10.3 AudioManager(音頻管理器)
- 10.4 Vibrator(振動器)
- 10.5 AlarmManager(鬧鐘服務)
- 10.6 PowerManager(電源服務)
- 10.7 WindowManager(窗口管理服務)
- 10.8 LayoutInflater(布局服務)
- 10.9 WallpaperManager(壁紙管理器)
- 10.10 傳感器專題(1)——相關介紹
- 10.11 傳感器專題(2)——方向傳感器
- 10.12 傳感器專題(3)——加速度/陀螺儀傳感器
- 10.12 傳感器專題(4)——其他傳感器了解
- 10.14 Android GPS初涉
- 11.0《2015最新Android基礎入門教程》完結散花~