Action+ IntentService架構
這一部分給大家總結一下mms裏面對於後臺任務的處理。正常情況下,一個互聯網應用可能會涉及到n多的後臺任務要運行,短信應用也不例外,例如插入短信到數據庫,刪除短信,標記爲已讀,發送短信,接收短信,下載彩信等。這些都是耗時任務,並且他們之間有些還有先後順序要求,如果沒有一個良好的後臺任務管理框架,拿維護起來可就要了親命了。
這裏我們就來看一下mms應用中的“Action+IntentService架構”是怎麼解決這個問題的。
先簡單看一下Action:
public abstract class Action implements Parcelable {
..........................................
public void start() {
DataModel.startActionService(this);
}
//該Action要執行的具體後臺任務放在這裏,該方法需要我們繼承的
//Action子類實現
protected Object executeAction() {
return null;
}
..........................................
//需要在該Action之後執行的Action放到mBackgroundActions中,在該Action執行完
//之後會將這些mBackgroundActions取出來執行
private final List<Action> mBackgroundActions = new LinkedList<Action>();
protected void requestBackgroundWork(final Action backgroundAction) {
LogUtil.i(TAG, "requestBackgroundWork");
mBackgroundActions.add(backgroundAction);
}
protected Bundle doBackgroundWork() throws DataModelException {
return null;
}
..........................................
}
我們大致上就可以瞭解Action是什麼了?簡單的來說,可以理解成一個Runnable,在executeAction方法中是具體的後臺任務邏輯,類似於run方法。但顯然Action比Runnable複雜一些,多維護了一個mBackgroundActions列表,他是一個List<Action>,維護的是需要在該Action執行之後再執行的Action,當然這些都是由Action對象本身維護和觸發,是典型的面向對象的設計。這樣的設計無疑給繁雜的後臺任務維護指明瞭一條簡單清晰的框架:誰關聯誰維護。這樣就不需要我們整體上再維護Action隊列來決定先後順序了,是一個非常不錯的設計。
我們再來以短信發送過程爲例來說明:
這個過程涉及到兩個Action,InsertNewMessageAction和SendMessageAction,分別對應的是將短信插入數據庫和真正發送的過程,都是耗時任務,所以都需要Action來處理。在這裏我們就看到SendMessageAction需要在InsertNewMessageAction之後執行,所以被放在了mBackgroundActions中了。
而實際上,我統計了一下,整個mms應用中有大概30個Action,包括DeleteConversationAction、DeleteMessageAction、
DownloadMmsAction、DumpDatabaseAction、HandleLowStorageAction、MarkAsReadAction、ReceiveSmsMessageAction、UpdateMessagePartSizeAction等。這些紛繁的後臺任務因爲有了Action架構,就顯得不那麼亂了。
再來看一下IntentService.
Action本身只是一個後臺任務邏輯載體,並沒有維護線程來執行任務,所以真正的任務執行是在IntentService裏面。有經驗的Android工程師應該都知道IntentService,因爲Service本身是運行在UI Thread中的,所以一般我們執行後臺任務都要再單獨開線程,而Android框架爲了方便,直接又封裝了一個帶Thread的Service,即IntentService。並且IntentService是即用即銷燬,所以我們連生命週期都不要維護,只需要實現它的onHandleIntent方法執行自己的後臺邏輯即可。
我們來看一下在mms源碼中對IntentService的使用:
public class ActionServiceImpl extends IntentService {
/**
*提供一個靜態全局方法,方便外界直接對自己實例化
*/
private static void startServiceWithIntent(final Intent intent) {
final Context context = Factory.get().getApplicationContext();
final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
// Increase refCount on wake lock - acquiring if necessary
if (VERBOSE) {
LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode);
}
sWakeLock.acquire(context, intent, opcode);
intent.setClass(context, ActionServiceImpl.class);
// TODO: Note that intent will be quietly discarded if it exceeds available rpc
// memory (in total around 1MB). See this article for background
// http://developer.android.com/reference/android/os/TransactionTooLargeException.html
// Perhaps we should keep large structures in the action monitor?
if (context.startService(intent) == null) {
LogUtil.e(TAG,
"ActionService.startServiceWithIntent: failed to start service for intent "
+ intent);
sWakeLock.release(intent, opcode);
}
}
protected void onHandleIntent(final Intent intent) {
if (intent == null) {
// Shouldn't happen but sometimes does following another crash.
LogUtil.w(TAG, "ActionService.onHandleIntent: Called with null intent");
return;
}
final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
sWakeLock.ensure(intent, opcode);
try {
Action action;
final Bundle actionBundle = intent.getBundleExtra(EXTRA_ACTION_BUNDLE);
actionBundle.setClassLoader(getClassLoader());
switch(opcode) {
case OP_START_ACTION: {
action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
//真正執行Action
executeAction(action);
break;
}
case OP_RECEIVE_BACKGROUND_RESPONSE: {
action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
final Bundle response = intent.getBundleExtra(EXTRA_WORKER_RESPONSE);
processBackgroundResponse(action, response);
break;
}
case OP_RECEIVE_BACKGROUND_FAILURE: {
action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
processBackgroundFailure(action);
break;
}
default:
throw new RuntimeException("Unrecognized opcode in ActionServiceImpl");
}
//執行Action的BackgroundAction
action.sendBackgroundActions(mBackgroundWorker);
} finally {
// Decrease refCount on wake lock - releasing if necessary
sWakeLock.release(intent, opcode);
}
}
}
我們可以看到,這裏封裝了一個ActionServiceImpl類用於執行Action,同時在最後會觸發Action的backgroundAction也去執行。這裏注意一下,backgroundAction是在另一個IntentService即BackgroundWorkerService中執行。不過這裏BackgroundWorkerService和ActionServiceImpl除了代碼結構規整之外,設計BackgroundWorkerService應該還有一些用處,因爲在DeleteMessageAction中有以下注釋:
// Doing this work in the background so that we're not competing with sync
// which could bring the deleted message back to life between the time we deleted
// it locally and deleted it in telephony (sync is also done on doBackgroundWork).
//
// Previously this block of code deleted from telephony first but that can be very
// slow (on the order of seconds) so this was modified to first delete locally, trigger
// the UI update, then delete from telephony.
@Override
protected Bundle doBackgroundWork() {
}
大致意思是將刪除短信的Action放在BackgroundWorkerService中執行,以避開與SyncMessagesAction競爭。但我仔細研究了一下代碼也沒有發現它怎麼避開競爭,這一塊還要再研究一下。
另外,ActionMonitor也值得一說
public class ActionMonitor {
protected int mState;
static void setState(final Action action, final int expectedOldState,
final int newState) {
int oldMonitorState = expectedOldState;
int newMonitorState = newState;
final ActionMonitor monitor
= ActionMonitor.lookupActionMonitor(action.actionKey);
if (monitor != null) {
oldMonitorState = monitor.mState;
monitor.updateState(action, expectedOldState, newState);
newMonitorState = monitor.mState;
}
.............................................
}
public interface ActionCompletedListener {
abstract void onActionSucceeded(ActionMonitor monitor,
final Action action, final Object data, final Object result);
abstract void onActionFailed(ActionMonitor monitor, final Action action,
final Object data, final Object result);
}
}
每一個Action對應一個ActionMonitor,用於維護Action所處於的狀態,這樣就可以在每個狀態與外界交互,比如在Action執行完之後回調onActionSucceeded方法。
當然,這裏都是抽絲剝繭的說了一下整個架構,而具體的細節其實還有很多,包括各種執行失敗的處理交互,各種任務的前後關聯,這裏面內容還是比較多的,但限於篇幅這裏就不展開了,感興趣的同學可以直接查看mms源碼。