环境:Macbook pro (2.6 GHz Inter Core i5),操作系统为OS X 10.9.2。
一、准备工作
1.1、android4.4源码编译
下载android4.4源码,编译通过,模拟器正常启动运行。详细步骤网上有很多教程,可参见http://blog.csdn.net/szzhaom/article/details/21462329。
1.2、开发环境搭建
Android SDK下载地址:http://developer.android.com/sdk/index.html。本人下载的版本为:adt-bundle-mac-x86_64-20131030,此包自带eclipse,并集成了ADT插件、Android SDK Tools及Android Platform-tools,下载解包后即可开发Android应用,推荐使用。
1.3、相关概念
- Thread对话 ——是指用户与某个联系人或某几个联系人之间的一系列信息交互。在Mms中,用Thread Id来标识和管理对话,Thread Id也即在数据库表threads中的_id。
- Conversation ——是用来管理Thread对话的,Conversation是一个Thread对话的抽象出来数据结构,它能够从数据 库中查询,删除一个对话中的消息等,每一个Conversation有一个唯一的Thread Id。同时它也负责管理一些所有对话的功能,比如查询所有对话,删除所有对话等。Conversation更多时候是充当当前对话的角色,比如在新建信息时,编辑信息时,或是查看某个对话时,都会有一个 Conversation对象存在,以代表当前信息所处的对话。它是一个近似单键,都是通过Conversation的静态方法来获得 Conversation对象,有一些其他的方法也是静态的。
- ConversationList ——负责显示和编辑所有的对话,以列表形式显示所有的Thread,每一项代表一个Thread,通常也会显示这个Thread的状态,如有无草稿,信息发送/接收是否成功等。
- Message ——消息,泛指短信SMS和彩信MMS。因为不再区分短信和彩信,在对话列表,草稿管理和信息列表中它们都是一样的,都是信息。 Message的数据结构是MessageItem,它是一个纯数据结构,里面存储着关于一个信息的所有数据,还有MessageListitem,它是一个View,专门用于在消息列表中显示一个信息,里面的数据都是从MessageItem获取。它们统一都被 ComposeMessageActivity,MessageListAdapter和MessageListView来管理。
- WorkingMessage ——当前消息,它是专门用于代表当前正在创建和编辑的信息的数据结构无论是短信还是彩信,在创建和编辑的时候都放在一个WorkingMessage对象里面。这个对象也负责信息的发送,存储和存储为草稿。
- Slideshow ——在Mms应用里面,彩信是以Slideshow幻灯片的形式来展示的。一个彩信可以有多张幻灯片,每张幻灯片上面可以有图片,文字,音频和视频,可以设置每张幻灯片的浏览时长,布局等,这里的幻灯片与Office中的PowerPoint有几分类似。幻灯片的数目限制是以彩 信允许的附件大小为上限,这个也与每张幻灯片上面的媒体大小有关。可以这样讲MMS就是以幻灯片形式存在的,创建的时候是一张一张的编辑,收到的彩信或编辑完后,就可以一张张的放映浏览幻灯片。
- Recipient ——接受人,这里是指信息的接收者,要么是一个陌生的电话号码,要么是一个陌生的电子邮件地址(彩信时),要么就是联系人数据库中的联系人。彩信和短信对接收人的数量都有限制,这个也是在Mms的Settings时面可以更改的。每一条信息要想发送成功,必须保证接收人是一个合法的联系人,合法也是不同的手机有不同的定义,但通常来讲,要么与联系人数据库中的某个联系人匹配,要么是一个电话号码,要么是一个电子邮件地址,其他情况则视为不合法,对于有不合法接收人的信息,不会进行发送。管理联系人的数据结构是Contact和ContactList,其中ContactList是一个以Contact为元素的ArrayList。Contact不但存储有联系人的一些信息,如名字,电话号码等,它还能与联系人数据库进行同步,也就是它能保证它是一个合法的联络人,并在数据库中存在。在信息发送前会先进行一次联系人同步,以保证已有的联系人是正确的。
二、MMS源码目录结构分析
2.1、导入MMS源码
启动eclipse,新建java工程,导入android源码,如下图:
2.2、MMS包关系图
Android4.4版本的MMS模块有11个包,其关系图如下:
com.android.mms.ui
—— GUI展示层,用于展示对话列表,消息列表,消息编辑页,彩信附件编辑,彩信展示,播放幻灯片。负责直接与用户交互。
com.android.mms.transaction
—— 对于Mms来讲是最底层的一个包,用户不可见,它负责发信息的最后处理和收信息的最初处理。主要是负责发送信息和接收信息。它并不是真正的发送和接收信息。是由系统Frameworks里面来负责接收和发送信息。这个包只是对于Mms应用层来讲是发送和接收。
com.android.mms.data
—— 用于操作当前正在编辑的信息的相关数据,比如联系人列表,比如当前对话,比如当前消息。负责管理当前正在编辑的信息和当前所处的对话以及当前信息用到的联 系人。这些类都是在编辑信息的时候使用,由于这些多半都是用来管理数据的,而又无法直接做为对象传递给编辑器。所以它们的很多方法都是静态的,也就是这些 类都近似单键。
com.android.mms.model —— 定义了彩信支持的附件数据结构和附件的组织方式。彩信可包含的内容有图片,视频,音频和文字。这些内容可以单独存在,也可以组合在一起。如果组合在一起就变成了幻灯片。用户可以用幻灯片的方式来创建含有多个媒体的附件,图文并茂的展示。每张幻灯片上面可以加视频,音频,图片和文字,但通常一张幻灯片上面只允许加一个图片或视频,文字是都可以添加的,音频在没有视频的情况下只可以添加的。播放的时候可以设置每张幻灯片的播放时长,以及文字的滚动速度等等。
com.android.mms.dom/org.w3c.dom
—— 用于解析彩信内容smil的工具包。
com.android.mms.drm
—— 用于处理DRM的媒体文件的工具包
com.android.mms.exif
—— 用于处理Exif格式的图像文件的工具包
com.android.mms.layout
—— 为了满足特殊需要而改写的布局元素
com.android.mms.model ——
定义了彩信支持的附件数据结构和附件的组织方式。彩信可包含的内容有图片,视频,音频和文字。这些内容可以单独存在,也可以组合在一起。如果组合在一起就变成了幻灯片。用户可以用幻灯片的方式来创建含有多个媒体的附件,图文并茂的展示。每张幻灯片上面可以加视频,音频,图片和文字,但通常一张幻灯片上面只允许加一个图片或视频,文字是都可以添加的,音频在没有视频的情况下只可以添加的。播放的时候可以设置每张幻灯片的播放时长,以及文字的滚动速度等等。
com.android.mms.util
——整个Mms共享的工具包,其中全部都是直接使用类,不可以创建对象和以对象方式来使用。
com.android.mms.widget ——用于Mms数据库发生改变时发出通知并做出相应处理。
三、短信发送流程分析
信息的发送,对于MMS应用程序来讲主要就是在信息数据库中创建并维护一条信息记录,真正的发送过程是由底层(Frameworks层)完成的,本文只分析MMS应用程序的短信发送流程,Frameworks层的发送流程后续更新。
3.1、启动发送流程:ComposeMessageActivity
当用户启动MMS短信应用,看到的第一个界面就是所有对话的信息列表,对应的源码为(.ui.ConversationList);写新信息或编辑信息时,会出现信息编辑界面,对应源码(.ui.ComposeMessageActivity)。用户写好信息和收信人,点击发送,即启动短信发送流程。ComposeMessageActivity实现了View.OnClickListener接口,代码如下:
public class ComposeMessageActivity extends Activity implements View.OnClickListener, TextView.OnEditorActionListener,MessageStatusListener, Contact.UpdateListener{
...
}
当用户点击时,将会调用onClick()方法:
@Override
public void onClick(View v) {
if ((v == mSendButtonSms || v == mSendButtonMms) && isPreparedForSending()) {
confirmSendMessageIfNeeded();
} else if ((v == mRecipientsPicker)) {
launchMultiplePhonePicker();
}
}
跟踪confirmSendMessageIfNeeded(),根据收信人输入框的状态做了一些处理:
private void confirmSendMessageIfNeeded() {
if (!isRecipientsEditorVisible()) {//收信人输入框不可见
sendMessage(true);
return;
}
boolean isMms = mWorkingMessage.requiresMms();
if (mRecipientsEditor.hasInvalidRecipient(isMms)) {//有无效的收信人
if (mRecipientsEditor.hasValidRecipient(isMms)) {
...
} else {
...
}
} else {
// 收信人输入框可见
ContactList contacts = mRecipientsEditor.constructContactsFromInput(false);
mDebugRecipients = contacts.serialize();
sendMessage(true);
}
}
继续跟踪sendMessage(),系统判断电话是否处于紧急拨号模式、移除收信人输入框的监听后将发送流程交给了WorkingMessage。
private void sendMessage(boolean bCheckEcmMode) {
if (bCheckEcmMode) {
// 判断电话是否处于紧急拨号模式
String inEcm = SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE);
if (Boolean.parseBoolean(inEcm)) {
...
}
}
if (!mSendingMessage) {
...
// 发送时会改变收信人输入框的状态,所以先移除监听,发送完成后再添加
removeRecipientsListeners();
mWorkingMessage.send(mDebugRecipients);
mSentMessage = true;
mSendingMessage = true;
addRecipientsListeners();
mScrollOnSend = true;
}
if (mSendDiscreetMode) {
finish();
}
}
3.2、拆分发送流程:WorkingMessage
WorkingMessage收到要发送的信息后,除了同步收信人、判断是否为彩信(根据信息内容)外,做的一件重要的事情就是根据配置和信息内容将流程分为短信发送流程和彩信发送流程:
public void send(final String recipientsInUI) {
...
// 同步收信人,判断是否为彩信
prepareForSave(true /* notify */);
final Conversation conv = mConversation;
String msgTxt = mText.toString();
if (requiresMms() || addressContainsEmailToMms(conv, msgTxt)) {//彩信发送流程
...
} else {//短信发送流程
final String msgText = mText.toString();
new Thread(new Runnable() { //启动线程后台发送
@Override
public void run() {
preSendSmsWorker(conv, msgText, recipientsInUI);
updateSendStats(conv);
}
}, "WorkingMessage.send SMS").start();
}
// 更新收信人缓存
RecipientIdCache.updateNumbers(conv.getThreadId(), conv.getRecipients());
mDiscarded = true;
}
跟踪preSendSmsWorker –> sendSmsWorker,流程交由SmsMessageSender处理。
private void preSendSmsWorker(Conversation conv, String msgText, String recipientsInUI) {
...
if (LogTag.SEVERE_WARNING && ((origThreadId != 0 && origThreadId != threadId) ||
(!semiSepRecipients.equals(recipientsInUI) && !TextUtils.isEmpty(recipientsInUI)))) {
...
}else {
// 发送短信
sendSmsWorker(msgText, semiSepRecipients, threadId);
// 删除草稿箱的信息.
deleteDraftSmsMessage(threadId);
}
}
private void sendSmsWorker(String msgText, String semiSepRecipients, long threadId) {
String[] dests = TextUtils.split(semiSepRecipients, ";");
...
MessageSender sender = new SmsMessageSender(mActivity, dests, msgText, threadId);
try {
sender.sendMessage(threadId);
// 确保此信息没有超过设置的上限,否则按时间删除此对话最早的信息
Recycler.getSmsRecycler().deleteOldMessagesByThreadId(mActivity, threadId);
} catch (Exception e) {
Log.e(TAG, "Failed to send SMS message, threadId=" + threadId, e);
}
mStatusListener.onMessageSent();
MmsWidgetProvider.notifyDatasetChanged(mActivity);
}
3.3、按收信人拆分信息:SmsMessageSender
SmsMessageSender的主要任务就是把信息按收信人拆分,也就是说,短信是要给每个收信人都发一条,虽然可能只编辑一个短信,但是当收信人有多个时,就变成了多条短信,要给每个收信人都发一条短信。因此,SmsMessageSender的第一个任务就是分析收信人地址,得到收信人的个数,然后把信息按每个收信人都放入待发送的队列中。之后它会发送Intent唤起SmsReceiverService来处理队列,它的工作就完成了。SmsMessageSender的sendMessage()返回后,WorkingMessage会再次回调UI的接口,因为此时短信已被写入数据库,所以UI会刷新信息列表显示刚刚的短信,这时的状态应该是正在发送中,因为是从待发送队列中取到的。从这以后,发送流程的类不会再直接与UI进行通信,发送服务SmsReceiverService会直接更新数据库中短信的状态,而UI会监听数据库的变 化,一旦信息数据发生变化,UI就会刷新列表中的消息,更新状态,比如将发送中变成已发送,或是标明发送失败等,而这些状态都是发送服务在更新。
public boolean sendMessage(long token) throws MmsException {
return queueMessage(token);
}
private boolean queueMessage(long token) throws MmsException {
if ((mMessageText == null) || (mNumberOfDests == 0)) {
// Don't try to send an empty message.
throw new MmsException("Null message body or dest.");
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
boolean requestDeliveryReport = prefs.getBoolean(
MessagingPreferenceActivity.SMS_DELIVERY_REPORT_MODE,
DEFAULT_DELIVERY_REPORT_MODE);
for (int i = 0; i < mNumberOfDests; i++) {
try {
if (LogTag.DEBUG_SEND) {
Log.v(TAG, "queueMessage mDests[i]: " + mDests[i] + " mThreadId: " + mThreadId);
}
Sms.addMessageToUri(mContext.getContentResolver(),
Uri.parse("content://sms/queued"), mDests[i],
mMessageText, null, mTimestamp,
true /* read */,
requestDeliveryReport,
mThreadId);
} catch (SQLiteException e) {
if (LogTag.DEBUG_SEND) {
Log.e(TAG, "queueMessage SQLiteException", e);
}
SqliteWrapper.checkSQLiteException(mContext, e);
}
}
// 发送广播通知SmsReceiverService处理待发送的信息
mContext.sendBroadcast(new Intent(SmsReceiverService.ACTION_SEND_MESSAGE,
null,
mContext,
SmsReceiver.class));
return false;
}
Sms.addMessageToUri进入Frameworks层,本文不再深入。
3.4、短信服务:SmsReceiverService
SmsReceiverService不能直译按字面意思理解,它不只是短信接收服务,实际上短信的发送和接收都由它负责处理。3.3说到待发送的信息写入数据库后会发送广播通知ACTION_SEND_MESSAGE,这个通知由SmsReceiver负责接收,然后交由SmsReceiverService处理。
public class SmsReceiver extends BroadcastReceiver {
static final Object mStartingServiceSync = new Object();
static PowerManager.WakeLock mStartingService;
private static SmsReceiver sInstance;
public static SmsReceiver getInstance() {
if (sInstance == null) {
sInstance = new SmsReceiver();
}
return sInstance;
}
@Override
public void onReceive(Context context, Intent intent) {
onReceiveWithPrivilege(context, intent, false);
}
protected void onReceiveWithPrivilege(Context context, Intent intent, boolean privileged) {
// 权限控制,防止短信欺骗
if (!privileged && intent.getAction().equals(Intents.SMS_DELIVER_ACTION)) {
return;
}
intent.setClass(context, SmsReceiverService.class);
intent.putExtra("result", getResultCode());
beginStartingService(context, intent);
}
public static void beginStartingService(Context context, Intent intent) {
synchronized (mStartingServiceSync) {
if (mStartingService == null) {
PowerManager pm =
(PowerManager)context.getSystemService(Context.POWER_SERVICE);
mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"StartingAlertService");
mStartingService.setReferenceCounted(false);
}
mStartingService.acquire();
context.startService(intent);//启动SmsReceiverService处理
}
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
int serviceId = msg.arg1;
Intent intent = (Intent)msg.obj;
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
Log.v(TAG, "handleMessage serviceId: " + serviceId + " intent: " + intent);
}
if (intent != null && MmsConfig.isSmsEnabled(getApplicationContext())) {
String action = intent.getAction();
int error = intent.getIntExtra("errorCode", 0);
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
Log.v(TAG, "handleMessage action: " + action + " error: " + error);
}
if (MESSAGE_SENT_ACTION.equals(intent.getAction())) {
handleSmsSent(intent, error);
} else if (SMS_DELIVER_ACTION.equals(action)) {
handleSmsReceived(intent, error);
} else if (ACTION_BOOT_COMPLETED.equals(action)) {
handleBootCompleted();
} else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
handleServiceStateChanged(intent);
} else if (ACTION_SEND_MESSAGE.endsWith(action)) {//处理待发送信息
handleSendMessage();
} else if (ACTION_SEND_INACTIVE_MESSAGE.equals(action)) {
handleSendInactiveMessage();
}
}
SmsReceiver.finishStartingService(SmsReceiverService.this, serviceId);
}
}
private void handleSendMessage() {
if (!mSending) {
sendFirstQueuedMessage();
}
}
//发送待发送队列里的第一条短信(按时间升序排列)
public synchronized void sendFirstQueuedMessage() {
boolean success = true;
// 从数据库获取所有待发送的信息
final Uri uri = Uri.parse("content://sms/queued");
ContentResolver resolver = getContentResolver();
Cursor c = SqliteWrapper.query(this, resolver, uri,
SEND_PROJECTION, null, null, "date ASC"); // date ASC 按时间排序,时间早的先发
if (c != null) {
try {
if (c.moveToFirst()) {//取第一条
String msgText = c.getString(SEND_COLUMN_BODY);
String address = c.getString(SEND_COLUMN_ADDRESS);
int threadId = c.getInt(SEND_COLUMN_THREAD_ID);
int status = c.getInt(SEND_COLUMN_STATUS);
int msgId = c.getInt(SEND_COLUMN_ID);
Uri msgUri = ContentUris.withAppendedId(Sms.CONTENT_URI, msgId);
SmsMessageSender sender = new SmsSingleRecipientSender(this,
address, msgText, threadId, status == Sms.STATUS_PENDING,
msgUri);
try {
sender.sendMessage(SendingProgressTokenManager.NO_TOKEN);;
mSending = true;
} catch (MmsException e) {
Log.e(TAG, "sendFirstQueuedMessage: failed to send message " + msgUri
+ ", caught ", e);
mSending = false;
messageFailedToSend(msgUri, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
success = false;
// 发送ACTION_SEND_MESSAGE通知,尝试再次发送。
sendBroadcast(new Intent(SmsReceiverService.ACTION_SEND_MESSAGE,
null,
this,
SmsReceiver.class));
}
}
} finally {
c.close();
}
}
if (success) {
unRegisterForServiceStateChanges();
}
}
SmsReceiverService根据不同的ACTION进行不同的处理,当收到的通知是ACTION_SEND_MESSAGE时,它会从数据库取出一条最早的待发送信息交由SmsSingleRecipientSender发送。
3.5、短信发送:SmsSingleRecipientSender
SmsSingleRecipientSender主要做了4件事情:
1、分割信息:调用SmsManager的方法divideMessage()来把短信分割成适合发送的几个部分,因为可能信息过长,不能一次发送完成,所以就需要分成几部分来分次发送;
2、把消息移动到发件箱;
3、创建PendingIntent:针对分割后的每一部分都会创建二个PendingIntent,这二个PendingIntent都是给底层用的,一个用于当短信被发送出去时广播出来,另一个是在短信已被收信人接收到时广播出来。所以二个广播的作用是,一个可用于标识短信已发送,另一个则可以作为送达的通知;
4、发送短信:调用SmsManager.sendMultipartTextMessage交由底层(Frameworks层)发送短信。
public boolean sendMessage(long token) throws MmsException {
SmsManager smsManager = SmsManager.getDefault();
ArrayList<String> messages = null;
if ((MmsConfig.getEmailGateway() != null) &&
(Mms.isEmailAddress(mDest) || MessageUtils.isAlias(mDest))) {
String msgText;
msgText = mDest + " " + mMessageText;
mDest = MmsConfig.getEmailGateway();
messages = smsManager.divideMessage(msgText);//分割信息
} else {
messages = smsManager.divideMessage(mMessageText);//分割信息
mDest = PhoneNumberUtils.stripSeparators(mDest);
mDest = Conversation.verifySingleRecipient(mContext, mThreadId, mDest);
}
int messageCount = messages.size();
if (messageCount == 0) {
// Don't try to send an empty message.
throw new MmsException("SmsMessageSender.sendMessage: divideMessage returned " +
"empty messages. Original message is \"" + mMessageText + "\"");
}
boolean moved = Sms.moveMessageToFolder(mContext, mUri, Sms.MESSAGE_TYPE_OUTBOX, 0);//移动短信到发件箱
if (!moved) {
throw new MmsException("SmsMessageSender.sendMessage: couldn't move message " +
"to outbox: " + mUri);
}
ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(messageCount);
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount);
for (int i = 0; i < messageCount; i++) {
if (mRequestDeliveryReport && (i == (messageCount - 1))) {
// 短信送达广播通知,根据配置选择是否需要
deliveryIntents.add(PendingIntent.getBroadcast(
mContext, 0,
new Intent(
MessageStatusReceiver.MESSAGE_STATUS_RECEIVED_ACTION,
mUri,
mContext,
MessageStatusReceiver.class),
0));
} else {
deliveryIntents.add(null);
}
Intent intent = new Intent(SmsReceiverService.MESSAGE_SENT_ACTION,
mUri,
mContext,
SmsReceiver.class);
int requestCode = 0;
if (i == messageCount -1) {
requestCode = 1;
intent.putExtra(SmsReceiverService.EXTRA_MESSAGE_SENT_SEND_NEXT, true);
}
//已发送广播通知
sentIntents.add(PendingIntent.getBroadcast(mContext, requestCode, intent, 0));
}
try {//发送短信
smsManager.sendMultipartTextMessage(mDest, mServiceCenter, messages, sentIntents, deliveryIntents);
} catch (Exception ex) {
Log.e(TAG, "SmsMessageSender.sendMessage: caught", ex);
throw new MmsException("SmsMessageSender.sendMessage: caught " + ex +
" from SmsManager.sendTextMessage()");
}
return false;
}
已发送广播和已送达广播分别由SmsReceiverService和MessageStatusReceiver监听。它们收到广播后,会从Intent中取得详细的发送和送达状态,然后更新数据库中信息的状态 (status),UI当发现数据库变化后,就会更新UI。
至此,一条短信发送完成。详细流程图如下: