近有客户反馈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. Java 从入门到进阶之路(十)

    之前的文章我们介绍了一下 Java 中的引用型数组类型,接下来我们再来看一下 Java 中的继承. 继承的概念 继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类. 继承就是子类继 ...

  2. 爬取电影top250 电影名 导演 演员 风格 国家 时长 评分 录入mySQL数据库

    import requestsfrom lxml import etreeimport reimport pymysqlimport time conn = pymysql.connect(host= ...

  3. maven中的setting文件

      localRepository默认jar包下载到本地哪个目录下 pluginGroups 把自己的插件放在这里进行管理 这样不用写groupId和artifactId     一个生命周期包含很多 ...

  4. MQTT版本升级过程及源码解析

    MQTT版本升级过程及源码解析 首先说一下为什么要写这篇文章呢,在我发现网上对MQTT的文章介绍实在太少了,可能也是使用这个的频率比较低吧!还有对问题的定位以及解决的方式和办法也太少了,所以特意写这篇 ...

  5. js5——页面转换

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  6. 如何在DevOps中实施连续测试

    在过去的十年中,对软件开发的需求已急剧发展.软件已成为公司获得竞争优势的关键优势,特别是如果您的公司属于SaaS范畴.通过在SDLC中实施瀑布等传统流程,组织现在正在向敏捷过渡,以便以更快的速度在市场 ...

  7. 浏览器url访问tomcat出现错误 java.lang.NoSuchMethodError解决方法

    一般该类错误: 找不到方法或找不到类, 都是maven pom 仓库依赖的问题,有时是 因为缺少该依赖类,可以考虑添加相关依赖: 有时因为依赖冲突, 可以到 maven 的仓库下面把 有关该类的包 全 ...

  8. mac eclipse maven tomcat 运行错误 tomcat HTTP Status 404

    在mac系统下安装好eclipse, maven以及tomcat, 之后运行一个web的helloworld项目,出现错误 tomcat HTTP Status 404. 查看eclipse cons ...

  9. [Python Basics]引用系统(The Import System)

    欲上高楼去避愁,愁还随我上高楼.经行几处江山改,多少亲朋尽白头. 归休去,去归休.不成人总要封侯?浮云出处元无定,得似浮云也自由. 我从短暂的Python工作当中学到一件事,越是模块化,就越会发现py ...

  10. vue项目中使用腾讯地图

    最近在使用腾讯地图api(以下以位置数据可视化API为例),在初建项目之后,按照官网的说法,直接引入 再将官网的初始化例子放一个方法 在mounted中调用即可看到腾讯地图,但是我引入之后,一直报TM ...