文档章节

试着从java到C去理解android中发送短信的源代码

我不是咸蛋
 我不是咸蛋
发布于 2012/09/04 20:45
字数 2267
阅读 4095
收藏 14

     今天在找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));
            }
        }
    }


关键是那个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.
     * @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不久的经验少的孩子。自己的理解很狭隘,能力也有限,只是做个抛砖引玉的效果,希望大家一起来分享自己的心得。

© 著作权归作者所有

我不是咸蛋
粉丝 36
博文 14
码字总数 23184
作品 0
成都
程序员
私信 提问
加载中

评论(12)

李安营
李安营
楼主好
if (Log.isLoggable("SMS", Log.VERBOSE)) {
log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
" text='"+ text + "' sentIntent=" +
sentIntent + " deliveryIntent=" + deliveryIntent);
}
这里是可能写入了日志的
那怎么设置日志的级别,让他写入日志,然后在哪查看这些日志呢?
我不是咸蛋
我不是咸蛋 博主

引用来自“haizhiyun”的评论

遇见一个虚函数的时候怎么查找它的具体实现的?有什么快捷的方法?3q!

用软件,source insight
haizhiyun
haizhiyun
哥们有GPRS 的研究,是否有笔记之类的东西,可否拿出来分享下,最近在看相关部分的源码,感觉很浩瀚啊,谢谢!
haizhiyun
haizhiyun
遇见一个虚函数的时候怎么查找它的具体实现的?有什么快捷的方法?3q!
我不是咸蛋
我不是咸蛋 博主

引用来自“Znic”的评论

在Smsmanager里调用iccISms.sendText,怎么就跳转到IccSmsInterfaceManager.java里的sendtext了?这里的ISms.Stub和ISms.Stub.Proxy是怎么工作的?

哦 不好意思 没注意你的问题 我晚上回去看一看 再回答你 不好意思
Znic
Znic
在Smsmanager里调用iccISms.sendText,怎么就跳转到IccSmsInterfaceManager.java里的sendtext了?这里的ISms.Stub和ISms.Stub.Proxy是怎么工作的?
Znic
Znic
请问isms服务的binder通信的IPC数据是通过什么函数发送出去的?发送的目标服务进程是哪个?
sendmsg是socket通信的发送函数,发送的目标进程应该是rild守护进程。
我不是咸蛋
我不是咸蛋 博主

引用来自“Znic”的评论

文中“再根据handle,同样在RIL.java中找到 ……”,这个handle哪冒出来的呢?

不是有个send函数嘛 它有个message 然后就有对应的handle(根据EVENT_SEND),就在一个文件中
Znic
Znic
文中“再根据handle,同样在RIL.java中找到 ……”,这个handle哪冒出来的呢?
唐逸风
能不能把文件Bearerdata.java中encodeEmsUserDataPayload中对uData.msgEncoding等于UserData.ENCODING_7BIT_ASCII编码时处理函数调用的源码?

如:UserData.ENCODING_ENCODING_16编码时,函数是encode16bitEMS(xx,xx);

我想知道
UserData.ENCODING_7BIT_ASCII 编码时,处理函数的源码?
谢谢!
Android 开发 及 编译系统

一、Android 的开发分为三个类型 移植开发移动设备系统;android 系统级开发;应用程序 可以把android 分为四个层次,从底层往上依次为:linux 内核、C/C++ 库、java 框架和java 应用程序。 ...

SibylY
2015/06/16
0
0
安卓程序员,Linux,java,有趣的架构。

先来看常见的Linux系统架构,你可以参考Linux的架构 内核是系统的底层。Linux开机后,内核即启动,并存活于属于自己的内存空间,即内核空间(kernel space)。内核的一大功能是和硬件通信。内核...

android自学
2018/07/24
0
0
Windows下部署Appium教程(Android App自动化测试框架搭建)

----------------------------------------------appium的一些基本概念---------------------------------------------- appium的核心其实是一个暴露了一系列REST API的server。 这个server的......

outcat
2015/08/12
0
26
基础总结篇之五:BroadcastReceiver应用详解

問渠那得清如許?為有源頭活水來。南宋.朱熹《觀書有感》 据说程序员是最爱学习的群体,IT男都知道,这个行业日新月异,必须不断地学习新知识,不断地为自己注入新鲜的血液,才能使自己跟上技...

MZHS
2013/11/27
0
0
基础总结篇之五:BroadcastReceiver应用详解

問渠那得清如許?為有源頭活水來。南宋.朱熹《觀書有感》 据说程序员是最爱学习的群体,IT男都知道,这个行业日新月异,必须不断地学习新知识,不断地为自己注入新鲜的血液,才能使自己跟上技...

Jonson
2013/09/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

分布式架构 共享session的常见解决方案

在使用分布式架构时,会遇到分布式架构常见的几个问题: 分布式事务、接口幂等性、分布式锁和分布式 session。 分布式session 一、什么是session 浏览器在访问一个web服务的时候,会在浏览器...

太猪-YJ
24分钟前
2
0
Android java.lang.NoClassDefFoundError:failed resolution of :Lorg/apache/http/ProtocolVersion解决方案

这是GooglePlay Services方面的一个bug,在修复之前,可以通过将此添加到AndroidManifest.xml内部<application>标签: <uses-library android:name="org.apache.http.legacy" android:requir......

醉雨
29分钟前
0
0
蚂蚁金服 Service Mesh 落地实践与挑战 | GIAC 实录

本文整理自 GIAC(GLOBAL INTERNET ARCHITECTURE CONFERENCE)全球互联网架构大会,蚂蚁金服平台数据技术事业群技术专家石建伟(花名:卓与)的分享。分享基于 Service Mesh 的理念,结合蚂蚁...

SOFAStack
35分钟前
1
0
Java跨平台原理

此篇博文主要源自网络xiaozhen的天空的博客:http://xiaozhen1900.blog.163.com/blog/static/1741732572011325111945246/   1、是么是平台 Java是可以跨平台的编程语言,那我们首先得知道什...

vinci321
35分钟前
1
0
分享 KubeCon 2019 (上海)关于 Serverless 及 Knative 相关演讲会议

有幸参加了 KubeCon 2019 上海大会,并参加了 Knative 及 Serverless 相关的几场分享会,收获满满。这里简单介绍一下各个演讲主题的主要内容。详细的演讲主题文档可以在KubeCon官方获取:htt...

阿里云官方博客
44分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部