彩信的接收简介:
主要是由应用程序负责从彩信服务中心(MMSC Multimedia Messaging Service Center)下载彩信信息。大致的流程是Frameworks会先发出一条短信,告知应用程序有一个彩信,短信中含有一些信息比如过期日期,发送者手机号码,彩信的URL等,然后应用程序自行通过HTTP取回URL所指的彩信内容。具体的流程为:
Telephony Frameworks会先发出一个Intent:android.provider.Telephony.Intents.WAP_PUSH_RECEIVED_ACTION=”android.provider.Telephony.WAP_PUSH_RECEIVED”告知上层应用有一个彩信来了。这个Intent中会含有一个”data”byte数组(通过byte[] data = intent.getByteArrayExtra(“data”)来获取),这个Byte数组是关于这个彩信的一些信息的描述,它是一个NotificationInd,里面含有彩信的一些信息,比如发送者手机号码,彩信的ContentLocation(URL)。之后是由应用程序来决定如何做下一步的处理。
在Mms中是由transaction.PushReceiver.java来接收WAP_PUSH_RECEIVED_ACTION,接收到彩信通知Intent后,它会做一些预处理,把data字段取出来,用Pdu的工具解析成为GenericPdu,然后转化为NotificationInd,并把它写入数据库,然后会启动TransactionService来做进一步的NOTIFICATION_TRANSACTION处理,同时把这个NotificationInd的Uri也传过去。
TransactionService被唤起,在其onStartCommand中会处理一下把PushReceiver所传来的Intent放入自己的MessageQueue中,然后在Handler.handleMessage()中处理TRANSACTION_REQUEST时处理NOTIFICATION_TRANSACTION。先是加载默认的一些彩信相关的配置信息,主要是MMSC,Proxy和Port,这些都是与运营商相关的信息,可以通过APN的设置来更改。TransactionService用PushReciver传来的NotificationInd的Uri和加载的配置信息TransactionSettings构建一个NotificationTransaction对象。之后,TransactionService检查其内的二个队列,或是加入Pending队列,或是直接处理(加入到正在处理队列),处理也是直接调用NotificationTransaction.process()。
NotificationTransaction的process()方法是继承自父类Transaction的方法,它只是简单的开启一个新的线程,然后返回,这样就可以让Service去处理其他的Transaction Request了。
在线程中,首先从DownloadManager和TelephonyManager中加载一些配置信息,是否彩信设置为自动获取(auto retrieve),以及Telephony是否设置为数据延迟(DATA_SUSPEND),然后会采取不同的措施,再从NotificationInd中取出彩信的过期日期。如果配置为不取数据(更确切的说,是不现在取数据),那么就先给DownloadManager的状态标记为STATE_UNSTARTED,再给MMSC发送一个Notify
Response Indication,之后结束处理,函数返回,彩信的通知处理流程到此为止。用户可以通过操作UI,用其他方法手动下载彩信,这个会在后面详细讨论。
如果设置为自动获取或者数据传输是畅通的,那么就把DownloadManager状态标记为START_DOWNLOADING并开始下载彩信数据。彩信的获取是通过HTTP到彩信的ContentLocation(URL)取得数据。先是调用父类方法getPdu(),传入彩信的URL,最终调用HttpUtils的httpConnection方法发送HTTP GET请求,MMSC会把彩信数据返回,作为getPdu()的返回值返回。拿到的是一个byte数组,需要用Pdu的工具解析成为GenericPdu,然后用PduPersister把其写入数据库,再把彩信的大小更新到数据库,到这里一个彩信的接收就算完成了。剩下的就是,因为已经获得了彩信的数据,所以要把先前的通知信息(NotificationInd)删除掉,然后更新一下相关的状态,给MMSC返回Notify
Response Indication,结束处理。
如前所述,如果彩信配置设置为不自动获取,那么UI刷新了后就会显示彩信通知:到期日期,彩信大小等,并提供一个”Download”按扭。用户可以点击按扭来下载彩信内容,点击按扭后,会启动TransactionService,把彩信通知的Uri,和RETRIEVE_TRANSACTION request打包进一个Intent传给TransactionService。TransactionService,像处理其他的Transaction一样,都是放进自己的MessageQueue,然后加载默认的TransactionSettings,构建RetrieveTransaction对象,然后处理调用RetrieveTransaction.process()。
RetrieveTransaction也是继承自Transaction,其process()也是创建一个线程,然后返回。在线程中,首先它用Pdu工具根据Uri从数据库中加载出彩信通知(NotificationInd),从NotificationInd中取得彩信的过期日期,检查过期日期,如果彩信已经过期,那么给MMSC发送Notify Response Indication。把DownloadManager状态标记为开始下载,然后如果彩信已过期,标记Transaction状态为Failed,然后返回,结束处理流程。如果一切正常,会用getPdu()从彩信的ContentLocation(URL)上面获取彩信内容,它会用HttpUtils.httpConnection()通过HTTP来获取,返回一个byte数组。用Pdu工具解析byte数组,得到GenericPdu,检查一下是否是新信息,是否是重复的信息,如果重复,标记状为失败,然后返回,结束处理。如果是新信息,先把GenericPdu用PduPersister写入数据库中,更新信息大小和ContentLocation(URL)到数据库中,到这里一个彩信其实已经全部获取完了。接下来就是发送收到确认信息给MMSC,标记处理状态为成功,结束处理。这时UI应该监听到数据库变化,并刷新,新信息应该会显示给用户。
在分析代码之前,也是首先与大家分享一下在网络上很流行的两张顺序图,本人也受到了很大的启发。
android的彩信接收应用层部分从PushReceiver类开始。当onReceive被调用后,让屏幕亮5秒( wl.acquire(5000);),然后创建一个ReceivePushTask并使用它的execute方法。ReceivePushTask(内部类)是一个AsyncTask,实现了doInBackground()方法。根据消息类型做出相应的处理。
调用PushReceiver.java类中的doInBackground()方法,部分代码如下:《TAG 2-1》
case MESSAGE_TYPE_NOTIFICATION_IND: {
NotificationInd nInd = (NotificationInd) pdu;
if (MmsConfig.getTransIdEnabled()) {
byte [] contentLocation = nInd.getContentLocation();
if ('=' == contentLocation[contentLocation.length - 1]) {
byte [] transactionId = nInd.getTransactionId();
byte [] contentLocationWithId = new byte [contentLocation.length
+ transactionId.length];
System.arraycopy(contentLocation, 0, contentLocationWithId,
0, contentLocation.length);
System.arraycopy(transactionId, 0, contentLocationWithId,
contentLocation.length, transactionId.length);
nInd.setContentLocation(contentLocationWithId);
}
}
if (!isDuplicateNotification(mContext, nInd)) {
int subId = intent.getIntExtra(MSimConstants.SUBSCRIPTION_KEY, 0);
ContentValues values = new ContentValues(1);
values.put(Mms.SUB_ID, subId);
Uri uri = p.persist(pdu, Inbox.CONTENT_URI,
true,
MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext),
null);
SqliteWrapper.update(mContext, cr, uri, values, null, null);
if (MessageUtils.isMobileDataDisabled(mContext) &&
!MessageUtils.CAN_SETUP_MMS_DATA) {
MessagingNotification.nonBlockingUpdateNewMessageIndicator(mContext,
MessagingNotification.THREAD_ALL, false);
}
// Start service to finish the notification transaction.
Intent svc = new Intent(mContext, TransactionService.class);
svc.putExtra(TransactionBundle.URI, uri.toString());
svc.putExtra(TransactionBundle.TRANSACTION_TYPE,
Transaction.NOTIFICATION_TRANSACTION);
svc.putExtra(Mms.SUB_ID, subId); //destination sub id
svc.putExtra(MultiSimUtility.ORIGIN_SUB_ID,
MultiSimUtility.getCurrentDataSubscription(mContext));
if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) {
boolean isSilent = true; //default, silent enabled.
if ("prompt".equals(
SystemProperties.get(
TelephonyProperties.PROPERTY_MMS_TRANSACTION))) {
isSilent = false;
}
if (isSilent) {
Log.d(TAG, "MMS silent transaction");
Intent silentIntent = new Intent(mContext,
com.android.mms.ui.SelectMmsSubscription.class);
silentIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
silentIntent.putExtras(svc); //copy all extras
mContext.startService(silentIntent);
} else {
Log.d(TAG, "MMS prompt transaction");
triggerPendingOperation(svc, subId);
}
} else {
mContext.startService(svc);
}
} else if (LOCAL_LOGV) {
Log.v(TAG, "Skip downloading duplicate message: "
+ new String(nInd.getContentLocation()));
}
break;
}
doInBackground中将其中的数据转成GenericPdu,并根据其消息类型做出不同的操作。如果是发送报告或已读报告,将其存入数据库。如果是彩信通知,若已存在,则不处理。否则将其存入数据库。启动TransactionService进行处理。TransactionService中的处理主要是调用mServiceHandler,大体过程与发送彩信时相同,只是此处创建的是NotificationTransaction。如果不支持自动下载或数据传输没打开,仅通知mmsc。否则,下载相应彩信,删除彩信通知,通知mmsc,删除超过容量限制的彩信,通知TransactionService处理其余待发送的彩信。
我们接着进入TransactionService.java类中进行分析,启动服务后,在onStartCommand()方法中接收Intent并进行封装并放入自己的MessageQueue中,在Handler的handleMessgae中进行处理:
调用mServiceHandler,根据业务类型创建一个NotificationTransaction对象,如下代码:《TAG 2-2》
case EVENT_TRANSACTION_REQUEST:
int serviceId = msg.arg1;
try {
TransactionBundle args = (TransactionBundle) msg.obj;
TransactionSettings transactionSettings;
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
Log.v(TAG, "EVENT_TRANSACTION_REQUEST MmscUrl=" +
args.getMmscUrl() + " proxy port: " + args.getProxyAddress());
}
// Set the connection settings for this transaction.
// If these have not been set in args, load the default settings.
String mmsc = args.getMmscUrl();
if (mmsc != null) {
transactionSettings = new TransactionSettings(
mmsc, args.getProxyAddress(), args.getProxyPort());
} else {
transactionSettings = new TransactionSettings(
TransactionService.this, null);
}
int transactionType = args.getTransactionType();
if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {
Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" +
transactionType + " " + decodeTransactionType(transactionType));
if (transactionSettings != null) {
Log.v(TAG, "mmsc=" + transactionSettings.getMmscUrl()
+ ", address=" + transactionSettings.getProxyAddress()
+ ", port=" + transactionSettings.getProxyPort());
}
}
// Create appropriate transaction
switch (transactionType) {
case Transaction.NOTIFICATION_TRANSACTION:
String uri = args.getUri();
if (uri != null) {
transaction = new NotificationTransaction(
TransactionService.this, serviceId,
transactionSettings, uri);
} else {
// Now it's only used for test purpose.
byte[] pushData = args.getPushData();
PduParser parser = new PduParser(pushData);
GenericPdu ind = parser.parse();
int type = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
if ((ind != null) && (ind.getMessageType() == type)) {
transaction = new NotificationTransaction(
TransactionService.this, serviceId,
transactionSettings, (NotificationInd) ind);
} else {
Log.e(TAG, "Invalid PUSH data.");
transaction = null;
return;
}
}
break;
case Transaction.RETRIEVE_TRANSACTION://这里当用户在界面中点击下载,会调用MessageListItem.java方法中的startDownloadAttachment()方法,随后会走这里。
transaction = new RetrieveTransaction(
TransactionService.this, serviceId,
transactionSettings, args.getUri());
break;
case Transaction.SEND_TRANSACTION:
transaction = new SendTransaction(
TransactionService.this, serviceId,
transactionSettings, args.getUri());
break;
case Transaction.READREC_TRANSACTION:
transaction = new ReadRecTransaction(
TransactionService.this, serviceId,
transactionSettings, args.getUri());
break;
default:
Log.w(TAG, "Invalid transaction type: " + serviceId);
transaction = null;
return;
}
if (!processTransaction(transaction)) {
transaction = null;
return;
}
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || DEBUG) {
Log.v(TAG, "Started processing of incoming message: " + msg);
}
} catch (Exception ex) {
Log.w(TAG, "Exception occurred while handling message: " + msg, ex);
if (transaction != null) {
try {
transaction.detach(TransactionService.this);
if (mProcessing.contains(transaction)) {
synchronized (mProcessing) {
mProcessing.remove(transaction);
}
}
} catch (Throwable t) {
Log.e(TAG, "Unexpected Throwable.", t);
} finally {
// Set transaction to null to allow stopping the
// transaction service.
transaction = null;
}
}
} finally {
if (transaction == null) {
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
Log.v(TAG, "Transaction was null. Stopping self: " + serviceId);
}
launchRetryAttempt++;
if (launchRetryAttempt <= maxLaunchRetryAttempts) {
Log.d(TAG, "launchTransaction retry attempt - "
+ launchRetryAttempt);
TransactionBundle args = (TransactionBundle) msg.obj;
sleep(5*1000);
launchTransaction(serviceId, args, false);
} else {
Log.e(TAG, "Multiple launchTransaction retries failed");
launchRetryAttempt = 0;
decRefCount();
}
}
}
return;
这里接着调用processTransaction()方法;
*/
private boolean processTransaction(Transaction transaction) throws IOException {
// Check if transaction already processing
synchronized (mProcessing) {
for (Transaction t : mPending) {
if (t.isEquivalent(transaction)) {
Log.d(TAG, "Transaction already pending: " +
transaction.getServiceId());
decRefCount();
return true;
}
}
for (Transaction t : mProcessing) {
if (t.isEquivalent(transaction)) {
Log.d(TAG, "Duplicated transaction: " + transaction.getServiceId());
decRefCount();
return true;
}
}
/*
* Make sure that the network connectivity necessary
* for MMS traffic is enabled. If it is not, we need
* to defer processing the transaction until
* connectivity is established.
*/
Log.d(TAG, "processTransaction: call beginMmsConnectivity...");
int connectivityResult = beginMmsConnectivity();
if (connectivityResult == PhoneConstants.APN_REQUEST_STARTED) {
mPending.add(transaction);
if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {
Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " +
"defer transaction pending MMS connectivity");
}
return true;
}
Log.d(TAG, "Adding transaction to 'mProcessing' list: " + transaction);
mProcessing.add(transaction);
}
// Set a timer to keep renewing our "lease" on the MMS connection
sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),
APN_EXTENSION_WAIT);
if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG) || DEBUG) {
Log.v(TAG, "processTransaction: starting transaction " + transaction);
}
// Attach to transaction and process it
transaction.attach(TransactionService.this);
transaction.process();
return true;
}
}
上述代码《TAG:2-3》中,调用Transaction.java类中的getPdu()方法下载彩信数据: