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源码。