博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
试着从java到C去理解android中发送短信的源代码
阅读量:7003 次
发布时间:2019-06-27

本文共 20444 字,大约阅读时间需要 68 分钟。

hot3.png

     今天在找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
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 PendingIntent is     *  broadcast when the message is sucessfully sent, or failed.     *  The result code will be Activity.RESULT_OK for success,     *  or one of these errors:
* RESULT_ERROR_GENERIC_FAILURE
* RESULT_ERROR_RADIO_OFF
* RESULT_ERROR_NULL_PDU
* For RESULT_ERROR_GENERIC_FAILURE the sentIntent may include * the extra "errorCode" containing a radio technology specific value, * generally only useful for troubleshooting.
* 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 PendingIntent 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. * * 

The following code snippet demonstrates a static method to * retrieve the ISms interface from Android:

*
private static ISms getSmsInterface()            throws DeadObjectException {    IServiceManager sm = ServiceManagerNative.getDefault();    ISms ss;    ss = ISms.Stub.asInterface(sm.getService("isms"));    return ss;} * 
*/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 PendingIntent is * broadcast when the message is sucessfully sent, or failed. * The result code will be Activity.RESULT_OK for success, * or one of these errors:
* RESULT_ERROR_GENERIC_FAILURE
* RESULT_ERROR_RADIO_OFF
* RESULT_ERROR_NULL_PDU
* For RESULT_ERROR_GENERIC_FAILURE the sentIntent may include * the extra "errorCode" containing a radio technology specific value, * generally only useful for troubleshooting.
* 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 PendingIntent 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 SubmitPdu 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
map = new HashMap
(); 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)); } } }

关键是那个sendSms函数,然后在SMSDispathcer里面找到sendSms也是一个虚函数,然后在GsmSMSDispatcher.java里面找到实现
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是什么呢?
protected CommandsInterface mCm; 接口再次出现!这个接口与RIL.java相关,于是看一看RIL.java文件,存放于ANDROID.2.3.3/frameworks/base/telephony/java/com/android/internal/telephony/
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.     *   an ArrayList of strings that, in order,     *   comprise the original message     */    public ArrayList
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.     *   an ArrayList of strings that, in order,     *   comprise the original msg text     *     * @hide     */    public static ArrayList
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
result = new ArrayList
(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不久的经验少的孩子。自己的理解很狭隘,能力也有限,只是做个抛砖引玉的效果,希望大家一起来分享自己的心得。

转载于:https://my.oschina.net/JumpLong/blog/76835

你可能感兴趣的文章