彩信的接收简介:
主要是由应用程序负责从彩信服务中心(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中进行处理:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
Log.d(TAG, "onStartCommand(): E");
incRefCount();
Message msg = mServiceHandler.obtainMessage(EVENT_NEW_INTENT);
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
return Service.START_NOT_STICKY;
}首先调用handleMessage()方法;
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "Handling incoming message: " + msg + " = " + decodeMessage(msg));
Transaction transaction = null;
switch (msg.what) {
case EVENT_NEW_INTENT:
onNewIntent((Intent)msg.obj, msg.arg1);
break;
接着调用onNewIntent()方法;
public void onNewIntent(Intent intent, int serviceId) {
mConnMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
/*AddBy:yabin.huang BugID:SWBUG00029243 Date:20140515*/
if (mConnMgr == null) {
endMmsConnectivity();
decRefCount();
return ;
}
NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
boolean noNetwork = ni == null || !ni.isAvailable();
Log.d(TAG, "onNewIntent: serviceId: " + serviceId + ": " + intent.getExtras() +
" intent=" + intent);
Log.d(TAG, " networkAvailable=" + !noNetwork);
Bundle extras = intent.getExtras();
String action = intent.getAction();//这里的action的值为null
if ((ACTION_ONALARM.equals(action) || ACTION_ENABLE_AUTO_RETRIEVE.equals(action) ||
(extras == null)) || ((extras != null) && !extras.containsKey("uri")
&& !extras.containsKey(CANCEL_URI))) {
//We hit here when either the Retrymanager triggered us or there is
//send operation in which case uri is not set. For rest of the
//cases(MT MMS) we hit "else" case.
// Scan database to find all pending operations.
Cursor cursor = PduPersister.getPduPersister(this).getPendingMessages(
System.currentTimeMillis());
Log.d(TAG, "Cursor= "+DatabaseUtils.dumpCursorToString(cursor));
if (cursor != null) {
try {
int count = cursor.getCount();
//if more than 1 records are present in DB.
if (count > 1) {
incRefCountN(count-1);
Log.d(TAG, "onNewIntent() multiple pending items mRef=" + mRef);
}
Log.d(TAG, "onNewIntent: cursor.count=" + count + " action=" + action);
if (count == 0) {
Log.d(TAG, "onNewIntent: no pending messages. Stopping service.");
RetryScheduler.setRetryAlarm(this);
cleanUpIfIdle(serviceId);
decRefCount();
return;
}
int columnIndexOfMsgId = cursor.getColumnIndexOrThrow(PendingMessages.MSG_ID);
int columnIndexOfMsgType = cursor.getColumnIndexOrThrow(
PendingMessages.MSG_TYPE);
while (cursor.moveToNext()) {
int msgType = cursor.getInt(columnIndexOfMsgType);
int transactionType = getTransactionType(msgType);
Log.d(TAG, "onNewIntent: msgType=" + msgType + " transactionType=" +
transactionType);
if (noNetwork) {
onNetworkUnavailable(serviceId, transactionType);
Log.d(TAG, "No network during MO or retry operation");
decRefCountN(count);
Log.d(TAG, "Reverted mRef to =" + mRef);
return;
}
switch (transactionType) {
case -1:
decRefCount();
break;
case Transaction.RETRIEVE_TRANSACTION:
// If it's a transiently failed transaction,
// we should retry it in spite of current
// downloading mode. If the user just turned on the auto-retrieve
// option, we also retry those messages that don't have any errors.
int failureType = cursor.getInt(
cursor.getColumnIndexOrThrow(
PendingMessages.ERROR_TYPE));
DownloadManager downloadManager = DownloadManager.getInstance();
boolean autoDownload = downloadManager.isAuto();
boolean isMobileDataEnabled = mConnMgr.getMobileDataEnabled();
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
Log.v(TAG, "onNewIntent: failureType=" + failureType +
" action=" + action + " isTransientFailure:" +
isTransientFailure(failureType) + " autoDownload=" +
autoDownload);
}
if (!autoDownload || MessageUtils.isMmsMemoryFull()
|| !isMobileDataEnabled) {
// If autodownload is turned off, don't process the
// transaction.
Log.d(TAG, "onNewIntent: skipping - autodownload off");
decRefCount();
break;
}
// Logic is twisty. If there's no failure or the failure
// is a non-permanent failure, we want to process the transaction.
// Otherwise, break out and skip processing this transaction.
if (!(failureType == MmsSms.NO_ERROR ||
isTransientFailure(failureType))) {
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
Log.v(TAG, "onNewIntent: skipping - permanent error");
}
decRefCount();
break;
}
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
Log.v(TAG, "onNewIntent: falling through and processing");
}
// fall-through
default:
Uri uri = ContentUris.withAppendedId(
Mms.CONTENT_URI,
cursor.getLong(columnIndexOfMsgId));
String txnId = getTxnIdFromDb(uri);
int subId = getSubIdFromDb(uri);
Log.d(TAG, "SubId from DB= "+subId);
if(subId != MultiSimUtility.getCurrentDataSubscription
(getApplicationContext())) {
Log.d(TAG, "This MMS transaction can not be done"+
"on current sub. Ignore it. uri="+uri);
decRefCount();
break;
}
int destSub = intent.getIntExtra(Mms.SUB_ID, -1);
int originSub = intent.getIntExtra(
MultiSimUtility.ORIGIN_SUB_ID, -1);
Log.d(TAG, "Destination Sub = "+destSub);
Log.d(TAG, "Origin Sub = "+originSub);
addUnique(txnId, destSub, originSub);
TransactionBundle args = new TransactionBundle(
transactionType, uri.toString());
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
Log.v(TAG, "onNewIntent: launchTransaction uri=" + uri);
}
// FIXME: We use the same serviceId for all MMs.
launchTransaction(serviceId, args, false);
break;
}
}
} finally {
cursor.close();
}
} else {
Log.d(TAG, "onNewIntent: no pending messages. Stopping service.");
RetryScheduler.setRetryAlarm(this);
cleanUpIfIdle(serviceId);
decRefCount();
}
} else if ((extras != null) && extras.containsKey(CANCEL_URI)) {
String uriStr = intent.getStringExtra(CANCEL_URI);
Uri mCancelUri = Uri.parse(uriStr);
for (Transaction transaction : mProcessing) {
transaction.cancelTransaction(mCancelUri);
}
for (Transaction transaction : mPending) {
transaction.cancelTransaction(mCancelUri);
}
} else {
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || DEBUG) {
Log.v(TAG, "onNewIntent: launch transaction...");
}
String uriStr = intent.getStringExtra("uri");
int destSub = intent.getIntExtra(Mms.SUB_ID, -1);
int originSub = intent.getIntExtra(MultiSimUtility.ORIGIN_SUB_ID, -1);
Uri uri = Uri.parse(uriStr);
int subId = getSubIdFromDb(uri);
String txnId = getTxnIdFromDb(uri);
if (txnId == null) {
Log.d(TAG, "Transaction already over.");
decRefCount();
return;
}
Log.d(TAG, "SubId from DB= "+subId);
Log.d(TAG, "Destination Sub = "+destSub);
Log.d(TAG, "Origin Sub = "+originSub);
if (noNetwork) {
synchronized (mRef) {
Log.e(TAG, "No network during MT operation");
decRefCount();
}
return;
}
addUnique(txnId, destSub, originSub);
// For launching NotificationTransaction and test purpose.
TransactionBundle args = new TransactionBundle(intent.getExtras());
launchTransaction(serviceId, args, noNetwork);
}
}
这里调用launchTransaction()方法;private void launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork) {
if (noNetwork) {
Log.w(TAG, "launchTransaction: no network error!");
onNetworkUnavailable(serviceId, txnBundle.getTransactionType());
return;
}
Message msg = mServiceHandler.obtainMessage(EVENT_TRANSACTION_REQUEST);
msg.arg1 = serviceId;
msg.obj = txnBundle;
if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {
Log.v(TAG, "launchTransaction: sending message " + msg);
}
mServiceHandler.sendMessage(msg);
}
调用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;
}
}
创建调用NotificationTransaction.java类中的Process()方法。
@Override
public void process() {
new Thread(this, "NotificationTransaction").start();
}
调用NotificationTransaction.java类中的run()方法,获得彩信数据(!这里需要注意的是,这里所指的下载是指的自动下载,而如果是点击下载按钮进行下载则调用的是RetrieveTransaction.java中的run()方法,或者一定时间内没有自动下载,也没有去点击下载彩信的按钮,也会走RetrieveTransaction.java中的run()方法)《TAG:2-3》
public void run() {
DownloadManager downloadManager = DownloadManager.getInstance();
boolean autoDownload = allowAutoDownload();
boolean isMemoryFull = MessageUtils.isMmsMemoryFull();
boolean isTooLarge = isMmsSizeTooLarge(mNotificationInd);
boolean isMobileDataDisabled= MessageUtils.isMobileDataDisabled(mContext);
try {
if (LOCAL_LOGV) {
Log.v(TAG, "Notification transaction launched: " + this);
}
// By default, we set status to STATUS_DEFERRED because we
// should response MMSC with STATUS_DEFERRED when we cannot
// download a MM immediately.
int status = STATUS_DEFERRED;
// Don't try to download when data is suspended, as it will fail, so defer download
if (!autoDownload || isMobileDataDisabled) {
downloadManager.markState(mUri, DownloadManager.STATE_UNSTARTED);
sendNotifyRespInd(status);
return;
}
if (isMemoryFull || isTooLarge) {
downloadManager.markState(mUri, DownloadManager.STATE_TRANSIENT_FAILURE);
sendNotifyRespInd(status);
return;
}
downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING);
if (LOCAL_LOGV) {
Log.v(TAG, "Content-Location: " + mContentLocation);
}
byte[] retrieveConfData = null;
// We should catch exceptions here to response MMSC
// with STATUS_DEFERRED.
try {
retrieveConfData = getPdu(mContentLocation);
} catch (IOException e) {
mTransactionState.setState(FAILED);
}
if (retrieveConfData != null) {
if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {
Log.v(TAG, "NotificationTransaction: retrieve data=" +
HexDump.dumpHexString(retrieveConfData));
}
GenericPdu pdu = new PduParser(retrieveConfData).parse();
if ((pdu == null) || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) {
Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU. " +
(pdu != null ? "message type: " + pdu.getMessageType() : "null pdu"));
mTransactionState.setState(FAILED);
status = STATUS_UNRECOGNIZED;
} else {
// Save the received PDU (must be a M-RETRIEVE.CONF).
PduPersister p = PduPersister.getPduPersister(mContext);
Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true,
MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext), null);
// Use local time instead of PDU time
ContentValues values = new ContentValues(2);
values.put(Mms.DATE, System.currentTimeMillis() / 1000L);
Cursor c = mContext.getContentResolver().query(mUri,
null, null, null, null);
if (c != null) {
try {
if (c.moveToFirst()) {
int subId = c.getInt(c.getColumnIndex(Mms.SUB_ID));
values.put(Mms.SUB_ID, subId);
}
} catch (Exception ex) {
Log.e(TAG, "Exception:" + ex);
} finally {
c.close();
}
}
SqliteWrapper.update(mContext, mContext.getContentResolver(),
uri, values, null, null);
// We have successfully downloaded the new MM. Delete the
// M-NotifyResp.ind from Inbox.
SqliteWrapper.delete(mContext, mContext.getContentResolver(),
mUri, null, null);
Log.v(TAG, "NotificationTransaction received new mms message: " + uri);
// Delete obsolete threads
SqliteWrapper.delete(mContext, mContext.getContentResolver(),
Threads.OBSOLETE_THREADS_URI, null, null);
// Notify observers with newly received MM.
mUri = uri;
status = STATUS_RETRIEVED;
}
}
if (LOCAL_LOGV) {
Log.v(TAG, "status=0x" + Integer.toHexString(status));
}
// Check the status and update the result state of this Transaction.
switch (status) {
case STATUS_RETRIEVED:
mTransactionState.setState(SUCCESS);
break;
case STATUS_DEFERRED:
// STATUS_DEFERRED, may be a failed immediate retrieval.
if (mTransactionState.getState() == INITIALIZED) {
mTransactionState.setState(SUCCESS);
}
break;
}
sendNotifyRespInd(status);
// Make sure this thread isn't over the limits in message count.
Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, mUri);
MmsWidgetProvider.notifyDatasetChanged(mContext);
} catch (Throwable t) {
Log.e(TAG, Log.getStackTraceString(t));
} finally {
mTransactionState.setContentUri(mUri);
if (!autoDownload || isMemoryFull || isTooLarge || isMobileDataDisabled) {
// Always mark the transaction successful for deferred
// download since any error here doesn't make sense.
mTransactionState.setState(SUCCESS);
}
if (mTransactionState.getState() != SUCCESS) {
mTransactionState.setState(FAILED);
Log.e(TAG, "NotificationTransaction failed.");
}
notifyObservers();
}
}
上述代码《TAG:2-3》中,调用Transaction.java类中的getPdu()方法下载彩信数据:
protected byte[] getPdu(String url) throws IOException {
ensureRouteToHost(url, mTransactionSettings);
return HttpUtils.httpConnection(
mContext, SendingProgressTokenManager.NO_TOKEN,
url, null, HttpUtils.HTTP_GET_METHOD,
mTransactionSettings.isProxySet(),
mTransactionSettings.getProxyAddress(),
mTransactionSettings.getProxyPort());
}
上述代码《TAG:2-3》中,调用PduParser.java类中的parse()方法解析彩信数据,PduParser类是用于把PDU字节流解析成为Android可识别的GenericPdu:《TAG:2-4》
public PduParser(byte[] pduDataStream) {
mPduDataStream = new ByteArrayInputStream(pduDataStream);
}
/**
* Parse the pdu.
*
* @return the pdu structure if parsing successfully.
* null if parsing error happened or mandatory fields are not set.
*/
public GenericPdu parse(){
if (mPduDataStream == null) {
return null;
}
/* parse headers */
mHeaders = parseHeaders(mPduDataStream);
if (null == mHeaders) {
// Parse headers failed.
return null;
}
/* get the message type */
int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
/* check mandatory header fields */
if (false == checkMandatoryHeader(mHeaders)) {
log("check mandatory headers failed!");
return null;
}
if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
/* need to parse the parts */
mBody = parseParts(mPduDataStream);
if (null == mBody) {
// Parse parts failed.
return null;
}
}
switch (messageType) {
case PduHeaders.MESSAGE_TYPE_SEND_REQ:
if (LOCAL_LOGV) {
Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");
}
SendReq sendReq = new SendReq(mHeaders, mBody);
return sendReq;
case PduHeaders.MESSAGE_TYPE_SEND_CONF:
if (LOCAL_LOGV) {
Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");
}
SendConf sendConf = new SendConf(mHeaders);
return sendConf;
case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
if (LOCAL_LOGV) {
Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");
}
NotificationInd notificationInd =
new NotificationInd(mHeaders);
return notificationInd;
case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
if (LOCAL_LOGV) {
Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
}
NotifyRespInd notifyRespInd =
new NotifyRespInd(mHeaders);
return notifyRespInd;
case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
if (LOCAL_LOGV) {
Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");
}
RetrieveConf retrieveConf =
new RetrieveConf(mHeaders, mBody);
byte[] contentType = retrieveConf.getContentType();
if (null == contentType) {
return null;
}
String ctTypeStr = new String(contentType);
if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
|| ctTypeStr.equals(ContentType.MULTIPART_RELATED)
|| ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
// The MMS content type must be "application/vnd.wap.multipart.mixed"
// or "application/vnd.wap.multipart.related"
// or "application/vnd.wap.multipart.alternative"
return retrieveConf;
} else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
// "application/vnd.wap.multipart.alternative"
// should take only the first part.
PduPart firstPart = mBody.getPart(0);
mBody.removeAll();
mBody.addPart(0, firstPart);
return retrieveConf;
}
return null;
case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
if (LOCAL_LOGV) {
Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");
}
DeliveryInd deliveryInd =
new DeliveryInd(mHeaders);
return deliveryInd;
case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
if (LOCAL_LOGV) {
Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
}
AcknowledgeInd acknowledgeInd =
new AcknowledgeInd(mHeaders);
return acknowledgeInd;
case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
if (LOCAL_LOGV) {
Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");
}
ReadOrigInd readOrigInd =
new ReadOrigInd(mHeaders);
return readOrigInd;
case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
if (LOCAL_LOGV) {
Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");
}
ReadRecInd readRecInd =
new ReadRecInd(mHeaders);
return readRecInd;
default:
log("Parser doesn't support this message type in this version!");
return null;
}
}上述代码中《TAG2-4》中调用parseParts()方法解析pdupart:
protected static PduBody parseParts(ByteArrayInputStream pduDataStream) {
if (pduDataStream == null) {
return null;
}
int count = parseUnsignedInt(pduDataStream); // get the number of parts
PduBody body = new PduBody();
for (int i = 0 ; i < count ; i++) {
int headerLength = parseUnsignedInt(pduDataStream);
int dataLength = parseUnsignedInt(pduDataStream);
PduPart part = new PduPart();
int startPos = pduDataStream.available();
if (startPos <= 0) {
// Invalid part.
return null;
}
/* parse part's content-type */
HashMap<Integer, Object> map = new HashMap<Integer, Object>();
byte[] contentType = parseContentType(pduDataStream, map);
if (null != contentType) {
part.setContentType(contentType);
} else {
part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
}
/* get name parameter */
byte[] name = (byte[]) map.get(PduPart.P_NAME);
if (null != name) {
part.setName(name);
}
/* get charset parameter */
Integer charset = (Integer) map.get(PduPart.P_CHARSET);
if (null != charset) {
part.setCharset(charset);
}
/* parse part's headers */
int endPos = pduDataStream.available();
int partHeaderLen = headerLength - (startPos - endPos);
if (partHeaderLen > 0) {
if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
// Parse part header faild.
return null;
}
} else if (partHeaderLen < 0) {
// Invalid length of content-type.
return null;
}
/* FIXME: check content-id, name, filename and content location,
* if not set anyone of them, generate a default content-location
*/
if ((null == part.getContentLocation())
&& (null == part.getName())
&& (null == part.getFilename())
&& (null == part.getContentId())) {
part.setContentLocation(Long.toOctalString(
System.currentTimeMillis()).getBytes());
}
/* get part's data */
if (dataLength > 0) {
byte[] partData = new byte[dataLength];
String partContentType = new String(part.getContentType());
pduDataStream.read(partData, 0, dataLength);
if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {
// parse "multipart/vnd.wap.multipart.alternative".
PduBody childBody = parseParts(new ByteArrayInputStream(partData));
// take the first part of children.
part = childBody.getPart(0);
} else {
// Check Content-Transfer-Encoding.
byte[] partDataEncoding = part.getContentTransferEncoding();
if (null != partDataEncoding) {
String encoding = new String(partDataEncoding);
if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
// Decode "base64" into "binary".
partData = Base64.decodeBase64(partData);
} else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
// Decode "quoted-printable" into "binary".
partData = QuotedPrintable.decodeQuotedPrintable(partData);
} else {
// "binary" is the default encoding.
}
}
if (null == partData) {
log("Decode part data error!");
return null;
}
part.setData(partData);
}
}
/* add this part to body */
if (THE_FIRST_PART == checkPartPosition(part)) {
/* this is the first part */
body.addPart(0, part);
} else {
/* add the part to the end */
body.addPart(part);
}
}
return body;
}
上述代码《TAG:2-3》中,调用PduPersister.java类中的persist()方法;《TAG:2-5》PduPersister类用于管理PDU存储,为什么会要把PDU的存储也封装成PduPersister呢?因为PDU的存储方式 是放在标准的SQLiteDatabase中,通过TelephonyProvider,而SQLiteDatabase中存储不能以直接的PDU的字节流来存储,必须要把PDU拆解成为可读的字段,因此在存储PDU和从存储加载PDU的过程 中涉及到PDU数据上面的处理,因此封装出来,更方便使用。其中persist(GenericPdu, Uri)方法把一个GenericPdu保存到Uri所指定的数据库中,返回指向新生成数据的Uri;load(Uri)方法从数据库把Uri所指的数据加载出来成一个GenericPdu对象;move(Uri, Uri)方法把Pdu从一个地方移到另一个地方,比如从草稿箱移动到发件箱,当MMS已发送时。
public Uri persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled,
HashMap<Uri, InputStream> preOpenedFiles)
throws MmsException {
if (uri == null) {
throw new MmsException("Uri may not be null.");
}
long msgId = -1;
try {
msgId = ContentUris.parseId(uri);
} catch (NumberFormatException e) {
// the uri ends with "inbox" or something else like that
}
boolean existingUri = msgId != -1;
if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) {
throw new MmsException(
"Bad destination, must be one of "
+ "content://mms/inbox, content://mms/sent, "
+ "content://mms/drafts, content://mms/outbox, "
+ "content://mms/temp.");
}
synchronized(PDU_CACHE_INSTANCE) {
// If the cache item is getting updated, wait until it's done updating before
// purging it.
if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
if (LOCAL_LOGV) {
Log.v(TAG, "persist: " + uri + " blocked by isUpdating()");
}
try {
PDU_CACHE_INSTANCE.wait();
} catch (InterruptedException e) {
Log.e(TAG, "persist1: ", e);
}
}
}
PDU_CACHE_INSTANCE.purge(uri);
PduHeaders header = pdu.getPduHeaders();
PduBody body = null;
ContentValues values = new ContentValues();
Set<Entry<Integer, String>> set;
set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet();
for (Entry<Integer, String> e : set) {
int field = e.getKey();
EncodedStringValue encodedString = header.getEncodedStringValue(field);
if (encodedString != null) {
String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
values.put(e.getValue(), toIsoString(encodedString.getTextString()));
values.put(charsetColumn, encodedString.getCharacterSet());
}
}
set = TEXT_STRING_COLUMN_NAME_MAP.entrySet();
for (Entry<Integer, String> e : set){
byte[] text = header.getTextString(e.getKey());
if (text != null) {
values.put(e.getValue(), toIsoString(text));
}
}
set = OCTET_COLUMN_NAME_MAP.entrySet();
for (Entry<Integer, String> e : set){
int b = header.getOctet(e.getKey());
if (b != 0) {
values.put(e.getValue(), b);
}
}
set = LONG_COLUMN_NAME_MAP.entrySet();
for (Entry<Integer, String> e : set){
long l = header.getLongInteger(e.getKey());
if (l != -1L) {
values.put(e.getValue(), l);
}
}
HashMap<Integer, EncodedStringValue[]> addressMap =
new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
// Save address information.
for (int addrType : ADDRESS_FIELDS) {
EncodedStringValue[] array = null;
if (addrType == PduHeaders.FROM) {
EncodedStringValue v = header.getEncodedStringValue(addrType);
if (v != null) {
array = new EncodedStringValue[1];
array[0] = v;
}
} else {
array = header.getEncodedStringValues(addrType);
}
addressMap.put(addrType, array);
}
HashSet<String> recipients = new HashSet<String>();
int msgType = pdu.getMessageType();
// Here we only allocate thread ID for M-Notification.ind,
// M-Retrieve.conf and M-Send.req.
// Some of other PDU types may be allocated a thread ID outside
// this scope.
if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
|| (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
|| (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
switch (msgType) {
case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
loadRecipients(PduHeaders.FROM, recipients, addressMap, false);
// For received messages when group MMS is enabled, we want to associate this
// message with the thread composed of all the recipients -- all but our own
// number, that is. This includes the person who sent the
// message or the FROM field (above) in addition to the other people the message
// was addressed to or the TO field. Our own number is in that TO field and
// we have to ignore it in loadRecipients.
if (groupMmsEnabled) {
loadRecipients(PduHeaders.TO, recipients, addressMap, true);
}
break;
case PduHeaders.MESSAGE_TYPE_SEND_REQ:
loadRecipients(PduHeaders.TO, recipients, addressMap, false);
break;
}
long threadId = 0;
if (createThreadId && !recipients.isEmpty()) {
// Given all the recipients associated with this message, find (or create) the
// correct thread.
threadId = Threads.getOrCreateThreadId(mContext, recipients);
}
values.put(Mms.THREAD_ID, threadId);
}
// Save parts first to avoid inconsistent message is loaded
// while saving the parts.
long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
// Figure out if this PDU is a text-only message
boolean textOnly = true;
// Get body if the PDU is a RetrieveConf or SendReq.
if (pdu instanceof MultimediaMessagePdu) {
body = ((MultimediaMessagePdu) pdu).getBody();
// Start saving parts if necessary.
if (body != null) {
int partsNum = body.getPartsNum();
if (partsNum > 2) {
// For a text-only message there will be two parts: 1-the SMIL, 2-the text.
// Down a few lines below we're checking to make sure we've only got SMIL or
// text. We also have to check then we don't have more than two parts.
// Otherwise, a slideshow with two text slides would be marked as textOnly.
textOnly = false;
}
for (int i = 0; i < partsNum; i++) {
PduPart part = body.getPart(i);
persistPart(part, dummyId, preOpenedFiles);
// If we've got anything besides text/plain or SMIL part, then we've got
// an mms message with some other type of attachment.
String contentType = getPartContentType(part);
if (contentType != null && !ContentType.APP_SMIL.equals(contentType)
&& !ContentType.TEXT_PLAIN.equals(contentType)) {
textOnly = false;
}
}
}
}
// Record whether this mms message is a simple plain text or not. This is a hint for the
// UI.
values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0);
Uri res = null;
if (existingUri) {
res = uri;
SqliteWrapper.update(mContext, mContentResolver, res, values, null, null);
} else {
res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
if (res == null) {
throw new MmsException("persist() failed: return null.");
}
// Get the real ID of the PDU and update all parts which were
// saved with the dummy ID.
msgId = ContentUris.parseId(res);
}
values = new ContentValues(1);
values.put(Part.MSG_ID, msgId);
SqliteWrapper.update(mContext, mContentResolver,
Uri.parse("content://mms/" + dummyId + "/part"),
values, null, null);
// We should return the longest URI of the persisted PDU, for
// example, if input URI is "content://mms/inbox" and the _ID of
// persisted PDU is '8', we should return "content://mms/inbox/8"
// instead of "content://mms/8".
// FIXME: Should the MmsProvider be responsible for this???
if (!existingUri) {
res = Uri.parse(uri + "/" + msgId);
}
// Save address information.
for (int addrType : ADDRESS_FIELDS) {
EncodedStringValue[] array = addressMap.get(addrType);
if (array != null) {
persistAddress(msgId, addrType, array);
}
}
return res;
}
上述代码《TAG:2-5》中,调用PduPersister.java类中的()方法对彩信进行持久化,该方法中调用SqliteWrapper类的insert方法将数据存入数据库mmssms的part表中:《TAG:2-6》public Uri persistPart(PduPart part, long msgId, HashMap<Uri, InputStream> preOpenedFiles)
throws MmsException {
Uri uri = Uri.parse("content://mms/" + msgId + "/part");
ContentValues values = new ContentValues(8);
int charset = part.getCharset();
if (charset != 0 ) {
values.put(Part.CHARSET, charset);
}
String contentType = getPartContentType(part);
if (contentType != null) {
// There is no "image/jpg" in Android (and it's an invalid mimetype).
// Change it to "image/jpeg"
if (ContentType.IMAGE_JPG.equals(contentType)) {
contentType = ContentType.IMAGE_JPEG;
}
values.put(Part.CONTENT_TYPE, contentType);
// To ensure the SMIL part is always the first part.
if (ContentType.APP_SMIL.equals(contentType)) {
values.put(Part.SEQ, -1);
}
} else {
throw new MmsException("MIME type of the part must be set.");
}
if (part.getFilename() != null) {
String fileName = new String(part.getFilename());
values.put(Part.FILENAME, fileName);
}
if (part.getName() != null) {
String name = new String(part.getName());
values.put(Part.NAME, name);
}
String value = null;
int encode=-1;
if (part.getContentDisposition() != null) {
value = toIsoString(part.getContentDisposition());
values.put(Part.CONTENT_DISPOSITION,value);
}
if (part.getContentId() != null) {
byte[] byte_cid=part.getContentId();
encode=detectEncoding(byte_cid);
try{
switch(encode){
case GB2312:
value=new String(byte_cid,"GB2312");
break;
case ASCII:
value=new String(byte_cid,"ASCII");
break;
case UTF8:
value=new String(byte_cid,"UTF-8");
break;
case UNICODE:
value=new String(byte_cid,"Unicode");
break;
default:
value = toIsoString(byte_cid);
break;
}
Log.d("bill","getContentId---"+value);
values.put(Part.CONTENT_ID, value);}catch(Exception e){}
}
if (part.getContentLocation() != null) {
byte[] byte_cl=part.getContentLocation();
encode=detectEncoding(byte_cl);
try{
switch(encode){
case GB2312:
value=new String(byte_cl,"GB2312");
break;
case ASCII:
value=new String(byte_cl,"ASCII");
break;
case UTF8:
value=new String(byte_cl,"UTF-8");
break;
case UNICODE:
value=new String(byte_cl,"Unicode");
break;
default:
value = toIsoString(byte_cl);
break;
}
Log.d("bill","getContentLocation---"+value);
values.put(Part.CONTENT_LOCATION,value);}catch(Exception e){}
}
Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
if (res == null) {
throw new MmsException("Failed to persist part, return null.");
}
persistData(part, res, contentType, preOpenedFiles);
// After successfully store the data, we should update
// the dataUri of the part.
part.setDataUri(res);
return res;
}我们接着分析,持久会存储之后会调用<TAG 2-1>中的PushReceiver.java类中的doInBackground()方法来启动TransactionService服务;