详解Android4.4 RIL短信接收流程分析

最近有客户反馈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 0;
}

二、解析短信息

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短信接收流程分析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • Android短信接收监听、自动回复短信操作例子

    定义广播接收器的Action: 复制代码 代码如下: private static final String TAG ="SmsService";/*** 信息发送状态广播*/private static final String ACTION_SMS_SEND  = "com.SmsService.send";  /*** 信息接收状态广播*/private static final String ACTION_SMS_DELIVERY = "com.S

  • Android项目实现短信的发送、接收和对短信进行拦截

    说实话,关于Android中对短信的一些相关操作是一个比较入门的东西.那我现在还要来写这一篇博客的原因只是因为现在开发中有相关内容,而又想将这些东西分享给更多的人来学习,同时在以后对Android系统的短信进行其他学习的时候也就放在这里做一个记录了,于是就写了这篇啰嗦的文章.如果你觉得这是一个不错的东西,欢迎收藏,以便在以后更方便地查看本人在此篇文章中更新的内容.下面我就从标题中的三个方面来对Android系统中的短信操作进行一个简单地学习. 短信的发送 由于Android中对短信发送方法的优良

  • Android创建简单发送和接收短信应用

    本文实例为大家分享了Android创建发送和接收短信应用的简单实现过程,供大家参考,具体内容如下 一.接收短信 项目的结构如下:一个简单的接收和发送短信的功能 1.定义一个接收短信的广播:当手机接收到一条短信的时候,系统会发出一条值为android.provider.Telephony.SMS_RECEIVED 的广播,这条广播里携带着与短信相关的所有数据. // 接收短信的广播 class MessageReceiver extends BroadcastReceiver { @Overrid

  • Android广播接收机制详细介绍(附短信接收实现)

    Android中广播(BroadcastReceiver)的详细讲解. 1. BroadcastReceiver的注册过程: (1).广播消息发出来后,只有订阅了该广播的对象才会接收发出来的广播消息并做出相应处理. **(2).**Android广播分为两个方面:广播发送者和广播接收者.Android中的广播使用了观察者模式,基于消息的发布/订阅事件模型.广播接收者通过Binder机制向AMS进行注册,AMS查找符合相应条件的Broadcastreceiver,将广播发送到BroadcastRe

  • Android接收和发送短信处理

    关于短信接收处理方面,当前已经有一些app做的比较好了,比如发给手机发验证码验证的问题,很多app在手机接收到验证码后,不需要输入,就直接可以跳过验证界面,这就是用到了对接收到的短信的处理.至于短信的发送,也没什么好说的了.在此也只是附上一个小实例. 效果图: MainActivity: import android.app.Activity; import android.app.PendingIntent; import android.content.BroadcastReceiver;

  • Android接收和发送短信的实现代码

    Android收到短信时会广播android.provider.Telephony.SMS_RECEIVED消息,因此只要定义一个Receiver,收听该消息,就能接收短信. <receiver android:name=".smsReceiver" > <intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </in

  • 详解Android短信的发送和广播接收实现短信的监听

    本文介绍了Android短信的发送和广播接收者实现短信的监听,要注意Android清单中权限的设置以及广播的注册监听实现,废话不多说,代码如下: 以下就是 Android清单的XML AndroidManifest.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.zyw.broadcastsendsms" android:versionC

  • 详解Android4.4 RIL短信接收流程分析

    最近有客户反馈Android接收不到短信,于是一头扎进RIL里面找原因.最后发现不是RIL的问题,而是BC72上报 短信的格式不对,AT+CNMA=1无作用等几个小问题导致的.尽管问题不在RIL,但总算把RIL短信接收流程搞清楚了. 接收到新信息的log: D/ATC ( 1269): AT< +CMT:,27 D/ATC ( 1268): AT< 0891683108705505F0040d91683117358313f500009101329154922307ea31da2c36a301

  • Springboot详解RocketMQ实现消息发送与接收流程

    springboot+rockermq 实现简单的消息发送与接收 普通消息的发送方式有3种:单向发送.同步发送和异步发送. 下面来介绍下 springboot+rockermq 整合实现 普通消息的发送与接收 创建Springboot项目,添加rockermq 依赖 <!--rocketMq依赖--> <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-

  • springboot与vue详解实现短信发送流程

    目录 一.前期工作 1.开启邮箱服务 2.导入依赖 3.配置application.yaml文件 二.实现流程 1.导入数据库 2.后端实现 编写实体类 编写工具类ResultVo 编写dao层接口 配置dao层接口的数据库操作 编写service层接口 编写service层的实现方法 实现controller层 Test代码 前端页面的实现 运行截图+sql图 总结 一.前期工作 1.开启邮箱服务 开启邮箱的POP3/SMTP服务(这里以qq邮箱为例,网易等都是一样的) 2.导入依赖 在spr

  • 详解监听MySQL的binlog日志工具分析:Canal

    Canal是阿里巴巴旗下的一款开源项目,利用Java开发.主要用途是基于MySQL数据库增量日志解析,提供增量数据订阅和消费,目前主要支持MySQL. GitHub地址:https://github.com/alibaba/canal 在介绍Canal内部原理之前,首先来了解一下MySQL Master/Slave同步原理: MySQL master启动binlog机制,将数据变更写入二进制日志(binary log, 其中记录叫做二进制日志事件binary log events,可以通过sho

  • 详解Java线程池和Executor原理的分析

    详解Java线程池和Executor原理的分析 线程池作用与基本知识 在开始之前,我们先来讨论下"线程池"这个概念."线程池",顾名思义就是一个线程缓存.它是一个或者多个线程的集合,用户可以把需要执行的任务简单地扔给线程池,而不用过多的纠结与执行的细节.那么线程池有哪些作用?或者说与直接用Thread相比,有什么优势?我简单总结了以下几点: 减小线程创建和销毁带来的消耗 对于Java Thread的实现,我在前面的一篇blog中进行了分析.Java Thread与内

  • 详解关于mybatis-plus中Service和Mapper的分析

    在后端开发过程中,如果有用到mybatis-plus,肯定会发现在其内部存在着两种数据库操作接口,Iservice和BaseMapper,如果只是用增删改查会发现两者的功能是一致的,除了方法名称有所不同,其他的基本相似.对此,我颇为好奇,便打开两个接口的源码进行对比. 先演示一下基本开发中的继承关系,手动创建的Service继承于ServiceImpl,并加载自己创建的Mapper @Service public class RestDeptService extends ServiceImpl

  • 详解Django中的FBV和CBV对比分析

    在学习Django过程中在views.py进行逻辑处理时接触到了两种视图的书写风格,FBV和CBV FBV 指 function based views,即基于函数的视图 CBV 指 class based views,即基于类的视图 基于类的视图相较于基于函数的视图可以更加便利的实现类的继承封装等.在日常使用的时候,二者的区别主要在于对于request的请求方法的处理方式 FBV 我们通过函数传入的request的method来判断客户端发起的是什么请求,并进行相应的操作,返回相应的数据. d

  • 详解OpenCV执行连通分量标记的方法和分析

    目录 1.OpenCV 连通分量标记和分析 1.1 OpenCV 连通分量标记和分析函数 1.2 项目结构 2.案例实现 2.1 使用 OpenCV 实现基本的连通分量标记 2.2 完整代码 2.3 过滤连通分量 2.4 C++代码案例 在本教程中,您将学习如何使用 OpenCV 执行连通分量标记和分析.具体来说,我们将重点介绍 OpenCV 最常用的连通分量标记函数:cv2.connectedComponentsWithStats. 连通分量标记(也称为连通分量分析.斑点提取或区域标记)是图论

  • 详解Node.js 应用高 CPU 占用率分析方法

    目录 本地运行 Node.js 应用 如何采集生产系统上的 Node.js 应用性能数据 本地运行 Node.js 应用 我们在本地运行 Node.js 应用,使用 --inspect 标志启动应用程序,再次执行负载测试,在 Chrome 浏览器中打开 chrome://inspect: 单击应用下方的 inspect 按钮,然后开始 CPU 占用率分析: 等待一段时间后,就能看到 CPU profile 的结果: 如何采集生产系统上的 Node.js 应用性能数据 在大多数情况下,如果性能问题

随机推荐