今天在找GPRS的源代码的时候居然找到了SMS的源代码,那么就写个短信发送功能,并来解释下源代码吧(自己的理解,求别喷)。
首先来个短信发送的小程序吧
String number = "110"; String content = "搞死日本人"; SmsManager smsManager = SmsManager.getDefault(); PendingIntent sentIntent = PendingIntent.getBroadcast(MainActivity.this, 0, new Intent(), 0); //如果字数超过70,需拆分成多条短信发送 if (strContent.length() > 70) { List<String> msgs = smsManager.divideMessage(content); for (String msg : msgs) { smsManager.sendTextMessage(number, null, msg, sentIntent, null); } } else { smsManager.sendTextMessage(number, null, content, sentIntent, null); }
下面来解释一下这里面用到的两个关键的函数:sendTextMessage 和 divideMessage.
首先来看sendTextMessage:存放于ANDROID.2.3.3/frameworks/base/telephony/java/android/telephony
/** * Send a text based SMS. * * @param destinationAddress the address to send the message to * @param scAddress is the service center address or null to use * the current default SMSC * @param text the body of the message to send * @param sentIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is sucessfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors:<br> * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> * <code>RESULT_ERROR_RADIO_OFF</code><br> * <code>RESULT_ERROR_NULL_PDU</code><br> * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include * the extra "errorCode" containing a radio technology specific value, * generally only useful for troubleshooting.<br> * The per-application based SMS control checks sentIntent. If sentIntent * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is delivered to the recipient. The * raw pdu of the status report is in the extended data ("pdu"). * * @throws IllegalArgumentException if destinationAddress or text are empty */ public void sendTextMessage( String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) { if (TextUtils.isEmpty(destinationAddress)) { throw new IllegalArgumentException("Invalid destinationAddress"); } if (TextUtils.isEmpty(text)) { throw new IllegalArgumentException("Invalid message body"); } try { ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); if (iccISms != null) { iccISms.sendText(destinationAddress, scAddress, text, sentIntent, deliveryIntent); } } catch (RemoteException ex) { // ignore it } }
这里面最关键的就是
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
这句话就是获得一个ISms接口的一个实例,然后可以去获得isms服务
找到ISms.aidl(aidl是 Android Interface definition language的缩写,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口。关于这方面的知识请查看file:///usr/local/dev/android-sdk-linux/docs/guide/developing/tools/aidl.html),存放于ANDROID.2.3.3/frameworks/base/telephony/java/com/android/internal/telephony/
/** Interface for applications to access the ICC phone book. * * <p>The following code snippet demonstrates a static method to * retrieve the ISms interface from Android:</p> * <pre>private static ISms getSmsInterface() throws DeadObjectException { IServiceManager sm = ServiceManagerNative.getDefault(); ISms ss; ss = ISms.Stub.asInterface(sm.getService("isms")); return ss; } * </pre> */ interface ISms { /** * Send an SMS. * * @param smsc the SMSC to send the message through, or NULL for the * default SMSC * @param text the body of the message to send * @param sentIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is sucessfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors:<br> * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> * <code>RESULT_ERROR_RADIO_OFF</code><br> * <code>RESULT_ERROR_NULL_PDU</code><br> * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include * the extra "errorCode" containing a radio technology specific value, * generally only useful for troubleshooting.<br> * The per-application based SMS control checks sentIntent. If sentIntent * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is delivered to the recipient. The * raw pdu of the status report is in the extended data ("pdu"). */ void sendText(in String destAddr, in String scAddr, in String text, in PendingIntent sentIntent, in PendingIntent deliveryIntent); }
而sendtext函数就是这个,存放于IccSmsInterfaceManager.java
public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) { mPhone.getContext().enforceCallingPermission( "android.permission.SEND_SMS", "Sending SMS message"); if (Log.isLoggable("SMS", Log.VERBOSE)) { log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr + " text='"+ text + "' sentIntent=" + sentIntent + " deliveryIntent=" + deliveryIntent); } mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent); }
原来调用的是mDispatcher里面的sendText,再去看SMSDispather.java文件
protected abstract void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent);
擦,一个虚函数,实现在哪??继续找吧
GsmSMSDispatcher.java
final class GsmSMSDispatcher extends SMSDispatcher { ... ... ... /** {@inheritDoc} */ protected void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) { SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( scAddr, destAddr, text, (deliveryIntent != null)); sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent); } ... ... }
先看下getSubmitPdu函数吧
/** * Get an SMS-SUBMIT PDU for a destination address and a message * * @param scAddress Service Centre address. Null means use default. * @return a <code>SubmitPdu</code> containing the encoded SC * address, if applicable, and the encoded message. * Returns null on encode error. */ public static SubmitPdu getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested) { SubmitPduBase spb; int activePhone = TelephonyManager.getDefault().getPhoneType(); if (PHONE_TYPE_CDMA == activePhone) { spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null); } else { spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested); } return new SubmitPdu(spb); }
再来看下sendRawPdu,注意,现在不是sendText了
protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent, PendingIntent deliveryIntent) { if (pdu == null) { if (sentIntent != null) { try { sentIntent.send(RESULT_ERROR_NULL_PDU); } catch (CanceledException ex) {} } return; } HashMap<String, Object> map = new HashMap<String, Object>(); map.put("smsc", smsc); map.put("pdu", pdu); SmsTracker tracker = new SmsTracker(map, sentIntent, deliveryIntent); int ss = mPhone.getServiceState().getState(); if (ss != ServiceState.STATE_IN_SERVICE) { handleNotInService(ss, tracker); } else { String appName = getAppNameByIntent(sentIntent); if (mCounter.check(appName, SINGLE_PART_SMS)) { sendSms(tracker); } else { sendMessage(obtainMessage(EVENT_POST_ALERT, tracker)); } } }
protected void sendSms(SmsTracker tracker) { HashMap map = tracker.mData; byte smsc[] = (byte[]) map.get("smsc"); byte pdu[] = (byte[]) map.get("pdu"); Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker); mCm.sendSMS(IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), reply); }
mCm是什么呢?
sendSMS (String smscPDU, String pdu, Message result) { RILRequest rr = RILRequest.obtain(RIL_REQUEST_SEND_SMS, result); rr.mp.writeInt(2); rr.mp.writeString(smscPDU); rr.mp.writeString(pdu); if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); send(rr); }
同样在RIL.java文件安中找到 send(RILRequest rr)
private void send(RILRequest rr) { Message msg; msg = mSender.obtainMessage(EVENT_SEND, rr); acquireWakeLock(); msg.sendToTarget(); }
再根据handle,同样在RIL.java中找到
class RILSender extends Handler implements Runnable { public RILSender(Looper looper) { super(looper); } // Only allocated once byte[] dataLength = new byte[4]; //***** Runnable implementation public void run() { //setup if needed } //***** Handler implemementation public void handleMessage(Message msg) { RILRequest rr = (RILRequest)(msg.obj); RILRequest req = null; switch (msg.what) { case EVENT_SEND: /** * mRequestMessagePending++ already happened for every * EVENT_SEND, thus we must make sure * mRequestMessagePending-- happens once and only once */ boolean alreadySubtracted = false; try { LocalSocket s; s = mSocket; if (s == null) { rr.onError(RADIO_NOT_AVAILABLE, null); rr.release(); if (mRequestMessagesPending > 0) mRequestMessagesPending--; alreadySubtracted = true; return; } synchronized (mRequestsList) { mRequestsList.add(rr); mRequestMessagesWaiting++; } if (mRequestMessagesPending > 0) mRequestMessagesPending--; alreadySubtracted = true; byte[] data; data = rr.mp.marshall(); rr.mp.recycle(); rr.mp = null; if (data.length > RIL_MAX_COMMAND_BYTES) { throw new RuntimeException( "Parcel larger than max bytes allowed! " + data.length); } // parcel length in big endian dataLength[0] = dataLength[1] = 0; dataLength[2] = (byte)((data.length >> 8) & 0xff); dataLength[3] = (byte)((data.length) & 0xff); //Log.v(LOG_TAG, "writing packet: " + data.length + " bytes"); s.getOutputStream().write(dataLength); s.getOutputStream().write(data); } catch (IOException ex) { Log.e(LOG_TAG, "IOException", ex); req = findAndRemoveRequestFromList(rr.mSerial); // make sure this request has not already been handled, // eg, if RILReceiver cleared the list. if (req != null || !alreadySubtracted) { rr.onError(RADIO_NOT_AVAILABLE, null); rr.release(); } } catch (RuntimeException exc) { Log.e(LOG_TAG, "Uncaught exception ", exc); req = findAndRemoveRequestFromList(rr.mSerial); // make sure this request has not already been handled, // eg, if RILReceiver cleared the list. if (req != null || !alreadySubtracted) { rr.onError(GENERIC_FAILURE, null); rr.release(); } } if (!alreadySubtracted && mRequestMessagesPending > 0) { mRequestMessagesPending--; } break;
关键代码:
|
s.getOutputStream().write(dataLength);
s.getOutputStream().write(data);
这样就通过socket把内容发出,LocalSocket.java存放于frameworks/base/core/java/android/net
LocalSocketImpl impl; public LocalSocket() { this(new LocalSocketImpl()); isBound = false; isConnected = false; } /** * Retrieves the output stream for this instance. * * @return output stream * @throws IOException if socket has been closed or cannot be created. */ public OutputStream getOutputStream() throws IOException { implCreateIfNeeded(); return impl.getOutputStream(); } /** * It's difficult to discern from the spec when impl.create() should be * called, but it seems like a reasonable rule is "as soon as possible, * but not in a context where IOException cannot be thrown" * * @throws IOException from SocketImpl.create() */ private void implCreateIfNeeded() throws IOException { if (!implCreated) { synchronized (this) { if (!implCreated) { try { impl.create(true); } finally { implCreated = true; } } } } }
LocalSocketImpl.jva同样存放于net文件夹
关键的部分要到来了
/** {@inheritDoc} */ @Override public void write (byte[] b, int off, int len) throws IOException { synchronized (writeMonitor) { FileDescriptor myFd = fd; if (myFd == null) throw new IOException("socket closed"); if (off < 0 || len < 0 || (off + len) > b.length ) { throw new ArrayIndexOutOfBoundsException(); } writeba_native(b, off, len, myFd); } }
看见没?
writeba_native(b, off, len, myFd);
在base/core/jni/android_net_LocalSocketImpl.cpp
JNI中发现调用的就是{"writeba_native", "([BIILjava/io/FileDescriptor;)V", (void*) socket_writeba},而socket_writeba就是调用的下面这个函数:
static int socket_write_all(JNIEnv *env, jobject object, int fd, void *buf, size_t len) { ssize_t ret; struct msghdr msg; unsigned char *buffer = (unsigned char *)buf; memset(&msg, 0, sizeof(msg)); jobjectArray outboundFds = (jobjectArray)env->GetObjectField( object, field_outboundFileDescriptors); if (env->ExceptionOccurred() != NULL) { return -1; } struct cmsghdr *cmsg; int countFds = outboundFds == NULL ? 0 : env->GetArrayLength(outboundFds); int fds[countFds]; char msgbuf[CMSG_SPACE(countFds)]; // Add any pending outbound file descriptors to the message if (outboundFds != NULL) { if (env->ExceptionOccurred() != NULL) { return -1; } for (int i = 0; i < countFds; i++) { jobject fdObject = env->GetObjectArrayElement(outboundFds, i); if (env->ExceptionOccurred() != NULL) { return -1; } fds[i] = jniGetFDFromFileDescriptor(env, fdObject); if (env->ExceptionOccurred() != NULL) { return -1; } } // See "man cmsg" really msg.msg_control = msgbuf; msg.msg_controllen = sizeof msgbuf; cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof fds); memcpy(CMSG_DATA(cmsg), fds, sizeof fds); } // We only write our msg_control during the first write while (len > 0) { struct iovec iv; memset(&iv, 0, sizeof(iv)); iv.iov_base = buffer; iv.iov_len = len; msg.msg_iov = &iv; msg.msg_iovlen = 1; do { ret = sendmsg(fd, &msg, MSG_NOSIGNAL); } while (ret < 0 && errno == EINTR); if (ret < 0) { jniThrowIOException(env, errno); return -1; } buffer += ret; len -= ret; // Wipes out any msg_control too memset(&msg, 0, sizeof(msg)); } return 0; }
终于找到了,原来就是linux下的ssize_t sendmsg这个系统调用!!!!!
再来看divideMessage
/** * Divide a message text into several fragments, none bigger than * the maximum SMS message size. * * @param text the original message. Must not be null. * @return an <code>ArrayList</code> of strings that, in order, * comprise the original message */ public ArrayList<String> divideMessage(String text) { return SmsMessage.fragmentText(text); }
而这个函数就主要调用里fragmentText这个方法
/** * Divide a message text into several fragments, none bigger than * the maximum SMS message text size. * * @param text text, must not be null. * @return an <code>ArrayList</code> of strings that, in order, * comprise the original msg text * * @hide */ public static ArrayList<String> fragmentText(String text) { int activePhone = TelephonyManager.getDefault().getPhoneType(); TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ? com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false) : com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false); // TODO(cleanup): The code here could be rolled into the logic // below cleanly if these MAX_* constants were defined more // flexibly... int limit; if (ted.msgCount > 1) { limit = (ted.codeUnitSize == ENCODING_7BIT) ? MAX_USER_DATA_SEPTETS_WITH_HEADER : MAX_USER_DATA_BYTES_WITH_HEADER; } else { limit = (ted.codeUnitSize == ENCODING_7BIT) ? MAX_USER_DATA_SEPTETS : MAX_USER_DATA_BYTES; } int pos = 0; // Index in code units. int textLen = text.length(); ArrayList<String> result = new ArrayList<String>(ted.msgCount); while (pos < textLen) { int nextPos = 0; // Counts code units. if (ted.codeUnitSize == ENCODING_7BIT) { if (activePhone == PHONE_TYPE_CDMA && ted.msgCount == 1) { // For a singleton CDMA message, the encoding must be ASCII... nextPos = pos + Math.min(limit, textLen - pos); } else { // For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode). nextPos = GsmAlphabet.findGsmSeptetLimitIndex(text, pos, limit); } } else { // Assume unicode. nextPos = pos + Math.min(limit / 2, textLen - pos); } if ((nextPos <= pos) || (nextPos > textLen)) { Log.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " + nextPos + " >= " + textLen + ")"); break; } result.add(text.substring(pos, nextPos)); pos = nextPos; } return result; }
其实我觉得发送短信这个功能最重要的还是去学习下关于aidl方面的知识,进程间通信很重要,尤其是我们这些刚接触android不久的经验少的孩子。自己的理解很狭隘,能力也有限,只是做个抛砖引玉的效果,希望大家一起来分享自己的心得。