frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/gsmSMSDispatcher.java

 

gsmSMSDispatcher.java 里有很多文件都调用了 sendRawPdu()   

sendTextWithEncodingType()

       sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, destAddr);

 

 

frameworks/opt/telephony/src/java/com/android/internal/telephony/SMSDispatcher.java

下定义了 4 个类

        public abstract class SMSDispatcherextends Handler

以下3个类都是 嵌套在SMSDispatcher  类当中的。
           private static class SettingsObserverextends ContentObserver
           protected static final class SmsTracker
           private final class ConfirmDialogListener

 

发送sms是通过 如下sendTextWithEncodingType函数来的。它是被 gemini 层的相关函数调用,然后发送的。

关于 它是被哪儿调用的,我们可以参考 “http://blog.csdn.net/duanlove/article/details/9499539”。

那我们看看sendTextWithEncodingType的实现。

frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/gsmSMSDispatcher.java

 

    // MTK-END [ALPS00094284] Filter hyphen of phone number by mtk80589 in 2011.11.23
    // MTK-END [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
   
    // MTK-START [ALPS00094531] Orange feature SMS Encoding Type Setting by mtk80589 in 2011.11.22
    /** {@inheritDoc} */
    protected void sendTextWithEncodingType( //单条发送,不需要拆分,就调用这个。
            String destAddr,
            String scAddr,
            String text,
            int encodingType,
            PendingIntent sentIntent,
            PendingIntent deliveryIntent) {
        // impl
        // MTK_OPTR_PROTECT_START
        if (isDmLock == true) {
            Log.d(TAG, "DM status: lock-on");
            return;
        }
        // MTK_OPTR_PROTECT_END

        int encoding = encodingType;
        TextEncodingDetails details = SmsMessage.calculateLength(text, false); // 需理解的重点1.
        if (encoding != details.codeUnitSize &&
            (encoding == SmsConstants.ENCODING_UNKNOWN ||
             encoding == SmsConstants.ENCODING_7BIT)) {
            Log.d(TAG, "[enc conflict between details[" + details.codeUnitSize
                    + "] and encoding " + encoding);
            details.codeUnitSize = encoding;
        }

        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu // 需理解的重点2.
                scAddr, destAddr, text, (deliveryIntent != null),
                null, encoding, details.languageTable, details.languageShiftTable);

        if (pdu != null) {
            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, destAddr);  // 需理解的重点3
        } else {
            Log.d(TAG, "sendText: pdu is null");
            if (sentIntent != null) {
                try {
                    sentIntent.send(RESULT_ERROR_NULL_PDU);
                } catch (CanceledException ex) {
                    Log.e(TAG, "failed to send back RESULT_ERROR_NULL_PDU");
                }
            }
        }
    }

    /** {@inheritDoc} */
    protected void sendMultipartTextWithEncodingType( // 需要拆分成多条发送,就使用这个函数
            String destAddr,
            String scAddr,
            ArrayList parts,
            int encodingType,
            ArrayList sentIntents,
            ArrayList deliveryIntents) {
        // impl
        // MTK_OPTR_PROTECT_START
        if (isDmLock == true) {
            Log.d(TAG, "DM status: lock-on");
            return;
        }
        // MTK_OPTR_PROTECT_END

        int refNumber = getNextConcatenatedRef() & 0xff;
        int msgCount = parts.size();
        int encoding = encodingType;

        mRemainingMessages = msgCount;
        TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
        for (int i = 0; i < msgCount; ++i) {
            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
            if (encoding != details.codeUnitSize &&
                (encoding == SmsConstants.ENCODING_UNKNOWN ||
                 encoding == SmsConstants.ENCODING_7BIT)) {
                Log.d(TAG, "[enc conflict between details[" + details.codeUnitSize
                        + "] and encoding " + encoding);
                details.codeUnitSize = encoding;
            }
            encodingForParts[i] = details;
        }

        for (int i = 0; i < msgCount; ++i) {// 按照拆分的条数,一条一条发送。
            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
            concatRef.refNumber = refNumber;
            concatRef.seqNumber = i + 1;  // 1-based sequence
            concatRef.msgCount = msgCount;
            // TODO: We currently set this to true since our messaging app will never
            // send more than 255 parts (it converts the message to MMS well before that).
            // However, we should support 3rd party messaging apps that might need 16-bit
            // references
            // Note:  It's not sufficient to just flip this bit to true; it will have
            // ripple effects (several calculations assume 8-bit ref).
            concatRef.isEightBits = true;
            SmsHeader smsHeader = new SmsHeader();
            smsHeader.concatRef = concatRef;
            if (encoding == SmsConstants.ENCODING_7BIT) {
                smsHeader.languageTable = encodingForParts[i].languageTable;
                smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
            }

            PendingIntent sentIntent = null;
            if (sentIntents != null && sentIntents.size() > i) {
                sentIntent = sentIntents.get(i);
            }

            PendingIntent deliveryIntent = null;
            if (deliveryIntents != null && deliveryIntents.size() > i) {
                deliveryIntent = deliveryIntents.get(i);
            }

            SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
                    scAddr, destAddr, parts.get(i), (deliveryIntent != null),
                    SmsHeader.toByteArray(smsHeader), encoding,
                    smsHeader.languageTable, smsHeader.languageShiftTable);

            if (pdu != null) {
                sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, destAddr);
            } else {
                Log.d(TAG, "sendText: pdu is null");
                if (sentIntent != null) {
                    try {
                        sentIntent.send(RESULT_ERROR_NULL_PDU);
                    } catch (CanceledException ex) {
                        Log.e(TAG, "failed to send back RESULT_ERROR_NULL_PDU");
                    }
                }
            }
        }
    }

 

 

 

GsmAlphabet.java (fram\base\telephony\java\com\android\internal\telephony)

package com.android.internal.telephony;

import android.content.res.Resources;
import android.text.TextUtils;
import android.util.SparseIntArray;

import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.R;

import java.util.ArrayList;
import java.util.List;

/**
 * This class implements the character set mapping between
 * the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1
 * and UTF-16
 *
 * {@hide}
 */
public class GsmAlphabet {
    private static final String TAG = "GSM";

    private GsmAlphabet() { }

    /**
     * This escapes extended characters, and when present indicates that the
     * following character should be looked up in the "extended" table.
     *
     * gsmToChar(GSM_EXTENDED_ESCAPE) returns 0xffff
     */
    public static final byte GSM_EXTENDED_ESCAPE = 0x1B;

    /**
     * User data header requires one octet for length. Count as one septet, because
     * all combinations of header elements below will have at least one free bit
     * when padding to the nearest septet boundary.
     */
    public static final int UDH_SEPTET_COST_LENGTH = 1;

    /**
     * Using a non-default language locking shift table OR single shift table
     * requires a user data header of 3 octets, or 4 septets, plus UDH length.
     */
    public static final int UDH_SEPTET_COST_ONE_SHIFT_TABLE = 4;

    /**
     * Using a non-default language locking shift table AND single shift table
     * requires a user data header of 6 octets, or 7 septets, plus UDH length.
     */
    public static final int UDH_SEPTET_COST_TWO_SHIFT_TABLES = 7;

    /**
     * Multi-part messages require a user data header of 5 octets, or 6 septets,
     * plus UDH length.
     */
    public static final int UDH_SEPTET_COST_CONCATENATED_MESSAGE = 6;

    /**
     * For a specific text string, this object describes protocol
     * properties of encoding it for transmission as message user
     * data.
     */
    public static class TextEncodingDetails {
        /**
         *The number of SMS's required to encode the text.
         */
        public int msgCount;

        /**
         * The number of code units consumed so far, where code units
         * are basically characters in the encoding -- for example,
         * septets for the standard ASCII and GSM encodings, and 16
         * bits for Unicode.
         */
        public int codeUnitCount;

        /**
         * How many code units are still available without spilling
         * into an additional message.
         */
        public int codeUnitsRemaining;

        /**
         * The encoding code unit size (specified using
         * android.telephony.SmsMessage ENCODING_*).
         */
        public int codeUnitSize;

        /**
         * The GSM national language table to use, or 0 for the default 7-bit alphabet.
         */
        public int languageTable;

        /**
         * The GSM national language shift table to use, or 0 for the default 7-bit extension table.
         */
        public int languageShiftTable;

        // MTK-START [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
        public boolean useSingleShift = false;

        public boolean useLockingShift = false;

        public int shiftLangId = -1;
        // MTK-END   [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16

        @Override
        public String toString() {
            return "TextEncodingDetails " +
                    "{ msgCount=" + msgCount +
                    ", codeUnitCount=" + codeUnitCount +
                    ", codeUnitsRemaining=" + codeUnitsRemaining +
                    ", codeUnitSize=" + codeUnitSize +
                    ", languageTable=" + languageTable +
                    ", languageShiftTable=" + languageShiftTable +
                    " }";
        }
    }

 

 

 

 

 

 

 

对于 sms 流程中非常重要的一个 函数 sendRawPdu 定义于SMSDispatcher类当中。我们来看看它的实现:

    /**
     * Send a SMS
     *
     * @param smsc the SMSC to send the message through, or NULL for the
     *  default SMSC
     * @param pdu the raw PDU to send
     * @param sentIntent if not NULL this Intent is
     *  broadcast when the message is successfully 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
     *  RESULT_ERROR_NO_SERVICE.
     *  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 Intent is
     *  broadcast when the message is delivered to the recipient.  The
     *  raw pdu of the status report is in the extended data ("pdu").
     * @param destAddr the destination phone number (for short code confirmation)
     */
    protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent, PendingIntent deliveryIntent, String destAddr)

{


    if (mSmsSendDisabled)
    {
        if (sentIntent != null)
        {
            try {
                sentIntent.send(RESULT_ERROR_NO_SERVICE);
            } catch (CanceledException ex) {}
        }
        Log.d(TAG, "Device does not support sending sms.");
        return;
    }

    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);

    // Get calling app package name via UID from Binder call
    PackageManager pm = mContext.getPackageManager();
    String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());

    if (packageNames == null || packageNames.length == 0)
    {
        // Refuse to send SMS if we can't get the calling package name.
        Log.e(TAG, "Can't get calling app package name: refusing to send SMS");
        if (sentIntent != null)
        {
            try
            {
                sentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
            } catch (CanceledException ex) {
                Log.e(TAG, "failed to send error result");
            }
        }
        return;
    }

    // MTK-START
    /* Because it may have multiple apks use the same uid, ex. Mms.apk and omacp.apk, we need to
     * exactly find the correct calling apk. We should use running process to check the correct
     * apk. If we could not find the process via pid, this apk may be killed. We will use the
     * default behavior, find the first package name via uid.
     */
    if (packageNames.length > 1)
    {
        int callingPid = Binder.getCallingPid();

        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        List processList = am.getRunningAppProcesses();
        Iterator index = processList.iterator();
        while (index.hasNext())
        {
            ActivityManager.RunningAppProcessInfo processInfo = (ActivityManager.RunningAppProcessInfo)(index.next());
            if (callingPid == processInfo.pid)
            {
                packageNames[0] = processInfo.processName;
                break;
            }
        }
    }
    // MTK-END

    // Get package info via packagemanager
    PackageInfo appInfo = null;
    try
    {
        // XXX this is lossy- apps can share a UID
        appInfo = pm.getPackageInfo(packageNames[0], PackageManager.GET_SIGNATURES);
    } catch (PackageManager.NameNotFoundException e) {
        Log.e(TAG, "Can't get calling app package info: refusing to send SMS");
        if (sentIntent != null) {
            try {
                sentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
            } catch (CanceledException ex) {
                Log.e(TAG, "failed to send error result");
            }
        }
        return;
    }

    // Strip non-digits from destination phone number before checking for short codes
    // and before displaying the number to the user if confirmation is required.
    SmsTracker tracker = new SmsTracker(map, sentIntent, deliveryIntent, appInfo,
            PhoneNumberUtils.extractNetworkPortion(destAddr));

    // checkDestination() returns true if the destination is not a premium short code or the
    // sending app is approved to send to short codes. Otherwise, a message is sent to our
    // handler with the SmsTracker to request user confirmation before sending.

 

// checkDestination(tracker):  true !      mUsageMonitor.check(appInfo.packageName, SINGLE_PART_SMS):   false  

 // 此打印表明 并没有进入下面的第二个if语句,sendMessage 没有被调用。


    if (checkDestination(tracker))
    {
        // check for excessive outgoing SMS usage by this app
        if (!mUsageMonitor.check(appInfo.packageName, SINGLE_PART_SMS)) { // 下面并没有执行
            sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
            return;
        }

// 流程到这里,继续往下走

        int ss = mPhone.getServiceState().getState();

// ss != ServiceState.STATE_IN_SERVICE:   false   ;表明,并没有执行             handleNotInService(ss, tracker.mSentIntent);

        if (ss != ServiceState.STATE_IN_SERVICE)
        {
            handleNotInService(ss, tracker.mSentIntent);
        } else
        {  // 实际上,走到了下面这个分支
            String appName = getAppNameByIntent(sentIntent);
            // MTK-START [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16

// MTK_SMS_FILTER_SUPPORT true, call createMessageFromSubmitPdu


            if(FeatureOption.MTK_SMS_FILTER_SUPPORT == true) {   // 短信正常,进入这里
                SmsMessage msg = createMessageFromSubmitPdu(smsc, pdu); // 从PDU创建 SmsMessage 类,以方便下面的 checkSmsWithNqFilter 函数使用
                if(msg != null)
                {    // 短信不为空,进入if分支; 上面的msg 是为了方便获取 sms的目的地址 和 消息体!以检查是否需要过滤!
                    boolean ret = checkSmsWithNqFilter(msg.getDestinationAddress(), msg.getMessageBody(), sentIntent);
                    if(ret == false)
                    {
                        Log.d(TAG, "[NQ this message is safe");
                        if (mUsageMonitor.check(appName, SINGLE_PART_SMS))
                        {
                            sendSms(tracker);// 发送短信 分支1
                        } else
                        {
                            sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));//发送短信 分支1
                        }
                    } else
                    {
                        Log.d(TAG, "[NQ this message may deduct fees");
                   
                        SmsHeader.ConcatRef newConcatRef = null;
                        if(msg.getUserDataHeader() != null) {
                            newConcatRef = msg.getUserDataHeader().concatRef;
                        }
                   
                        if(newConcatRef != null) {
                            if(sConcatRef == null || sConcatRef.refNumber != newConcatRef.refNumber) {
                                Log.d(TAG, "[NQ this is a new concatenated message, just update");
                                sConcatRef = newConcatRef;
                                //sConcatMsgCount = 1;
                                sendMessage(obtainMessage(EVENT_HANDLE_REDUCTED_MESSAGE, tracker));
                            } else {
                                Log.d(TAG, "[NQ this is the same concatenated message, keep previous operation");
                                //mSTrackers.add(tracker);
                                sConcatMsgCount += 1;
                            }
                        } else {
                            Log.d(TAG, "[NQ this is a non-concatenated message");
                            //sConcatMsgCount = 0;
                            sendMessage(obtainMessage(EVENT_HANDLE_REDUCTED_MESSAGE, tracker));
                        }
                    }
                } else
                {
                    Log.d(TAG, "[NQ fail to create message from pdu");
                    if (mUsageMonitor.check(appName, SINGLE_PART_SMS)) {
                        sendSms(tracker);
                    } else {
                        sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
                    }
                }
            } else
            {
            // MTK-END [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
                if (mUsageMonitor.check(appName, SINGLE_PART_SMS)) {
                    sendSms(tracker);
                } else {
                    sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
                }
            // MTK-START [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
            }
            // MTK-END   [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
        }
        // 短信发送完成,直接跳到这里。
    }

 

}

 

同一个文件也定义了:class SmsTracker 和 sendSms


    /**
     * Send the message along to the radio.
     *
     * @param tracker holds the SMS message to send
     */
    protected abstract void sendSms(SmsTracker tracker);

 

    protected static final class SmsTracker

   {
        // fields need to be public for derived SmsDispatchers
        public final HashMap mData;
        public int mRetryCount;
        public int mMessageRef;

        public final PendingIntent mSentIntent;
        public final PendingIntent mDeliveryIntent;

        public final PackageInfo mAppInfo;
        public final String mDestAddress;

       //构造函数

        public SmsTracker(HashMap data, PendingIntent sentIntent,
                PendingIntent deliveryIntent, PackageInfo appInfo, String destAddr)

      {
            mData = data;
            mSentIntent = sentIntent;
            mDeliveryIntent = deliveryIntent;
            mRetryCount = 0;
            mAppInfo = appInfo;
            mDestAddress = destAddr;
        }

        /**
         * Returns whether this tracker holds a multi-part SMS.
         * @return true if the tracker holds a multi-part SMS; false otherwise
         */
        protected boolean isMultipart() {
            HashMap map = mData;
            return map.containsKey("parts");
        }
    }

 

 

 

而 以下文件中也定义了 sendSms 函数

frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/SMSDispatcher.java
 

class GsmSMSDispatcher  中定义了:

 

    /** {@inheritDoc} */
    @Override
    protected void sendSms(SmsTracker tracker)

   {
        HashMap map = tracker.mData;

        byte smsc[] = (byte[]) map.get("smsc");
        byte pdu[] = (byte[]) map.get("pdu");

        // MTK-START [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
        synchronized (mSTrackersQueue)
        {
            if (mSTrackersQueue.isEmpty() || mSTrackersQueue.get(0) == tracker) {// 如果队列为空,且当前第一个消息是本消息,那么,就发送这个消息。
        // MTK-END [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
                Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
                mCm.sendSMS(IccUtils.bytesToHexString(smsc),  IccUtils.bytesToHexString(pdu), reply);//  发送消息
        // MTK-START [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
            }

            if (mSTrackersQueue.isEmpty() || mSTrackersQueue.get(0) != tracker) { // 如果队列为空,且第一个不是 本消息 ,那么就把本消息加入到队列中去。
                Log.d(TAG, "Add tracker into the list: " + tracker);                //如果 队列不为空,第一个 消息,也不是本消息,还是把本消息加入到队列中去。
                mSTrackersQueue.add(tracker);
            }
        }
        // MTK-END [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
    }

 

GsmSMSDispatcher 实际上是 SMSDispatcher 的一个 子类,也就是派生类。

         public final class GsmSMSDispatcherextendsSMSDispatcher

 

它还包含了一个 嵌套类:

    private static final class SmsCbConcatInfo{

        private final SmsCbHeader mHeader;
        private final SmsCbLocation mLocation;

        public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
            mHeader = header;
            mLocation = location;
        }

        @Override
        public int hashCode() {
            return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof SmsCbConcatInfo) {
                SmsCbConcatInfo other = (SmsCbConcatInfo)obj;

                // Two pages match if they have the same serial number (which includes the
                // geographical scope and update number), and both pages belong to the same
                // location (PLMN, plus LAC and CID if these are part of the geographical scope).
                return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
                        && mLocation.equals(other.mLocation);
            }

            return false;
        }

        /**
         * Compare the location code for this message to the current location code. The match is
         * relative to the geographical scope of the message, which determines whether the LAC
         * and Cell ID are saved in mLocation or set to -1 to match all values.
         *
         * @param plmn the current PLMN
         * @param lac the current Location Area (GSM) or Service Area (UMTS)
         * @param cid the current Cell ID
         * @return true if this message is valid for the current location; false otherwise
         */
        public boolean matchesLocation(String plmn, int lac, int cid) {
            return mLocation.isInLocationArea(plmn, lac, cid);
        }
    }

 

 

#### 下面我们看一下 createMessageFromSubmitPdu返回  android.telephony.SmsMessage 的这个流程 ######

 

GsmSMSDispatcher  中的createMessageFromSubmitPdu  可以从 一个PDU BYTE数组创建一个 android.telephony.SmsMessage  类。

    protected android.telephony.SmsMessage  createMessageFromSubmitPdu(byte[] smsc, byte[] tpdu)

{
        // smsc + tpdu
        Log.d(TAG, "[NQ tpdu first byte is " + tpdu[0]);
        int tpduLen = tpdu.length;
        int smscLen = 1;
        if (smsc != null) {// 如果 smsc不为null的话 ,就取出 smsc的长度 赋值给 smscLen  ,否则 smscLen   就是 默认为1 。
            smscLen
= smsc.length; 
        } else {
            Log.d(TAG, "[NQ smsc is null");
        }
        byte[] msgPdu = new byte[smscLen + tpduLen]; 
        int curIndex = 0;
        try {
            if (smsc != null) {  //smsc :需要注意的是smsc第一个byte的低八位 代表了发送者地址的长度。
                System.arraycopy(smsc, 0, msgPdu, curIndex, smscLen);// // 如果 smsc不为null的话 , 就先把smsc (发送者的地址)存入到 msgPdu
            } else {
                msgPdu[0] = 0;   // 否则的话,就只占一个byte,并且这个 byte是第一个byte,内容是 0
            }
            curIndex += smscLen;
            System.arraycopy(tpdu, 0, msgPdu, curIndex, tpduLen); // 接着把剩下的tpdu部分原始的pdu数据,追加到 smsc后面。 其实到这里为止做的主要工作就是把发送者的地址和pdu数据整合到一起。并准备传入下面的函数。
            Log.d(TAG, "[NQ mti byte in msgPdu is " + msgPdu[1]);
        } catch (IndexOutOfBoundsException e) {
            Log.d(TAG, "[NQ out of bounds error when copy pdu data");
        }

        return android.telephony.SmsMessage.createFromPdu(msgPdu, getFormat());
    }
   

    @Override
    protected String getFormat() {
        return SmsConstants.FORMAT_3GPP;   //  import com.android.internal.telephony.SmsConstants;
    }

 

 

frameworks/opt/telephony/src/java/android/telephony/SmsMessage.java:

package android.telephony;

import android.os.Parcel;
import android.util.Log;

import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
import com.mediatek.common.featureoption.FeatureOption;

import java.lang.Math;
import java.util.ArrayList;
import java.util.Arrays;

import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;


/**
 * A Short Message Service message.
 */
public class SmsMessage    类中:

 

    public SmsMessageBase mWrappedSmsMessage;

    public static class SubmitPdu{

        public byte[] encodedScAddress; // Null if not applicable.
        public byte[] encodedMessage;

        public String toString() {
            return "SubmitPdu: encodedScAddress = "
                    + Arrays.toString(encodedScAddress)
                    + ", encodedMessage = "
                    + Arrays.toString(encodedMessage);
        }

        /**
         * @hide
         */
        protected SubmitPdu(SubmitPduBase spb) {
            this.encodedMessage = spb.encodedMessage;
            this.encodedScAddress = spb.encodedScAddress;
        }

    }

    private SmsMessage(SmsMessageBase smb) {
        mWrappedSmsMessage = smb;
    }

 

 

createFromPdu 进行了重载:

     */
    public static SmsMessage createFromPdu(byte[] pdu)

  {
        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
        String format = (PHONE_TYPE_CDMA == activePhone) ?
                SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
        return createFromPdu(pdu, format);
    }

    /**
     * Create an SmsMessage from a raw PDU with the specified message format. The
     * message format is passed in the {@code SMS_RECEIVED_ACTION} as the{@code format}
     * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
     * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
     *
     * @param pdu the message PDU from the SMS_RECEIVED_ACTION intent
     * @param format the format extra from the SMS_RECEIVED_ACTION intent
     * @hide pending API council approval
     */
    public static SmsMessage createFromPdu(byte[] pdu, String format)

  {
        SmsMessageBase wrappedMessage;

        if (SmsConstants.FORMAT_3GPP2.equals(format)) {
            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
        } else if (SmsConstants.FORMAT_3GPP.equals(format)) { // 因为传入的是FORMAT_3GPP 所以走这个分支
            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);   //java/com/android/internal/telephony/gsm/SmsMessage.java 中 createFromPdu 根据 pdu参数生成的对象,返回到 wrappedMessage
        } else {
            Log.e(LOG_TAG, "createFromPdu(): unsupported message format " + format);
            return null;
        }

        return new SmsMessage(wrappedMessage);   //返回了一个SmsMessage 对象 . 那最后 SmsMessage 构造函数通过 传入上面包含信息的的 wrappedMessage 对象 构造了一个  android.telephony.SmsMessage对象。
    }

 

 

frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/SmsMessage.java

package com.android.internal.telephony.gsm;

import android.os.Parcel;
import android.telephony.PhoneNumberUtils;
import android.text.format.Time;
import android.util.Log;

import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;

import static com.android.internal.telephony.SmsConstants.MessageClass;
import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
// MTK-START [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16
import android.os.SystemProperties;
import android.util.Config;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import static android.telephony.SmsMessage.MWI_VIDEO;
// MTK-END   [ALPS000xxxxx] MTK code port to ICS added by mtk80589 in 2011.11.16

import com.android.internal.telephony.IMessageWaitingExt;

/**
 * A Short Message Service message.
 *
 */
public class SmsMessageextendsSmsMessageBase

在这个SmsMessage   中也定义了 createFromPdu  这个函数

    /**
     * Create an SmsMessage from a raw PDU.
     */
    public static SmsMessage createFromPdu(byte[] pdu) {
        try {
            SmsMessage msg = new SmsMessage();
            msg.parsePdu(pdu); // 解析PDU。其实就是根据原始的PDU 解析出来的各种东东存在msg对象里头。 于是下行返回的msg就具备有用的信息了。
           
return msg//实际上最后返回的是这个SmsMessage 类型的实例 
        } catch (RuntimeException ex) {
            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
            return null;
        }
    }

同时我们也可以看到上面调用的函数parsePdu 来解析 PDU byte数组:


    /**
     * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
     * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
     * ME/TA converts each octet of TP data unit into two IRA character long
     * hex number (e.g. octet with integer value 42 is presented to TE as two
     * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
     * something else...
     */
    private void parsePdu(byte[] pdu) {
        mPdu = pdu; // 首先把原始的pdu保存在 全局变量mPdu里头。这样我们可以随时取出原始的PDU
        // Log.d(LOG_TAG, "raw sms message:");
        // Log.d(LOG_TAG, s);

        PduParser p = new PduParser(pdu); // 通过 PduParser 的构造函数解析PDU

        scAddress = p.getSCAddress();  //获取解析好的原始地址。

        if (scAddress != null) {
            if (false) Log.d(LOG_TAG, "SMS SC address: " + scAddress);
        }

        // TODO(mkf) support reply path, user data header indicator

        // TP-Message-Type-Indicator
        // 9.2.3
        int firstByte = p.getByte();

        mti = firstByte & 0x3;    //根据第一byte的 值进行 各自的解析。
        switch (mti) {
        // TP-Message-Type-Indicator
        // 9.2.3
        case 0:
        case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
                //This should be processed in the same way as MTI == 0 (Deliver)
            parseSmsDeliver(p, firstByte);
            break;
        case 1:
            parseSmsSubmit(p, firstByte);
            break;
        case 2:
            parseSmsStatusReport(p, firstByte);
            break;
        default:
            // TODO(mkf) the rest of these
            throw new RuntimeException("Unsupported message type");
        }
    }

 

 



    /**
     * Parses a SMS-STATUS-REPORT message.
     *
     * @param p A PduParser, cued past the first byte.
     * @param firstByte The first byte of the PDU, which contains MTI, etc.
     */
    private void parseSmsStatusReport(PduParser p, int firstByte) {
        isStatusReportMessage = true;


        // TP-Status-Report-Qualifier bit == 0 for SUBMIT
        forSubmit = (firstByte & 0x20) == 0x00;
        // TP-Message-Reference
        messageRef = p.getByte();
        // TP-Recipient-Address
        recipientAddress = p.getAddress();
        // TP-Service-Centre-Time-Stamp
        scTimeMillis = p.getSCTimestampMillis();
        // TP-Discharge-Time
        dischargeTimeMillis = p.getSCTimestampMillis();
        // TP-Status
        status = p.getByte();
        
        messageBody = "";


        // The following are optional fields that may or may not be present.
        if (p.moreDataPresent()) {
            // TP-Parameter-Indicator
            int extraParams = p.getByte();
            int moreExtraParams = extraParams;
            while (((moreExtraParams & 0x80) != 0) && (p.moreDataPresent() == true)) {
                // We only know how to parse a few extra parameters, all
                // indicated in the first TP-PI octet, so skip over any
                // additional TP-PI octets.
                moreExtraParams = p.getByte();
            }
            // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
            // only process the byte if the reserved bits (bits3 to 6) are zero.
            if ((extraParams & 0x78) == 0) {
                // TP-Protocol-Identifier
                if ((extraParams & 0x01) != 0) {
                    protocolIdentifier = p.getByte();
                }
                // TP-Data-Coding-Scheme
                if ((extraParams & 0x02) != 0) {
                    dataCodingScheme = p.getByte();
                }
                // TP-User-Data-Length (implies existence of TP-User-Data)
                if ((extraParams & 0x04) != 0) {
                    boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
                    parseUserData(p, hasUserDataHeader);
                }
            }
        }
    }


    private void parseSmsDeliver(PduParser p, int firstByte) {
        replyPathPresent = (firstByte & 0x80) == 0x80;


        originatingAddress = p.getAddress();


        if (originatingAddress != null) {
            if (false) Log.v(LOG_TAG, "SMS originating address: "
                    + originatingAddress.address);
        }


        // TP-Protocol-Identifier (TP-PID)
        // TS 23.040 9.2.3.9
        protocolIdentifier = p.getByte();


        // TP-Data-Coding-Scheme
        // see TS 23.038
        dataCodingScheme = p.getByte();


        if (false) {
            Log.v(LOG_TAG, "SMS TP-PID:" + protocolIdentifier
                    + " data coding scheme: " + dataCodingScheme);
        }


        scTimeMillis = p.getSCTimestampMillis();


        if (false) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);


        boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;


        parseUserData(p, hasUserDataHeader);
    }


    /**
     * Parses the User Data of an SMS.
     *
     * @param p The current PduParser.
     * @param hasUserDataHeader Indicates whether a header is present in the
     *                          User Data.
     */
    private void parseUserData(PduParser p, boolean hasUserDataHeader) {
        boolean hasMessageClass = false;
        boolean userDataCompressed = false;


        int encodingType = ENCODING_UNKNOWN;


        // Look up the data encoding scheme
        if ((dataCodingScheme & 0x80) == 0) {
            // Bits 7..4 == 0xxx
            automaticDeletion = (0 != (dataCodingScheme & 0x40));
            userDataCompressed = (0 != (dataCodingScheme & 0x20));
            hasMessageClass = (0 != (dataCodingScheme & 0x10));


            if (userDataCompressed) {
                Log.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
                        + "(compression) " + (dataCodingScheme & 0xff));
            } else {
                switch ((dataCodingScheme >> 2) & 0x3) {
                case 0: // GSM 7 bit default alphabet
                    encodingType = ENCODING_7BIT;
                    break;


                case 2: // UCS 2 (16bit)
                    encodingType = ENCODING_16BIT;
                    break;


                case 1: // 8 bit data
                case 3: // reserved
                    Log.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
                            + (dataCodingScheme & 0xff));
                    encodingType = ENCODING_8BIT;
                    break;
                }
            }
        } else if ((dataCodingScheme & 0xf0) == 0xf0) {
            automaticDeletion = false;
            hasMessageClass = true;
            userDataCompressed = false;


            if (0 == (dataCodingScheme & 0x04)) {
                // GSM 7 bit default alphabet
                encodingType = ENCODING_7BIT;
            } else {
                // 8 bit data
                encodingType = ENCODING_8BIT;
            }
        } else if ((dataCodingScheme & 0xF0) == 0xC0
                || (dataCodingScheme & 0xF0) == 0xD0
                || (dataCodingScheme & 0xF0) == 0xE0) {
            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4


            // 0xC0 == 7 bit, don't store
            // 0xD0 == 7 bit, store
            // 0xE0 == UCS-2, store


            if ((dataCodingScheme & 0xF0) == 0xE0) {
                encodingType = ENCODING_16BIT;
            } else {
                encodingType = ENCODING_7BIT;
            }


            userDataCompressed = false;
            boolean active = ((dataCodingScheme & 0x08) == 0x08);


            // bit 0x04 reserved


            if ((dataCodingScheme & 0x03) == 0x00) {
                isMwi = true;
                mwiSense = active;
                mwiDontStore = ((dataCodingScheme & 0xF0) == 0xC0);
            } else {
                isMwi = false;


                Log.w(LOG_TAG, "MWI for fax, email, or other "
                        + (dataCodingScheme & 0xff));
            }
        } else if ((dataCodingScheme & 0xC0) == 0x80) {
            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
            // 0x80..0xBF == Reserved coding groups
            if (dataCodingScheme == 0x84) {
                // This value used for KSC5601 by carriers in Korea.
                encodingType = ENCODING_KSC5601;
            } else {
                Log.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
                        + (dataCodingScheme & 0xff));
            }
        } else {
            Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
                    + (dataCodingScheme & 0xff));
        }


        // set both the user data and the user data header.
        int count = p.constructUserData(hasUserDataHeader,
                encodingType == ENCODING_7BIT);
        this.userData = p.getUserData();
        this.userDataHeader = p.getUserDataHeader();


        switch (encodingType) {
        case ENCODING_UNKNOWN:
        case ENCODING_8BIT:
            messageBody = null;
            break;


        case ENCODING_7BIT:
            messageBody = p.getUserDataGSM7Bit(count,
                    hasUserDataHeader ? userDataHeader.languageTable : 0,
                    hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
            break;


        case ENCODING_16BIT:
            messageBody = p.getUserDataUCS2(count);
            break;


        case ENCODING_KSC5601:
            messageBody = p.getUserDataKSC5601(count);
            break;
        }


        if (false) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'");


        if (messageBody != null) {
            parseMessageBody();
        }


        if (!hasMessageClass) {
            messageClass = MessageClass.UNKNOWN;
        } else {
            switch (dataCodingScheme & 0x3) {
            case 0:
                messageClass = MessageClass.CLASS_0;
                break;
            case 1:
                messageClass = MessageClass.CLASS_1;
                break;
            case 2:
                messageClass = MessageClass.CLASS_2;
                break;
            case 3:
                messageClass = MessageClass.CLASS_3;
                break;
            }
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public MessageClass getMessageClass() {
        return messageClass;
    }



############# 从PDU返回 msg 结束##################

 


   
mCm 是定义于 “frameworks/opt/telephony/src/java/com/android/internal/telephony/SMSDispatcher.java” 中的
public abstract class SMSDispatcher extends Handler 类中。即  SMSDispatcher 类中定义了:
    protected final CommandsInterface  mCm;

 

frameworks/opt/telephony/src/java/com/android/internal/telephony/CommandsInterface.java:
    /**
     * smscPDU is smsc address in PDU form GSM BCD format prefixed
     *      by a length byte (as expected by TS 27.005) or NULL for default SMSC
     * pdu is SMS in PDU format as an ASCII hex string
     *      less the SMSC address
     */
    void sendSMS (String smscPDU, String pdu, Message response);
   
   
frameworks/opt/telephony/src/java/com/android/internal/telephony/UsimDataDownloadCommands.java:
UsimDataDownloadCommands类中:


    @Override
    public void sendSMS(String smscPDU, String pdu, Message response) {
    }

    以上两个文件中,值看到 sendSMS的定义,但却没看到包含有具体的实现。如果有人知道为什么,请告诉我。谢谢!

 

frameworks/opt/telephony/src/java/com/android/internal/telephony/SMSDispatcher.java:
    /**
     * This list is used to maintain the unsent Sms Tracker
     * we have this queue list to avoid we send a lot of SEND_SMS request to RIL
     * and block other commands.
     * So we only send the next SEND_SMS request after the previously request has been completed
     */
    protected ArrayList mSTrackersQueue= new ArrayList(MO_MSG_QUEUE_LIMIT);

 

 

 在 frameworks\opt\telephony\src\java\com\android\internal\telephony\ril.java

 public final class RIL extendsBaseCommands implements CommandsInterface

       最后追溯到,ril 类中有 sendSMS的实现。如下:

 

    public void  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);
    }

 

    private void   send(RILRequest rr) {
        Message msg;

        boolean show = (requestToString(rr.mRequest).compareTo("LAST_CALL_FAIL_CAUSE") == 0);
        if (show) riljLog("###send start");

        if (mSocket == null) {
           if (show) riljLog("###mSocket == null");
            rr.onError(RADIO_NOT_AVAILABLE, null);
            rr.release();
            return;
        }

       //正常的发送流程应该走这里:

        if (show) riljLog("###send 1");
        msg = mSender.obtainMessage(EVENT_SEND, rr);
        if (show) riljLog("###send 2");

        acquireWakeLock();
        if (show) riljLog("###send 3");

        msg.sendToTarget();  // 正式发送消息
        if (show) riljLog("###send end");
    }

 

msg.sendToTarget()  定义在如下文件中的Message 类中。  

frameworks\base\core\java\android\os\Message.java :

     Handler target;   // target  是 handler类型的

    /**
     * Sends this Message to the Handler specified by {@link #getTarget}.
     * Throws a null pointer exception if this field has not been set.
     */
    public void sendToTarget() {
        target.sendMessage(this);
    }

 

那么 再看 target.sendMessage(this); 的 sendMessage 方法 定义于:

frameworks\base\core\java\android\os\Handler.java :

 

    /**
     * Pushes a message onto the end of the message queue after all pending messages
     * before the current time. It will be received in {@link #handleMessage},
     * in the thread attached to this handler.
     * 
     * @return Returns true if the message was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

 

 

    /**
     * Enqueue a message into the message queue after all pending messages
     * before (current time + delayMillis). You will receive it in
     * {@link #handleMessage}, in the thread attached to this handler.
     * 
     * @return Returns true if the message was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the message will be processed -- if
     *         the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

 


    /**
     * Enqueue a message into the message queue after all pending messages
     * before the absolute time (in milliseconds) uptimeMillis.
     * The time-base is {@link android.os.SystemClock#uptimeMillis}.
     * You will receive it in {@link #handleMessage}, in the thread attached
     * to this handler.
     *
     * @param uptimeMillis The absolute time at which the message should be
     *         delivered, using the
     *         {@link android.os.SystemClock#uptimeMillis} time-base.
     *        
     * @return Returns true if the message was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the message will be processed -- if
     *         the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

 

    /// MSG Logger Manager @}

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

 

 

frameworks\base\core\java\android\os\MessageQueue.java :

    final boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        }
        if (msg.target == null) {
            throw new AndroidRuntimeException("Message must have a target.");
        }

        boolean needWake;
        synchronized (this) {
            if (mQuiting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }

            /// M: Protection of sending the same message instance
            if(IS_ENG_BUILD)
            {
                Message tempP = mMessages;
                while(tempP != null)
                {
                    if(tempP == msg)
                    {
                        Log.wtf("MessageQueue", "Warning: Sending the same message instance! Ignored. msg=" + msg);
                        return false;
                    }//if

                    tempP = tempP.next;
                }//while
            }//if

            msg.when = when;
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
        }
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
    }

 

 

 

public class MessageQueue  类中 有定义如下变量:
    /// M: for enqueueMessage debug enhancement used
    private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);

    // True if the message queue can be quit.
    private final boolean mQuitAllowed;

    @SuppressWarnings("unused")
    private int mPtr; // used by native code

 

 

 http://wenku.baidu.com/view/33a31767783e0912a2162a88.html

 

更多相关文章

  1. 自定义Tab1
  2. 【ListView】自定义控件:下拉刷新
  3. Android 自定义view的简单应用(3) 时钟
  4. 自定义Cordova插件、Ionic插件开发
  5. Android 自定义View清除画布Canvas
  6. android 多媒体部分学习笔记十二----mediaplay自定义播放器
  7. 高德地图Android版SDK的应用(定位,添加自定义标记点)

随机推荐

  1. 在 Android(安卓)应用中使用数据库
  2. Android(安卓)与 JS 交互数据上限问题【R
  3. 面试例题4:绘制5行文本,每一行的字体大小逐
  4. Android(安卓)2.3 r1 中文API (78)―― Vie
  5. Android(安卓)输入系统
  6. 64位Ubuntu配置android环境报错(...adb":
  7. 活用Android的Message Queue(1/3)
  8. android中菜单以及自定义组件的使用
  9. eclipse中无法新建Android工程 出现问题:P
  10. android 7.1.1 软件升级安装报解析软件包