由于前一篇已经介绍了启动TransactionService之前的主要内容,本篇主要介绍TransactionService处理彩信业务的主要逻辑流程。
TransactionService,与短信的SmsReceiverService类似,是负责处理彩信的服务,可以发送,接收等。对于TransactionService来讲,所有的需要处理的流程,无论是发送还是接收,都是一个Transaction。它内部有二个队列,一个是当前正在处理(processing)的Transaction,一个是待处理(pending)的Transaction。它维护这二个队列,并检查网络的连接,打开彩信网络连接,准备和检查环境,然后从待处理的队列中取出第一个,放入正在处理的队列中,并处理这个Transaction,也就是调用Transaction.process()。
发送彩信是一个SendTransaction,它的process()方法负责发送彩信,它会创建一个独立的线程来做,因此不会阻塞TransactionService,处理服务就可以再处理其他的Transaction。它会先从数据库中取出彩信Pdu,M-Send.req,(SendReq),更新一些字段,比如date,然后调用其父类Transaction.java中的方法sendPdu来把SendReq发送出去,sendPdu()会返回发送的结果(send confirmation)。Transaction.sendPdu()会先设置好网路,然后直接调用HttpUtils中的httpConnection()方法,用HTTP把彩信发送出去,同时取得返回消息(Response)给SendTransaction。SendTransaction会检查发送结果,返回结果(Send Confirmation),分析状态并更新至数据库(比如发送失败或发送成功)。UI会监听到状态变化,并更新信息列表。
首先,先简单介绍一下方法流程:onCreate()
->onStartCommand()//使用handler发送消息【EVENT_NEW_INTENT】
->onNewIntent()
-> launchTransaction ()
-> sendMessage()【ServiceHandler.java<sub>】【EVENT_TRANSACTION_REQUEST】
->handlemessage ()【ServiceHandler.java<sub>】【EVENT_TRANSACTION_REQUEST】
-> new SendTransaction()【Transaction.SEND_TRANSACTION】
- >processTransaction(transaction)
->process();【SendTransaction.java】<注意,标红处查看代码 getTransactionType case PduHeaders.MESSAGE_TYPE_SEND_REQ: return Transaction.SEND_TRANSACTION>
-> run()【SendTransaction.java】
-> sendPdu ()【Transcation.java】
-> HttpUtils.httpConnection()
这里从服务的生命周期函数onStartCommand()开始进行分析;
@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;
}
@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;
在上述方法中,使用handler发送了一个消息并在处理消息的方法中调用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();
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);
}
}
调用lunchTransaction()方法启动事务来发送消息:
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());
}
}
......
case Transaction.SEND_TRANSACTION:
transaction = new SendTransaction(
TransactionService.this, serviceId,
transactionSettings, args.getUri());
break;
......
上述方法中提到了TransactionSettings,它是对于一个处理流程的相关配置信息,里面含有MMSC(Multimedia Message Service Center),Proxy和ProxyPort。这些信息,特别对于发送和接收来说是十分重要的。因为对于手机的信息,并不是手机直接把信息发送到接收人的手机上,而是直接发给服务中心,后面就是由服务中心再把信息发送给对应的接收人的手机上。对于彩信也是这样,HttpUtils通过HTTP协议把彩信发送给MMSC,它是一个URL地址,之后对于发送方来讲,彩信就发送完了,彩信服务中心(MMSC)会处理接下来的发送过程,服务中心是与手机运营相关的,它由运营商来提供。对于Mms发送彩信,是不会特意指定TransactionSettings的,也就是说它不会指定MMSC和Proxy,那么TransactionService就会用系统默认的MMSC,Proxy作为TranscationSetting,MMSC,Proxy和ProxyPort需要从Telephony数据库中查询出来,它们是与具体手机的APN设置和具体的运营商相关。所以,这里如果想要改变彩信的配置信息,只能更改APN系统设置来完成。
而短信的发送就不涉及SMSC(短信服务中心),因为Frameworks中的工具已经封装好了SmsManager提供了几个发送短信的方法,可能它会去处理SMSC相关的东西。
然后接着调用了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;
}
}
接着调用了SendTransaction.java类的process()方法和run()方法:
@Override
public void process() {
mThread = new Thread(this, "SendTransaction");
mThread.start();
}
public void run() {
try {
RateController rateCtlr = RateController.getInstance();
if (rateCtlr.isLimitSurpassed() && !rateCtlr.isAllowedByUser()) {
Log.e(TAG, "Sending rate limit surpassed.");
return;
}
// Load M-Send.req from outbox
PduPersister persister = PduPersister.getPduPersister(mContext);
SendReq sendReq = (SendReq) persister.load(mSendReqURI);
// Update the 'date' field of the PDU right before sending it.
long date = System.currentTimeMillis() / 1000L;
sendReq.setDate(date);
// Persist the new date value into database.
ContentValues values = new ContentValues(1);
values.put(Mms.DATE, date);
SqliteWrapper.update(mContext, mContext.getContentResolver(),
mSendReqURI, values, null, null);
// fix bug 2100169: insert the 'from' address per spec
String lineNumber;
if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) {
lineNumber = MessageUtils.getLocalNumber(
MultiSimUtility.getCurrentDataSubscription(mContext));
Log.d(TAG, "lineNumber " + lineNumber);
} else {
lineNumber = MessageUtils.getLocalNumber();
}
if (!TextUtils.isEmpty(lineNumber)) {
sendReq.setFrom(new EncodedStringValue(lineNumber));
}
// Pack M-Send.req, send it, retrieve confirmation data, and parse it
long tokenKey = ContentUris.parseId(mSendReqURI);
byte[] response = sendPdu(SendingProgressTokenManager.get(tokenKey),
new PduComposer(mContext, sendReq).make());
SendingProgressTokenManager.remove(tokenKey);
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
String respStr = new String(response);
Log.d(TAG, "[SendTransaction] run: send mms msg (" + mId + "), resp=" + respStr);
}
SendConf conf = (SendConf) new PduParser(response).parse();
if (conf == null) {
Log.e(TAG, "No M-Send.conf received.");
}
// Check whether the responding Transaction-ID is consistent
// with the sent one.
byte[] reqId = sendReq.getTransactionId();
byte[] confId = conf.getTransactionId();
if (!Arrays.equals(reqId, confId)) {
Log.e(TAG, "Inconsistent Transaction-ID: req="
+ new String(reqId) + ", conf=" + new String(confId));
return;
}
// From now on, we won't save the whole M-Send.conf into
// our database. Instead, we just save some interesting fields
// into the related M-Send.req.
values = new ContentValues(2);
int respStatus = conf.getResponseStatus();
values.put(Mms.RESPONSE_STATUS, respStatus);
if (respStatus != PduHeaders.RESPONSE_STATUS_OK) {
SqliteWrapper.update(mContext, mContext.getContentResolver(),
mSendReqURI, values, null, null);
Log.e(TAG, "Server returned an error code: " + respStatus);
return;
}
String messageId = PduPersister.toIsoString(conf.getMessageId());
values.put(Mms.MESSAGE_ID, messageId);
SqliteWrapper.update(mContext, mContext.getContentResolver(),
mSendReqURI, values, null, null);
// Move M-Send.req from Outbox into Sent.
Uri uri = persister.move(mSendReqURI, Sent.CONTENT_URI);
mTransactionState.setState(TransactionState.SUCCESS);
mTransactionState.setContentUri(uri);
} catch (Throwable t) {
Log.e(TAG, Log.getStackTraceString(t));
} finally {
if (mTransactionState.getState() != TransactionState.SUCCESS) {
mTransactionState.setState(TransactionState.FAILED);
mTransactionState.setContentUri(mSendReqURI);
Log.e(TAG, "Delivery failed.");
}
notifyObservers();
}
}
接着调用了sendPdu()方法通过http协议将彩信发送给SMSC;
protected byte[] sendPdu(long token, byte[] pdu,
String mmscUrl) throws IOException, MmsException {
if (pdu == null) {
throw new MmsException();
}
ensureRouteToHost(mmscUrl, mTransactionSettings);
return HttpUtils.httpConnection(
mContext, token,
mmscUrl,
pdu, HttpUtils.HTTP_POST_METHOD,
mTransactionSettings.isProxySet(),
mTransactionSettings.getProxyAddress(),
mTransactionSettings.getProxyPort());
}
总结,可以看出数据库在信息的发送过程中扮演了重要的角色,当信息离开编辑器后就马上写入了数据库,发送过程中的各个类都是先从数据库中加载信息,然后做相应处理,然后写回数据库或是更新状态,然后再交由下一个流程来处理。而所谓的Pending Message Queue其实没有相应的数据结构,它们都是数据库中的信息且状态是待发送而已。所以信息离开编辑器后就被写入了数据库,只不过状态一直在改变,从发送中到已发送,或发送失败,或如果Telephony服务不可用会仍处在待发送,但对于UI页面来讲可能没有那么多状态,它可能只显示发送中,已发送和发送失败。