近有客户反馈Android接收不到短信,于是一头扎进RIL里面找原因。最后发现不是RIL的问题,而是BC72上报
短信的格式不对,AT+CNMA=1无作用等几个小问题导致的。尽管问题不在RIL,但总算把RIL短信接收流程搞清楚了。

接收到新信息的log:

D/ATC ( 1269): AT< +CMT:,27
D/ATC ( 1268): AT< 0891683108705505F0040d91683117358313f500009101329154922307ea31da2c36a301
D/RILJ ( 1792): [UNSL]< UNSOL_RESPONSE_NEW_SMS
D/SmsMessage( 1792): SMS SC address: +8613800755500
V/SmsMessage( 1792): SMS originating address: +8613715338315
V/SmsMessage( 1792): SMS TP-PID:0 data coding scheme: 0
D/SmsMessage( 1792): SMS SC timestamp: 1571831129000
V/SmsMessage( 1792): SMS message body (raw): 'jchfbfh'
D/GsmInboundSmsHandler( 1776): Idle state processing message type 1
D/GsmInboundSmsHandler( 1776): acquired wakelock, leaving Idle state
D/GsmInboundSmsHandler( 1776): entering Delivering state
D/GsmInboundSmsHandler( 1776): URI of new row -> content://raw/3
D/RILJ ( 1775): [3706]> SMS_ACKNOWLEDGE true 0
D/RILC ( 1254): onRequest: SMS_ACKNOWLEDGE
D/ATC ( 1254): AT> AT+CNMA=1
D/ATC ( 1254): AT< OK
D/RILJ ( 1775): [3706]< SMS_ACKNOWLEDGE
D/GsmInboundSmsHandler( 1775): Delivering SMS to: com.android.mms com.android.mms.transaction.PrivilegedSmsReceiver
E/GsmInboundSmsHandler( 1775): unexpected BroadcastReceiver action: android.provider.Telephony.SMS_RECEIVED
D/GsmInboundSmsHandler( 1775): successful broadcast, deleting from raw table.
D/SmsMessage( 2124): SMS SC address: +8613800755500
D/GsmInboundSmsHandler( 1775): Deleted 1 rows from raw table.
D/GsmInboundSmsHandler( 1775): ordered broadcast completed in: 276 ms
D/GsmInboundSmsHandler( 1775): leaving Delivering state
D/GsmInboundSmsHandler( 1775): entering Delivering state
D/GsmInboundSmsHandler( 1775): leaving Delivering state
D/GsmInboundSmsHandler( 1775): entering Idle state
V/SmsMessage( 2124): SMS originating address: +8613715338315
V/SmsMessage( 2124): SMS TP-PID:0 data coding scheme: 0
D/SmsMessage( 2124): SMS SC timestamp: 1572253549000
V/SmsMessage( 2124): SMS message body (raw): 'jchfbfh'
D/GsmInboundSmsHandler( 1775): Idle state processing message type 5
D/GsmInboundSmsHandler( 1775): mWakeLock released

一、短信接收

1. vendor ril接收到modem上报的短信息

hardware/ril/reference-ril/reference-ril.c

static void onUnsolicited (const char *s, const char *sms_pdu)
{
... ...
if (strStartsWith(s, "+CMT:")) {
RIL_onUnsolicitedResponse (
RIL_UNSOL_RESPONSE_NEW_SMS, /* 上报UNSOL_RESPONSE_NEW_SMS消息 */
sms_pdu, strlen(sms_pdu));
}
... ...
}

2. RILD把短信息发送到RILJ

hardware/ril/libril/ril.cpp

extern "C"
void RIL_onUnsolicitedResponse(int unsolResponse, void *data,
size_t datalen)
{
... ...
unsolResponseIndex = unsolResponse - RIL_UNSOL_RESPONSE_BASE; /* 找出消息在s_unsolResponses[]的索引 */
... ... switch (s_unsolResponses[unsolResponseIndex].wakeType) { /* 禁止进入休眠 */
case WAKE_PARTIAL:
grabPartialWakeLock();
shouldScheduleTimeout = true;
break;
... ...
}
... ...
ret = s_unsolResponses[unsolResponseIndex] /* 调用消息处理函数responseString() */
.responseFunction(p, data, datalen);
... ... ret = sendResponse(p); /* 发送Parcel中的信息内容到服务端RILJ */
} static UnsolResponseInfo s_unsolResponses[] = {
... ...
/* 消息对应的消息处理函数,新信息到来会唤醒系统 */
{RIL_UNSOL_RESPONSE_NEW_SMS, responseString, WAKE_PARTIAL},
... ...
}; static int responseString(Parcel &p, void *response, size_t responselen) {
/* one string only */
startResponse;
appendPrintBuf("%s%s", printBuf, (char*)response);
closeResponse; writeStringToParcel(p, (const char *)response); /* 把字符串格式的信息存到Parcel容器中 */ return ;
}

二、解析短信息

1. RILJ获取短信息

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

private void
processUnsolicited (Parcel p) {
... ...
case RIL_UNSOL_RESPONSE_NEW_SMS: ret = responseString(p); break;
... ... switch(response) {
... ...
case RIL_UNSOL_RESPONSE_NEW_SMS: {
if (RILJ_LOGD) unsljLog(response); /* 参考log:[UNSL]< UNSOL_RESPONSE_NEW_SMS */
// FIXME this should move up a layer
String a[] = new String[2]; a[1] = (String)ret; SmsMessage sms; sms = SmsMessage.newFromCMT(a); /* 解析PDU格式的短信息 */
if (mGsmSmsRegistrant != null) {
mGsmSmsRegistrant
.notifyRegistrant(new AsyncResult(null, sms, null));
}
break;
}
... ...
}
... ...
} private Object
responseString(Parcel p) {
String response; response = p.readString(); /* 信息内容转换成Object */ return response;
}

2. 解析短信息

SmsMessage.newFromCMT(a);根据import android.telephony.SmsMessage,得知代码路径:

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

public static SmsMessage newFromCMT(String[] lines) {
// received SMS in 3GPP format
SmsMessageBase wrappedMessage =
com.android.internal.telephony.gsm.SmsMessage.newFromCMT(lines); /* 是对另一个newFromCMT的封装,因为有gsm和cdma两种短信,
* 即cdma中也有newFromCMT,根据情况按需选择
*/ return new SmsMessage(wrappedMessage);
}

com.android.internal.telephony.gsm.SmsMessage.newFromCMT(lines)的实现在

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

public class SmsMessage extends SmsMessageBase {

    ... ...

    public static SmsMessage newFromCMT(String[] lines) {
try {
SmsMessage msg = new SmsMessage();
msg.parsePdu(IccUtils.hexStringToBytes(lines[1])); /* 解析PDU短信 */
return msg;
} catch (RuntimeException ex) {
Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
} ... ...
}

IccUtils.hexStringToBytes(lines[1])把十六进制的字符串转换成字节数组msg.parsePdu()解析这个数组的内容,最后获得短信内容
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/SmsMessage.java

private void parsePdu(byte[] pdu) {
... ...
mScAddress = p.getSCAddress(); if (mScAddress != null) {
if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress); /* 参考log:SMS SC address: +8613800755500 */
} ... ...
mMti = firstByte & 0x3;
switch (mMti) {
... ...
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); /* 对短信类型为Deliver的短信进行解析 */
break;
... ...
}
... ...
} private void parseSmsDeliver(PduParser p, int firstByte) {
... ...
mOriginatingAddress = p.getAddress(); if (mOriginatingAddress != null) {
if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: " /* 参考log: SMS originating address: +861371533xxxx */
+ mOriginatingAddress.address);
} ... ... mProtocolIdentifier = p.getByte(); // TP-Data-Coding-Scheme
// see TS 23.038
mDataCodingScheme = p.getByte(); if (VDBG) {
Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+ " data coding scheme: " + mDataCodingScheme); /* 参考log: SMS TP-PID:0 data coding scheme: 0 */
} mScTimeMillis = p.getSCTimestampMillis(); if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis); /* 参考log:SMS SC timestamp: 1571831129000 */ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40; parseUserData(p, hasUserDataHeader); /* 解析信息有效内容 */ ... ...
} private void parseUserData(PduParser p, boolean hasUserDataHeader) {
... ...
if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'"); /* 短信内容,参考log: SMS message body (raw): 'jchfbfh' */
... ...
}

三、处理短信息

对用户有效的短信内容,最终保存在类型为String的mMessageBody变量中,该变量属于SmsMessageBase抽象类,而
SmsMessage继承于SmsMessageBase。
        回到前面frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java中processUnsolicited(),
sms = SmsMessage.newFromCMT(a);解析完短信息后,返回一个SmsMessage并通知上层应用。

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

mGsmSmsRegistrant
.notifyRegistrant(new AsyncResult(null, sms, null)); /* 把sms转成Object类型 */

frameworks/base/core/java/android/os/AsyncResult.java

public class AsyncResult
{
... ...
/** please note, this sets m.obj to be this */
public
AsyncResult (Object uo, Object r, Throwable ex)
{
userObj = uo;
result = r;
exception = ex;
}
... ...
}

根据mGsmSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null));找到mGsmSmsRegistrant注册的代码:

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

public abstract class BaseCommands implements CommandsInterface {
... ... @Override
public void setOnNewGsmSms(Handler h, int what, Object obj) { /* mGsmSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null))中的mGsmSmsRegistrant是在这里创建的 */
mGsmSmsRegistrant = new Registrant (h, what, obj);
} ... ...
}

封装消息EVENT_NEW_SMS消息

frameworks/base/core/java/android/os/Registrant.java

public class Registrant
{
public
Registrant(Handler h, int what, Object obj) /* 传入需要处理消息为what的事件处理Handler h,obj为事件内容,参考phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null); */
{
refH = new WeakReference(h);
this.what = what;
userObj = obj;
} ... ... /**
* This makes a copy of @param ar
*/
public void
notifyRegistrant(AsyncResult ar) /* 参考mGsmSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null)) */
{
internalNotifyRegistrant (ar.result, ar.exception); /* ar.result为sms */
} /*package*/ void
internalNotifyRegistrant (Object result, Throwable exception) /* internalNotifyRegistrant (sms, Throwable exception) */
{
Handler h = getHandler(); if (h == null) {
clear();
} else {
Message msg = Message.obtain(); /* 创建一个消息 */ msg.what = what; /* 消息类型EVENT_NEW_SMS */ msg.obj = new AsyncResult(userObj, result, exception); /* 消息内容sms */ h.sendMessage(msg); /* 发送消息到注册了这个消息的Handler,参考phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null);的getHandler() */
}
} ... ...
}

然而BaseCommands是一个抽象类,实现了CommandsInterface中的setOnNewGsmSms接口,这个接口由GsmInboundSmsHandler调用
(phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null)),也就是说GsmInboundSmsHandler的getHandler()是EVENT_NEW_SMS
的监听者,也就是说frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java中mGsmSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null))
调用之后,会触发GsmInboundSmsHandler中getHandler()的Handler对EVENT_NEW_SMS消息进行解析。这个Handler肯定是GsmInboundSmsHandler
实例化的对象中的,这个对象在什么时候,在哪里创建的,暂且不管。我们只管EVENT_NEW_SMS这个消息从哪里来,然后到哪里去
就行了。

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

public final class ImsSMSDispatcher extends SMSDispatcher {
... ...
mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(), /* 获取mGsmInboundSmsHandler,并启动状态机 */
storageMonitor, phone);
... ...
}

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

public class GsmInboundSmsHandler extends InboundSmsHandler {
... ...
/**
* Create a new GSM inbound SMS handler.
*/
private GsmInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
PhoneBase phone) {
super("GsmInboundSmsHandler", context, storageMonitor, phone, /* 构造GsmInboundSmsHandler时,通过super()调用InboundSmsHandler的构造函数 */
GsmCellBroadcastHandler.makeGsmCellBroadcastHandler(context, phone));
phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null); /* 注册EVENT_NEW_SMS消息 */
mDataDownloadHandler = new UsimDataDownloadHandler(phone.mCi);
} ... ... /**
* Wait for state machine to enter startup state. We can't send any messages until then.
*/
public static GsmInboundSmsHandler makeInboundSmsHandler(Context context,
SmsStorageMonitor storageMonitor, PhoneBase phone) {
GsmInboundSmsHandler handler = new GsmInboundSmsHandler(context, storageMonitor, phone); /* 实例化GsmInboundSmsHandler */
handler.start(); /* 抽象类InboundSmsHandler继承与StateMachine,而GsmInboundSmsHandler继承于InboundSmsHandler,
* GsmInboundSmsHandler调用启动状态机方法start()
*/
return handler;
} ... ...
}

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

public abstract class InboundSmsHandler extends StateMachine {
... ...
protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor,
PhoneBase phone, CellBroadcastHandler cellBroadcastHandler) {
... ... addState(mDefaultState); /* 构造InboundSmsHandler时,添加状态机的状态 */
addState(mStartupState, mDefaultState);
addState(mIdleState, mDefaultState);
addState(mDeliveringState, mDefaultState);
addState(mWaitingState, mDeliveringState); setInitialState(mStartupState); /* 初始化状态机 */
if (DBG) log("created InboundSmsHandler");
} ... ... class IdleState extends State {
@Override
public void enter() {
if (DBG) log("entering Idle state");
sendMessageDelayed(EVENT_RELEASE_WAKELOCK, WAKELOCK_TIMEOUT);
} @Override
public void exit() {
mWakeLock.acquire();
if (DBG) log("acquired wakelock, leaving Idle state");
} @Override
public boolean processMessage(Message msg) {
if (DBG) log("Idle state processing message type " + msg.what);
switch (msg.what) {
case EVENT_NEW_SMS: /* 空闲时,接收到短信 */
case EVENT_BROADCAST_SMS:
deferMessage(msg);
transitionTo(mDeliveringState); /* 转到mDeliveringState */
return HANDLED; ... ...
}
}
}
... ... class DeliveringState extends State { /* 转到mDeliveringState状态 */
@Override
public void enter() {
if (DBG) log("entering Delivering state");
} @Override
public void exit() {
if (DBG) log("leaving Delivering state");
} @Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case EVENT_NEW_SMS:
// handle new SMS from RIL
handleNewSms((AsyncResult) msg.obj); /* 处理新SMS */
sendMessage(EVENT_RETURN_TO_IDLE); /* 处理完回到空闲状态 */
return HANDLED; ... ...
}
}
... ...
}
} void handleNewSms(AsyncResult ar) {
... ...
SmsMessage sms = (SmsMessage) ar.result;
result = dispatchMessage(sms.mWrappedSmsMessage);
... ...
} public int dispatchMessage(SmsMessageBase smsb) {
... ...
return dispatchMessageRadioSpecific(smsb);
... ...
}

通过以上流程可以了解到,当状态机接收到SMS后,对消息进行分发,针对type zero, SMS-PP data download,
和3GPP/CPHS MWI type SMS判断,如果是Normal SMS messages,则调用dispatchNormalMessage(smsb),然后创建
一个InboundSmsTracker对象,把信息保存到raw table,然后在通过sendMessage(EVENT_BROADCAST_SMS, tracker)
把消息广播出去。

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

class DeliveringState extends State {
... ...
public boolean processMessage(Message msg) {
switch (msg.what) {
... ...
case EVENT_BROADCAST_SMS: /* 接收到EVENT_BROADCAST_SMS消息并处理 */
// if any broadcasts were sent, transition to waiting state
if (processMessagePart((InboundSmsTracker) msg.obj)) {
transitionTo(mWaitingState);
}
return HANDLED;
... ...
}
}
... ... } boolean processMessagePart(InboundSmsTracker tracker) {
... ...
BroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker); /* 创建一个广播接收者,用来处理短信广播的结果 */
... ...
intent = new Intent(Intents.SMS_DELIVER_ACTION); /* 设置当前intent的action为SMS_DELIVER_ACTION */ // Direct the intent to only the default SMS app. If we can't find a default SMS app
// then sent it to all broadcast receivers.
ComponentName componentName = SmsApplication.getDefaultSmsApplication(mContext, true); /* 这个action只会发送给carrier app,而且carrier app可以通过set result为RESULT_CANCELED来终止这个广播 */
if (componentName != null) {
// Deliver SMS message only to this receiver
intent.setComponent(componentName);
log("Delivering SMS to: " + componentName.getPackageName() +
" " + componentName.getClassName());
}
... ...
dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS, /* 广播intent */
AppOpsManager.OP_RECEIVE_SMS, resultReceiver);
... ...
} private final class SmsBroadcastReceiver extends BroadcastReceiver {
... ...
public void onReceive(Context context, Intent intent) {
... ...
// Now that the intents have been deleted we can clean up the PDU data.
if (!Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
&& !Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
&& !Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) {
loge("unexpected BroadcastReceiver action: " + action);
} int rc = getResultCode();
if ((rc != Activity.RESULT_OK) && (rc != Intents.RESULT_SMS_HANDLED)) {
loge("a broadcast receiver set the result code to " + rc
+ ", deleting from raw table anyway!");
} else if (DBG) {
log("successful broadcast, deleting from raw table.");
} deleteFromRawTable(mDeleteWhere, mDeleteWhereArgs);
sendMessage(EVENT_BROADCAST_COMPLETE); /* 成功广播 */ ... ...
}
... ...
}

到这里,在应用层注册具有Intents.SMS_RECEIVED_ACTION这样action的广播,就可以获取到短信了。

Android4.4 RIL短信接收流程分析的更多相关文章

  1. Android短彩信源码解析-短信发送流程(二)

    转载请注明出处:http://blog.csdn.net/droyon/article/details/11699935 2,短彩信发送framework逻辑 短信在SmsSingleRecipien ...

  2. Android系统应用Mms之Sms短信发送流程(Mms应用部分)二

    1. 新建一条短信, 在发送短信之前, 首先创建的是一个会话Conversation, 以后所有与该接收人(一个或多个接收人)的消息交互, 都在该会话Conversation中. ComposeMes ...

  3. 无废话Android之activity的生命周期、activity的启动模式、activity横竖屏切换的生命周期、开启新的activity获取他的返回值、利用广播实现ip拨号、短信接收广播、短信监听器(6)

    1.activity的生命周期 这七个方法定义了Activity的完整生命周期.实现这些方法可以帮助我们监视其中的三个嵌套生命周期循环: (1)Activity的完整生命周期 自第一次调用onCrea ...

  4. Android短彩信源码解析-短信发送流程(三)

    3.短信pdu的压缩与封装 相关文章: ------------------------------------------------------------- 1.短信发送上层逻辑 2.短信发送f ...

  5. android4.4.2 短信广播变更

    近期三星陆续放出android4.4.2ROM更新包,android4.4.2对短信虽说是放开了权限,但其实是加强了限制,一台手机智能通过一个设置为默认应用的短信应用软件才能发送短信,否则就无法对短信 ...

  6. Android短信监听实现,及Android4.4之后短信机制变更

    前阵子公司有一个项目,简单的监听短信应用,功能只有如下两个: 1.监听短信并获取短信内容上传服务器: 2.从服务器获取短信内容,发送出去    按照传统的思路,监听短信我们有两种方式:第一种是使用广播 ...

  7. Android4.4 往短信收件箱中插入自定义短信(伪造短信)

    这段时间稍微有点空闲,把前一段学习Android做过的一些小项目整理整理.虽然没有什么工程量很大的项目,但是对于一个新手,解决这些问题还是花了一段时间.感觉还是非常有记录的意义呢~~~么么哒*—* 今 ...

  8. Android学习笔记(二十二)——短信接收与发送

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 当手机接收到一条短信的时候, 系统会发出一条值为 android.provider.Telephony.SMS ...

  9. Android4.0源码Launcher启动流程分析【android源码Launcher系列一】

    最近研究ICS4.0的Launcher,发现4.0和2.3有稍微点区别,但是区别不是特别大,所以我就先整理一下Launcher启动的大致流程. Launcher其实是贯彻于手机的整个系统的,时时刻刻都 ...

随机推荐

  1. redis的主从复制,以及使用sentinel自动处理主机宕机问题,集群

    以下部分想看懂得有一定的redis基础,且步骤是连贯的,错一步都不行.redis运行多个实例,不懂得自行百度. 1. redis主从同步 原理: 从服务器向主服务器发送 SYNC 命令. 接到 SYN ...

  2. tcpdump 详解

    目录 简介 安装 参数详解 案例 监听指定主机的数据包 监视指定主机和端口的数据包 监视指定网络的数据包 监视指定协议的数据包 使用tcpdump抓取HTTP包 简介 用简单的话来定义tcpdump, ...

  3. 虚拟机中linux操作系统raid10(5块磁盘)配置流程及损坏磁盘的移除

    打开所要用的虚拟机,点击编辑虚拟机设置,点击硬盘,添加 2.一直点击下一步不做修改,直到最后完成 3.按照以上步骤添加5块磁盘 4.点击开启虚拟机,输入用户名root密码登录进去 5.进入虚拟机后,鼠 ...

  4. mysql那些事(4)建库建表编码的选择

    mysql建数据库或者建表的时候会遇到选择编码的问题,以前我们都是习惯性的选择utf8,但是在mysql在5.5.3版本后加了utf8mb4的编码,utf8mb4可以存4个字节Unicode,mb4就 ...

  5. 《吊打面试官》系列-HashMap

    你知道的越多,你不知道的越多 点赞再看,养成习惯 本文 GitHub https://github.com/JavaFamily 上已经收录,有一线大厂面试点思维导图,也整理了很多我的文档,欢迎Sta ...

  6. 索引很难么?带你从头到尾捋一遍MySQL索引结构,不信你学不会!

    前言 Hello我又来了,快年底了,作为一个有抱负的码农,我想给自己攒一个年终总结.自上上篇写了手动搭建Redis集群和MySQL主从同步(非Docker)和上篇写了动手实现MySQL读写分离and故 ...

  7. 链接脚本(Linker Script)用法解析(二) clear_table & copy_table

    可执行文件中的.bss段和.data段分别存放未赋初值的全局变量和已赋初值的全局变量,两者的特点分别为: (1).bss段:①无初值,所以不占ROM空间:②运行时存储于RAM:③默认初值为0 (2). ...

  8. 宜信SDL实践:产品经理如何驱动产品安全建设

    一.序言 本文从产品经理的角度出发,对产品经理的安全职责.产品驱动安全的内涵.工作内容.工作方法.所需安全资源.以及产品经理的安全工作量进行了分析.希望所有产品经理在没有心理负担的情况下,有目标.有方 ...

  9. 上手spring boot项目(四)之springboot如何返回json数据

    在springboot整合thymeleaf中,经常会在HTML页面中接收来自服务器的json数据,然后处理json数据并在页面上渲染.那么如何在服务器中返回json类型的数据呢? 1.使用@Resp ...

  10. textView的用法及技巧

    转自:http://bbs.9ria.com/thread-244445-1-1.html 一.新建一个textView //初始化 UITextView *textView = [[[UITextV ...