Android 進階16:IntentService 使用及源碼解析
版權聲明:轉載前請留言獲得作者許可,轉載後標明作者 張拭心 與 原文鏈接。大家都是成年人,創作不易,感謝您的支持!
- It’s time to start living the life you’ve only imagined.
讀完本文你將瞭解:
在前面兩篇文章 源碼解讀 Android 消息機制( Message MessageQueue Handler Looper) 和 HandlerThread 使用場景及源碼解析 中我們瞭解了 Android 中執行異步任務的兩種方式。
本篇文章介紹另外一種:IntentService。
IntentService 簡介
public abstract class IntentService extends Service {...}
- 1
- 1
IntentService 是一個抽象類,繼承了 Service
。
由於是一個 Service,IntentService 的優先級比較高,在後臺不會輕易被系統殺死;它可以接收 Intent 請求,然後在子線程中按順序執行。
官方文檔關於它的介紹:
IntentService 使用工作線程逐一處理所有啓動請求。如果你不需要在 Service 中執行併發任務,IntentService 是最好的選擇。
IntentService 源碼分析
IntentService 源碼很短:
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
//內部創建的 Handler
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//調用這個方法處理數據
onHandleIntent((Intent)msg.obj);
//處理完就自盡了
stopSelf(msg.arg1);
}
}
//子類需要重寫的構造函數,參數是服務的名稱
public IntentService(String name) {
super();
mName = name;
}
//設置當前服務被意外關閉後是否重新
//如果設置爲 true,onStartCommand() 方法將返回 Service.START_REDELIVER_INTENT,這樣當
//當前進程在 onHandleIntent() 方法返回前銷燬時,會重啓進程,重新使用之前的 Intent 啓動這個服務
//(如果有多個 Intent,只會使用最後的一個)
//如果設置爲 false,onStartCommand() 方法返回 Service.START_NOT_STICKY,當進程銷燬後也不重啓服務
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
super.onCreate();
//創建時啓動一個 HandlerThread
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//拿到 HandlerThread 中的 Looper,然後創建一個子線程中的 Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
//將 intent 和 startId 以消息的形式發送到 Handler
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit(); //值得學習的,在銷燬時退出 Looper
}
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
從上述代碼可以看到,IntentService 做了以下工作:
- 創建了一個
HandlerThread
默認的工作線程 - 使用
HandlerThread
的Looper
創建了一個Handler
,這個Handler
執行在子線程 - 在
onStartCommand()
中調用onStart()
,然後在onStart()
中將 intent 和 startId 以消息的形式發送到 Handler - 在
Handler
中將消息隊列中的Intent
按順序傳遞給onHandleIntent()
方法 - 在處理完所有啓動請求後自動停止服務,不需要我們調用
stopSelf()
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
有同學可能有疑問,在 handleMessage
方法中不是調用了一次 onHandleIntent()
後就調用
stopSelf()
了嗎,這不是隻能執行一個任務麼?
仔細看下可以發現,這個 stopSelf()
方法傳遞了一個 id,這個 id 是啓動服務時 IActivityManager
分配的 id,當我們調用
stopSelf(id)
方法結束服務時,IActivityManager
會對比當前 id 是否爲最新啓動該服務的 id,如果是就關閉服務。
public final void stopSelf(int startId) {
if (mActivityManager == null) {
return;
}
try {
mActivityManager.stopServiceToken(
new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
因此只有當最後一次啓動 IntentService 的任務執行完畢纔會關閉這個服務。
此外還要注意的是,IntentService 中除了
onHandleIntent
方法其他都是運行在主線程的。
IntentService 的使用
通過前面的源碼分析,我們可以看到,最終每個任務的處理都會調用 onHandleIntent()
,因此使用 IntentService
也很簡單,只需實現
onHandleIntent()
方法,在這裏執行對應的後臺工作即可。
舉個例子:
我們寫一個使用 IntentService 實現在子線程下載多張 美女圖片 的效果。
創建 IntentService 的子類
/**
* Description:
* <br> 使用 IntentService 實現下載
* <p>
* <br> Created by shixinzhang on 17/6/8.
* <p>
* <br> Email: [email protected]
* <p>
* <a href="https://about.me/shixinzhang">About me</a>
*/
public class DownloadService extends IntentService {
private static final String TAG = "DownloadService";
public static final String DOWNLOAD_URL = "down_load_url";
public static final int WHAT_DOWNLOAD_FINISHED = 1;
public static final int WHAT_DOWNLOAD_STARTED = 2;
public DownloadService() {
super(TAG);
}
private static Handler mUIHandler;
public static void setUIHandler(final Handler UIHandler) {
mUIHandler = UIHandler;
}
/**
* 這個方法運行在子線程
*
* @param intent
*/
@Override
protected void onHandleIntent(final Intent intent) {
String url = intent.getStringExtra(DOWNLOAD_URL);
if (!TextUtils.isEmpty(url)) {
sendMessageToMainThread(WHAT_DOWNLOAD_STARTED, "\n " + DateUtils.getCurrentTime() + " 開始下載任務:\n" + url);
try {
Bitmap bitmap = downloadUrlToBitmap(url);
SystemClock.sleep(1000); //延遲一秒發送消息
sendMessageToMainThread(WHAT_DOWNLOAD_FINISHED, bitmap);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 發送消息到主線程
*
* @param id
* @param o
*/
private void sendMessageToMainThread(final int id, final Object o) {
if (mUIHandler != null) {
mUIHandler.sendMessage(mUIHandler.obtainMessage(id, o));
}
}
/**
* 下載圖片
*
* @param url
* @return
* @throws Exception
*/
private Bitmap downloadUrlToBitmap(String url) throws Exception {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
Bitmap bitmap = BitmapFactory.decodeStream(in);
urlConnection.disconnect();
in.close();
return bitmap;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
在上面的代碼中,我們做了以下幾件事:
- 在
onHandleIntent()
中接收任務,開始下載,同時將狀態返回給主線程 - 下載完成後將得到的 Bitmap 通過 Handler 發送到主線程
爲了界面上有明顯效果,設置了一定延時。
IntentService 也是 Service,別忘了在 AndroidManifest 中註冊!
佈局界面
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:id="@+id/iv_display"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:id="@+id/tv_status"
android:layout_width="match_parent"
android:layout_height="250dp"
android:padding="8dp"
android:text="狀態信息:"/>
<Button
android:id="@+id/btn_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="開始下載"/>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
界面上有一個開始下載按鈕,一個顯示下載狀態的 TextView,一個展示圖片的 ImageView.
調用方代碼
/**
* Description:
* <br> IntentService 實例
* <p>
* <br> Created by shixinzhang on 17/6/9.
* <p>
* <br> Email: [email protected]
* <p>
* <a href="https://about.me/shixinzhang">About me</a>
*/
public class IntentServiceActivity extends AppCompatActivity implements Handler.Callback {
@BindView(R.id.iv_display)
ImageView mIvDisplay;
@BindView(R.id.btn_download)
Button mBtnDownload;
@BindView(R.id.tv_status)
TextView mTvStatus;
private List<String> urlList = Arrays.asList("https://ws1.sinaimg.cn/large/610dc034ly1fgepc1lpvfj20u011i0wv.jpg",
"https://ws1.sinaimg.cn/large/d23c7564ly1fg6qckyqxkj20u00zmaf1.jpg",
"https://ws1.sinaimg.cn/large/610dc034ly1fgchgnfn7dj20u00uvgnj.jpg"); //美女圖片地址
int mFinishCount; //完成的任務個數
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_intent_service);
ButterKnife.bind(this);
DownloadService.setUIHandler(new Handler(this));
}
@OnClick(R.id.btn_download)
public void downloadImage() {
Intent intent = new Intent(this, DownloadService.class);
for (String url : urlList) {
intent.putExtra(DownloadService.DOWNLOAD_URL, url);
startService(intent);
}
mBtnDownload.setEnabled(false);
}
@Override
public boolean handleMessage(final Message msg) {
if (msg != null) {
switch (msg.what) {
case DownloadService.WHAT_DOWNLOAD_FINISHED:
mIvDisplay.setImageBitmap((Bitmap) msg.obj);
mBtnDownload.setText("完成 " + (++mFinishCount) + "個任務");
break;
case DownloadService.WHAT_DOWNLOAD_STARTED:
mTvStatus.setText(mTvStatus.getText() + (String) msg.obj);
break;
}
}
return true;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
Activity 中做了以下幾件事:
- 設置 UI 線程的 Handler 給 IntentService
- 使用
startService(intent)
啓動 IntentService 執行圖片下載任務 - 在 Handler 的
handleMessage
中根據消息類型進行相應處理
可以看到,調用方的代碼和上一篇使用 HandlerThread 的方法很相似。
運行效果
總結
本篇文章介紹了 IntentService 的使用和源碼。
在第一次啓動 IntentService 後,IntentService 仍然可以接受新的請求,接受到的新的請求被放入了工作隊列中,等待被串行執行。
使用 IntentService 顯著簡化了啓動服務的實現,如果您決定還重寫其他回調方法(如 onCreate()、onStartCommand() 或 onDestroy()),請確保調用超類實現,以便 IntentService 能夠妥善處理工作線程的生命週期。
由於大多數啓動服務都不必同時處理多個請求(實際上,這種多線程情況可能很危險),因此使用 IntentService 類實現服務也許是最好的選擇。
一句話總結 IntentService:
- 優先級比較高的、用於串行執行異步任務、會自盡的 Service。
代碼地址
Thanks
《android 開發藝術探索》
https://developer.android.com/guide/components/services.html#ExtendingIntentService
http://rainbow702.iteye.com/blog/1143286
http://blog.csdn.net/javazejian/article/details/52426425