将一些认识写下来,和大家交流一下,同时也方便自己复习。

用户可以通过附件按钮,添加附件。以添加幻灯片为例:

如果点击幻灯片,会走如下代码:

ComposeMessageActivity.java
private void editSlideshow() {
// The user wants to edit the slideshow. That requires us to persist the slideshow to
// disk as a PDU in saveAsMms. This code below does that persisting in a background
// task. If the task takes longer than a half second, a progress dialog is displayed.
// Once the PDU persisting is done, another runnable on the UI thread get executed to start
// the SlideshowEditActivity.
getAsyncDialog().runAsync(new Runnable() {
@Override
public void run() {
// This runnable gets run in a background thread.
mTempMmsUri = mWorkingMessage.saveAsMms(false);
}
}, new Runnable() {
@Override
public void run() {
// Once the above background thread is complete, this runnable is run
// on the UI thread.
if (mTempMmsUri == null) {
return;
}
Intent intent = new Intent(ComposeMessageActivity.this,
SlideshowEditActivity.class);
intent.setData(mTempMmsUri);
startActivityForResult(intent, REQUEST_CODE_CREATE_SLIDESHOW);
}
}, R.string.building_slideshow_title);
}

这段代码比较简单,总的来说就是先构建一个Uri,用于表示附件的唯一Id,然后这个Uri被传到附件幻灯片编辑页面,也就是SlideshowEditActivity.java。重点就是在构建Uri的过程中,做了什么事情。(关于getAsyncDialog(),并不影响我们分析,读者可跳过。其实这段代码是异步执行的,也就是说代码在主线程中调用了一下,主线程就返回了,也就是常说的不阻塞主线程。这段代码归根结底是调用了AsycnTask,只不过Mms应用封装了它并对外提供了异步线程接口。如果第一个Runnable在0.5秒之后没有执行完毕,那么会弹出一个progressBar,提示信息就是第三个参数,如果执行完毕,就执行第二个Runnable)。下面我们来看一下构建Uri中的重点。

WorkingMessage.java
public Uri saveAsMms(boolean notify) {
if (DEBUG) LogTag.debug("saveAsMms mConversation=%s", mConversation); // If we have discarded the message, just bail out.
if (mDiscarded) {
LogTag.warn("saveAsMms mDiscarded: true mConversation: " + mConversation +
" returning NULL uri and bailing");
return null;
} // FORCE_MMS behaves as sort of an "invisible attachment", making
// the message seem non-empty (and thus not discarded). This bit
// is sticky until the last other MMS bit is removed, at which
// point the message will fall back to SMS.
updateState(FORCE_MMS, true, notify); // Collect our state to be written to disk.
prepareForSave(true /* notify */); try {
// Make sure we are saving to the correct thread ID.
DraftCache.getInstance().setSavingDraft(true);
if (!mConversation.getRecipients().isEmpty()) {
mConversation.ensureThreadId();
}
mConversation.setDraftState(true); PduPersister persister = PduPersister.getPduPersister(mActivity);
SendReq sendReq = makeSendReq(mConversation, mSubject); // If we don't already have a Uri lying around, make a new one. If we do
// have one already, make sure it is synced to disk.
if (mMessageUri == null) {
mMessageUri = createDraftMmsMessage(persister, sendReq, mSlideshow, null);
} else {
updateDraftMmsMessage(mMessageUri, persister, mSlideshow, sendReq);
}
mHasMmsDraft = true;
} finally {
DraftCache.getInstance().setSavingDraft(false);
}
return mMessageUri;
}

这段代码会在多种情况下调用,故而不好分析它的具体作用。代码中先判断mDiscarded是否为true,这个值在我们删除会话,舍弃信息,退出Activity等情况下会为true,我们添加幻灯片附件,因而此处为false。

我们首先来看看updateState(FORCE_MMS,true, notify);

private void updateState(int state, boolean on, boolean notify) {
if (!sMmsEnabled) {
// If Mms isn't enabled, the rest of the Messaging UI should not be using any
// feature that would cause us to to turn on any Mms flag and show the
// "Converting to multimedia..." message.
return;
}
int oldState = mMmsState;
if (on) {
mMmsState |= state;
} else {
mMmsState &= ~state;
} // If we are clearing the last bit that is not FORCE_MMS,
// expire the FORCE_MMS bit.
if (mMmsState == FORCE_MMS && ((oldState & ~FORCE_MMS) > 0)) {
mMmsState = 0;
} // Notify the listener if we are moving from SMS to MMS
// or vice versa.
if (notify) {
if (oldState == 0 && mMmsState != 0) {
mStatusListener.onProtocolChanged(true);
} else if (oldState != 0 && mMmsState == 0) {
mStatusListener.onProtocolChanged(false);
}
} if (oldState != mMmsState) {
if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) LogTag.debug("updateState: %s%s = %s",
on ? "+" : "-",
stateString(state), stateString(mMmsState));
}
}

一开始mMmsState为0,然后更新mMmsState的值为彩信状态或者恢复为短信状态,也就是说此信息是否带有彩信标记。关于彩信的标记,给出如下:

private static final int RECIPIENTS_REQUIRE_MMS = (1 << 0);     // 1
private static final int HAS_SUBJECT = (1 << 1); // 2
private static final int HAS_ATTACHMENT = (1 << 2); // 4
private static final int LENGTH_REQUIRES_MMS = (1 << 3); // 8
private static final int FORCE_MMS = (1 << 4); // 16

如果带有以上标记就认为说信息是彩信,否则是短息。

是否通知用户,也就是我们看到的界面Toast提示:正在由短信转位彩信,或者正在由彩信专为短信的提示。

接下来回到saveAsMms函数,继续分析:

private void prepareForSave(boolean notify) {
// Make sure our working set of recipients is resolved
// to first-class Contact objects before we save.
syncWorkingRecipients(); if (requiresMms()) {
ensureSlideshow();
syncTextToSlideshow();
}
}

syncWorkingRecipients()的作用是同步会话联系人,将当前的联系人同步到当前会话对象mConversation中。

ensureSlideshow()比较重要

private void ensureSlideshow() {
if (mSlideshow != null) {
return;
} SlideshowModel slideshow = SlideshowModel.createNew(mActivity);
SlideModel slide = new SlideModel(slideshow);
slideshow.add(slide); mSlideshow = slideshow;
}

它首先创建了一个SlideshowModel,SlideshowModel是幻灯片的抽象类,它实现了List接口,即你可以把它当成一个集合,对外提供了增加幻灯片、删除幻灯片,以及构建PduBody等方法操作。SlideshowModel继承自Model,其中涉及到一个观察者模式,SlideshowModel在其中的作用既是观察者又是主题。我们后面会介绍。

这段代码不仅创建了一个幻灯片集合SlideshowModel,以及一页幻灯片SlideModel。通过add方法,幻灯页加入到了幻灯片集合中。在add的同时就发生了观察着模式的注册绑定事件(SlideModel为主题,SlideshowModel成为了SlideModel的观察者,当SlideModel出现变化的时候,SlideshowModel会收到并执行OnModelChangedObserver)。此处就不粘贴代码详细叙述了,读者可以仔细看一下add方法的实现。

syncTextToSlideshow();每页幻灯片至少存在一个TextModel,确认是否存在,如果不存在,构建一个并进入slideModel幻灯页中。在add的过程中,同样发生了观察者模式的注册事件,TextModel作为主题,SlideshowModel作为观察者。

关于这段代码:SendReqsendReq = makeSendReq(mConversation, mSubject);

如下所示:

private static SendReq makeSendReq(Conversation conv, CharSequence subject) {
String[] dests = conv.getRecipients().getNumbers(true /* scrub for MMS address */); SendReq req = new SendReq();
EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(dests);
if (encodedNumbers != null) {
req.setTo(encodedNumbers);
} if (!TextUtils.isEmpty(subject)) {
req.setSubject(new EncodedStringValue(subject.toString()));
} req.setDate(System.currentTimeMillis() / 1000L); return req;
}

SendReq是framework中的一个类对象,作为Pdu框架的一部分,其中主要封装了PduHeaders,彩信pdu中的信息内容就封装在PduHeaders中。此处仅将彩信的联系人以及主题、日期加入到此PDU对象中。

mMessageUri= createDraftMmsMessage(persister, sendReq, mSlideshow, null);

updateDraftMmsMessage(mMessageUri,persister, mSlideshow, sendReq);

这两句是Uri的关键,如果Uri存在就更新这个Uri所在的数据库,如果不存在,就创建一个。

private static Uri createDraftMmsMessage(PduPersister persister, SendReq sendReq,
SlideshowModel slideshow, Uri preUri) {
if (slideshow == null) {
return null;
}
try {
PduBody pb = slideshow.toPduBody();
sendReq.setBody(pb);
Uri res = persister.persist(sendReq, preUri == null ? Mms.Draft.CONTENT_URI : preUri);
slideshow.sync(pb);
return res;
} catch (MmsException e) {
return null;
}
}

创建PudBody,这也是framework中的一个类,主要封装附件的内容,每一页幻灯片作为一个part,part中包含了多媒体的一些信息,多媒体也就是我们我幻灯片中加入的视频、图片等。调用persister.persist方法,将一些信息内容加入到数据库pdu表中,并返回我们需要的Uri。Pdu由很多类别,此处的创建的pdu类别指的就是sendReq,关于sendReq的继承关系后面会介绍。

slideshow.sync(pb);多媒体Model同步pb中的pduPart的uri。关于这一块我也不是知特别清楚。大体作用是把属于整个幻灯片的Uri,同步到PduBody中。

得到了Uri,我们会从当前界面跳转到SlideshowEditActivity中。

这个界面比较简单,添加操作幻灯片依靠的是SlideshowEditor。此类中的重点是如何将幻灯片的数据绑定在ListView上,你可能会说依靠的是类中的Adapter,其实不仅仅是Adapter,还有一个比较重要的类,它就是presenter的子类。让我们来认识一下它:

Presenter presenter = PresenterFactory.getPresenter(
"SlideshowPresenter", mContext, slideListItemView, mSlideshow);
((SlideshowPresenter) presenter).setLocation(position);
presenter.present(null);

PresenterFactory在这里算是一个简单工厂模式吧,在它内部用到了java的反射机制,通过类名,加载并new对象。在这里我们得到了一个SlideshowPresenter类对象,并且我们的Adapter的View,也就是slideListItemView作为参数传了进去,一并传过去的还有mSlideshow。

我们看一下SlideshowPresenter这个类的构造:

public SlideshowPresenter(Context context, ViewInterface view, Model model) {
super(context, view, model);
mLocation = 0;
mSlideNumber = ((SlideshowModel) mModel).size(); if (view instanceof AdaptableSlideViewInterface) {
((AdaptableSlideViewInterface) view).setOnSizeChangedListener(
mViewSizeChangedListener);
}
}

如此简单。真的如此简单吗?我们很容易忽略一个重点,它就是super(context,view,
model);中所做的操作。

如果想搞明白它的观察者模式框架的话,super(context,view,
model);这行代码非常重要,它做了什么?

public Presenter(Context context, ViewInterface view, Model model) {
mContext = context;
mView = view; mModel = model;
mModel.registerModelChangedObserver(this);
}

在父类的构造中,调用了mModel.registerModelChangedObserver(this),注册了观察者,Model是一个父类,此处的registerModelChangedObserver方法,会调用registerModelChangedObserverInDescendants,这个方法在子类中实现,即在Model类中注册的观察者的方法是深度注册,也就是说主题注册的观察者的行为会使主题的集合元素们一并发生注册观察者行为,这句话比较绕。让我们看看在SlideshowModel中registerModelChangedObserverInDescendants的实现。

protected void registerModelChangedObserverInDescendants(
IModelChangedObserver observer) {
mLayout.registerModelChangedObserver(observer); for (SlideModel slide : mSlides) {
slide.registerModelChangedObserver(observer);
}
}
Slide调用registerModelChangedObserver,也会触发registerModelChangedObserverInDescendants,它其中的实现为:
SlideModel.java
protected void registerModelChangedObserverInDescendants(
IModelChangedObserver observer) {
for (MediaModel media : mMedia) {
media.registerModelChangedObserver(observer);
}
}

我们看到,不仅仅是SlideshowModel中的集合元素slide注册了观察者,连mLayout也注册了同一个观察者。(主题是SlideshowPresenter,观察者为SildeshowModel以及SlideshowM中所有的幻灯页SlideModel,以及幻灯页中的所有子对象、以及LayoutModel)。

在model中给出了registerModelChangedObserverInDescendants,让子类重写此方法,来确定注册时行为,设计灵活、巧妙。

总结:SlideshowEditActivity中,会再SlideshowPresenter的帮助下,完成幻灯片的显示。

点击每一个幻灯片会进入幻灯片的详细编辑页面,即SlideEditorActivity.java。

在SlideEditorActivity类中的onCreate方法中,通过mSlideshowModel=
SlideshowModel.createFromMessageUri(this, mUri);得到SlideshowModel。

我们看看它是如何得到的:

SlideshowModel类:
publicstaticSlideshowModel createFromMessageUri(
Contextcontext, Uri uri) throwsMmsException {
returncreateFromPduBody(context,getPduBody(context,uri));
} publicstaticPduBody getPduBody(Context context, Uri msg) throwsMmsException {
PduPersisterp = PduPersister.getPduPersister(context);
GenericPdupdu = p.load(msg); intmsgType = pdu.getMessageType();
if((msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)
||(msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)) {
return((MultimediaMessagePdu) pdu).getBody();
}else{
thrownewMmsException();
}
}

根据Uri从数据库中加载数据,构建Pdu,然后得到PduBody。

public static SlideshowModel createFromPduBody(Context context, PduBody pb) throws MmsException {
SMILDocument document = SmilHelper.getDocument(pb); // Create root-layout model.
SMILLayoutElement sle = document.getLayout();
SMILRootLayoutElement srle = sle.getRootLayout();
int w = srle.getWidth();
int h = srle.getHeight();
if ((w == 0) || (h == 0)) {
w = LayoutManager.getInstance().getLayoutParameters().getWidth();
h = LayoutManager.getInstance().getLayoutParameters().getHeight();
srle.setWidth(w);
srle.setHeight(h);
}
RegionModel rootLayout = new RegionModel(
null, 0, 0, w, h); // Create region models.
ArrayList<RegionModel> regions = new ArrayList<RegionModel>();
NodeList nlRegions = sle.getRegions();
int regionsNum = nlRegions.getLength(); for (int i = 0; i < regionsNum; i++) {
SMILRegionElement sre = (SMILRegionElement) nlRegions.item(i);
RegionModel r = new RegionModel(sre.getId(), sre.getFit(),
sre.getLeft(), sre.getTop(), sre.getWidth(), sre.getHeight(),
sre.getBackgroundColor());
regions.add(r);
}
LayoutModel layouts = new LayoutModel(rootLayout, regions); // Create slide models.
SMILElement docBody = document.getBody();
NodeList slideNodes = docBody.getChildNodes();
int slidesNum = slideNodes.getLength();
ArrayList<SlideModel> slides = new ArrayList<SlideModel>(slidesNum);
int totalMessageSize = 0; for (int i = 0; i < slidesNum; i++) {
// FIXME: This is NOT compatible with the SMILDocument which is
// generated by some other mobile phones.
SMILParElement par = (SMILParElement) slideNodes.item(i); // Create media models for each slide.
NodeList mediaNodes = par.getChildNodes();
int mediaNum = mediaNodes.getLength();
ArrayList<MediaModel> mediaSet = new ArrayList<MediaModel>(mediaNum); for (int j = 0; j < mediaNum; j++) {
SMILMediaElement sme = (SMILMediaElement) mediaNodes.item(j);
try {
MediaModel media = MediaModelFactory.getMediaModel(
context, sme, layouts, pb); /*
* This is for slide duration value set.
* If mms server does not support slide duration.
*/
if (!MmsConfig.getSlideDurationEnabled()) {
int mediadur = media.getDuration();
float dur = par.getDur();
if (dur == 0) {
mediadur = MmsConfig.getMinimumSlideElementDuration() * 1000;
media.setDuration(mediadur);
} if ((int)mediadur / 1000 != dur) {
String tag = sme.getTagName(); if (ContentType.isVideoType(media.mContentType)
|| tag.equals(SmilHelper.ELEMENT_TAG_VIDEO)
|| ContentType.isAudioType(media.mContentType)
|| tag.equals(SmilHelper.ELEMENT_TAG_AUDIO)) {
/*
* add 1 sec to release and close audio/video
* for guaranteeing the audio/video playing.
* because the mmsc does not support the slide duration.
*/
par.setDur((float)mediadur / 1000 + 1);
} else {
/*
* If a slide has an image and an audio/video element
* and the audio/video element has longer duration than the image,
* The Image disappear before the slide play done. so have to match
* an image duration to the slide duration.
*/
if ((int)mediadur / 1000 < dur) {
media.setDuration((int)dur * 1000);
} else {
if ((int)dur != 0) {
media.setDuration((int)dur * 1000);
} else {
par.setDur((float)mediadur / 1000);
}
}
}
}
}
SmilHelper.addMediaElementEventListeners(
(EventTarget) sme, media);
mediaSet.add(media);
totalMessageSize += media.getMediaSize();
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
} catch (IllegalArgumentException e) {
Log.e(TAG, e.getMessage(), e);
}
} SlideModel slide = new SlideModel((int) (par.getDur() * 1000), mediaSet);
slide.setFill(par.getFill());
SmilHelper.addParElementEventListeners((EventTarget) par, slide);
slides.add(slide);
} SlideshowModel slideshow = new SlideshowModel(layouts, slides, document, pb, context);
slideshow.mTotalMessageSize = totalMessageSize;
slideshow.registerModelChangedObserver(slideshow);
return slideshow;
}

根据PduBody中的信息得到SlidehsowModel,同时注册自己为观察者。

在SlideEditorActivity类中,SlideshowModel会注册另外一个观察者。

mSlideshowModel.registerModelChangedObserver(mModelChangedObserver);

同样的,数据的绑定也是构建了一个SlideViewPresenter。即代码如下所示:

mPresenter = (SlideshowPresenter) PresenterFactory.getPresenter(
"SlideshowPresenter", this, mSlideView, mSlideshowModel);

这个类没什么特别,相信你能够看懂。

我们经历了对幻灯片的编辑,对幻灯页的编辑,那么还差什么?预览界面,下面我们来分析一下预览界面。

SlideshowActivity.java

在这个界面同样的,依靠SlideViewPresenter绑定数据到视图上。难点在于Mms应用自身构建了一个彩信播放器。其中SmilPlayer,结合MediaController以及SMILDocument加上DOM解析得到事件,控制附件播放,目前这部分我也不是很清楚,期待大牛给出解析。

在整个播放机制中比较重要的代码如下所示:

SmilPlayer.java
public void run() {
if (isStoppedState()) {
return;
}
if (LOCAL_LOGV) {
dumpAllEntries();
}
// Play the Element by following the timeline
int size = mAllEntries.size();
for (mCurrentElement = 0; mCurrentElement < size; mCurrentElement++) {
TimelineEntry entry = mAllEntries.get(mCurrentElement);
if (isBeginOfSlide(entry)) {
mCurrentSlide = mCurrentElement;
}
long offset = (long) (entry.getOffsetTime() * 1000); // in ms.
while (offset > mCurrentTime) {
try {
waitForEntry(offset - mCurrentTime);
} catch (InterruptedException e) {
Log.e(TAG, "Unexpected InterruptedException.", e);
} while (isPauseAction() || isStopAction() || isReloadAction() || isNextAction() ||
isPrevAction()) {
if (isPauseAction()) {
actionPause();
waitForWakeUp();
} if (isStopAction()) {
actionStop();
return;
} if (isReloadAction()) {
actionReload();
entry = reloadCurrentEntry();
if (entry == null)
return;
if (isPausedState()) {
mAction = SmilPlayerAction.PAUSE;
}
} if (isNextAction()) {
TimelineEntry nextEntry = actionNext();
if (nextEntry != null) {
entry = nextEntry;
}
if (mState == SmilPlayerState.PAUSED) {
mAction = SmilPlayerAction.PAUSE;
actionEntry(entry);
} else {
mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
}
offset = mCurrentTime;
} if (isPrevAction()) {
TimelineEntry prevEntry = actionPrev();
if (prevEntry != null) {
entry = prevEntry;
}
if (mState == SmilPlayerState.PAUSED) {
mAction = SmilPlayerAction.PAUSE;
actionEntry(entry);
} else {
mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
}
offset = mCurrentTime;
}
}
}
mCurrentTime = offset;
actionEntry(entry);
} mState = SmilPlayerState.PLAYED;
}

依靠事件解析机制,从SimDocument得到播放控制事件,如果用户操作了MediaController,代码中的isPauseAction()||
isStopAction() || isReloadAction() || isNextAction() ||isPrevAction()等会对用户的操作进行处理。

在控制视图播放方面:我们的presenter也会做一些处理,比如对声音的处理,代码如下:

protected void presentAudio(SlideViewInterface view, AudioModel audio,
boolean dataChanged) {
// Set audio only when data changed.
if (dataChanged) {
view.setAudio(audio.getUri(), audio.getSrc(), audio.getExtras());
} MediaAction action = audio.getCurrentAction();
if (action == MediaAction.START) {
view.startAudio();
} else if (action == MediaAction.PAUSE) {
view.pauseAudio();
} else if (action == MediaAction.STOP) {
view.stopAudio();
} else if (action == MediaAction.SEEK) {
view.seekAudio(audio.getSeekTo());
}
}

在幻灯片编辑、幻灯页编辑这两个界面中,我们的MediaAction为NO_ACTIVE_ACTION,故而在这两个界面,不会播放。在我们的预览界面中,便会调用view.startAudio()进行播放事件,调用view.pauseAudio()进行暂停事件等。

如果我们编辑完成了对幻灯片的操作,可能有的读者注意到了,我们一开始调用的是startActivityForResult方法启动的Activity,那么如今返回,在OnActivityResults中会触发以下操作:

case REQUEST_CODE_CREATE_SLIDESHOW:
if (data != null) {
WorkingMessage newMessage = WorkingMessage.load(this, data.getData());
if (newMessage != null) {
mWorkingMessage = newMessage;
mWorkingMessage.setConversation(mConversation);
updateThreadIdIfRunning();
drawTopPanel(false);
updateSendButtonState();
invalidateOptionsMenu();
}
}
break;

通过WorkingMessage的load方法,返回一个新的WorkingMessage,赋给成员变量mWorkingMessage。

在load中作了什么,我们来看看:

public static WorkingMessage load(ComposeMessageActivity activity, Uri uri) {
// If the message is not already in the draft box, move it there.
if (!uri.toString().startsWith(Mms.Draft.CONTENT_URI.toString())) {
PduPersister persister = PduPersister.getPduPersister(activity);
if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
LogTag.debug("load: moving %s to drafts", uri);
}
try {
uri = persister.move(uri, Mms.Draft.CONTENT_URI);
} catch (MmsException e) {
LogTag.error("Can't move %s to drafts", uri);
return null;
}
} WorkingMessage msg = new WorkingMessage(activity);
if (msg.loadFromUri(uri)) {
msg.mHasMmsDraft = true;
return msg;
} return null;
}

调用WorkingMessage的loadFromUri方法

private boolean loadFromUri(Uri uri) {
if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) LogTag.debug("loadFromUri %s", uri);
try {
mSlideshow = SlideshowModel.createFromMessageUri(mActivity, uri);
} catch (MmsException e) {
LogTag.error("Couldn't load URI %s", uri);
return false;
} mMessageUri = uri; // Make sure all our state is as expected.
syncTextFromSlideshow();
correctAttachmentState(); return true;
}

得到新的SlideShowModel以及mMessageUri。

syncTextFromSlideshow();得到附件幻灯片中的第一页文本,赋值给WorkingMessage的mText

private void correctAttachmentState() {
int slideCount = mSlideshow.size(); // If we get an empty slideshow, tear down all MMS
// state and discard the unnecessary message Uri.
if (slideCount == 0) {
removeAttachment(false);
} else if (slideCount > 1) {
mAttachmentType = SLIDESHOW;
} else {
SlideModel slide = mSlideshow.get(0);
if (slide.hasImage()) {
mAttachmentType = IMAGE;
} else if (slide.hasVideo()) {
mAttachmentType = VIDEO;
} else if (slide.hasAudio()) {
mAttachmentType = AUDIO;
}
} updateState(HAS_ATTACHMENT, hasAttachment(), false);
}

在这个方法中,更新WorkingMessage的mAttachmentType的值,并且更新附件状态为HAS_ATTACHMENT。DrawTopPanel(false)这个方法,用户构建AttachmentEditior,在这个类中同样也用到了Presenter,不过是MmsThumbnailPresenter,相信根据前面的介绍,大家能够看懂这个类。AttachmentEditor显示在界面的最下方,我们可以通过查看幻灯片、编辑幻灯片、删除幻灯片,以及发送彩信。这个类在按钮事件设计上也挺巧妙,AttachemntEditor中维护了一个ComposeMessageActivity类中的Handler引用,在我们点击AttachementEditor类中的按钮时,它不会在它内部处理点击事件监听,而是通过handler发送一个信息,那么信息就发送到了ComposeMessageActivity类中了,实现了处理控件监听的另一种方式。

private static final int RECIPIENTS_REQUIRE_MMS = (1 << 0);     // 1
private static final int HAS_SUBJECT = (1 << 1); // 2
private static final int HAS_ATTACHMENT = (1 << 2); // 4
private static final int LENGTH_REQUIRES_MMS = (1 << 3); // 8
private static final int FORCE_MMS = (1 << 4); // 16
这里我们用8位二进制来表示彩信的状态:
0000 0001 RECIPIENTS_REQUIRE_MMS
0000 0010 HAS_SUBJECT
0000 0100 HAS_ATTACHMENT
0000 1000 LENGTH_REQUIRES_MMS
0001 0000 FORCE_MMS
下面的这个是每次我们更新彩信状态的方法代码片段:
int oldState = mMmsState;
if (on) {
mMmsState |= state;
} else {
mMmsState &= ~state;
}
if (mMmsState == FORCE_MMS && ((oldState & ~FORCE_MMS) > 0)) {
mMmsState = 0;
}
if (notify) {
if (oldState == 0 && mMmsState != 0) {
mStatusListener.onProtocolChanged(true);
} else if (oldState != 0 && mMmsState == 0) {
mStatusListener.onProtocolChanged(false);
}
}

我们会想一下我们上面的分析,在我们刚刚构建幻灯片是,彩信状态被更新为了FORCE_MMS,如果幻灯片编辑完毕,我们刚刚也看到了,调用了

添加:updateState(HAS_ATTACHMENT,true,false);

那么现在彩信状态更新为了:FORCE_MMS|
HAS_ATTACHMENT,即:00010000|0000 0100。如果我们在添加了彩信主题,那么我们的彩信状态就变成了FORCE_MMS|
HAS_ATTACHMENT | HAS_SUBJECT。即:00010000|0000 0100|0000 0010。

删除:调用updateState(HAS_ATTACHMENT,false,false);

那么我们的彩信状态为

FORCE_MMS| HAS_ATTACHMENT | HAS_SUBJECT)&~HAS_ATTCHEMNT

得到的值为:FORCE_MMS| HAS_SUBJECT,如果我们这个时候在删除了彩信主题,那么值为:FOR_MMS,那么下面的代码就起作用了:

if(mMmsState==FORCE_MMS&&
((oldState & ~FORCE_MMS)> 0)) {

mMmsState= 0;

}

也就是说,彩信是有标志位的,它的标志就是如果内容长度达到了彩信的要求、含有主题、含有附件那么就认为它是彩信,如果没有以上标志,那么就认为它是短信。

05-11 16:58