转载:https://blog.csdn.net/linyongan/article/details/51406123

本文主要讲述Android 9.0 SIM卡初始化流程,这个过程也涉及到UICC框架的初始化,UICC(Universal Integrated Circuit Card)的架构图如下:
主要的类及其作用:
UiccController:整个Uicc相关信息的控制接口,监控SIM状态变化
UiccCard:Uicc卡的抽象,用来更新卡的状态
IccCardStatus:维护Uicc卡的状态,CardState&PinState
UiccCardApplication:Uicc的一个具体的应用,负责卡中数据读写,存取,pin和puk密码设置,解锁
CatService:主要负责SIM Tollkit相关
IccConstants:SIM卡中文件地址,不同数据在SIM卡上的字段地址。
IccRecords:记录SIM卡的数据
IccFileHandler:读取SIM卡数据以及处理接收的结果。
一、实例化UiccController对象

UiccController是整个UICC相关信息的控制接口,UiccController的实例化就是在RIL与UiccController 之间建立监听关系,这样的话,当SIM卡状态发生变化时,UiccController就可以马上知道并且做出相应的操作。
UiccController对象是在PhoneFacotry.java中的makeDefaultPhone()方法中初始化的,有个细节值得注意的是sCommandsInterfaces数组的i对应的是PhoneId。

    public static void makeDefaultPhone(Context context) {// Instantiate UiccController so that all other classes can just//得到UiccController对象mUiccController = UiccController.make(context, sCommandsInterfaces);for (int i = 0; i < numPhones; i++) {PhoneBase phone = null;int phoneType = TelephonyManager.getPhoneType(networkModes[i]);if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {//sCommandsInterfaces的i对应的是PhoneId;第二小节会再次提到这个i值phone = TelephonyPluginDelegate.getInstance().makeGSMPhone(context,sCommandsInterfaces[i], sPhoneNotifier, i);} else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {phone = TelephonyPluginDelegate.getInstance().makeCDMALTEPhone(context,sCommandsInterfaces[i], sPhoneNotifier, i);}Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);sProxyPhones[i] = TelephonyPluginDelegate.getInstance().makePhoneProxy(phone);}}@TelephonyPluginBase.javapublic PhoneBase makeGSMPhone(Context context, CommandsInterface ci,PhoneNotifier notifier, int phoneId) {return new GSMPhone(context, ci, notifier, phoneId);}}

在UiccController.java的make()方法中new了一个UiccController对象,

    public static UiccController make(Context c, CommandsInterface[] ci) {synchronized (mLock) {if (mInstance != null) {throw new RuntimeException("MSimUiccController.make() should only be called once");}//实例化UiccController对象mInstance = new UiccController(c, ci);return (UiccController)mInstance;}}private UiccController(Context c, CommandsInterface []ci) {if (DBG) log("Creating UiccController");mContext = c;mCis = ci;for (int i = 0; i < mCis.length; i++) {//index对应的是PhoneIdInteger index = new Integer(i);//注册监听四种事件mCis[i].registerForIccStatusChanged(this, EVENT_ICC_STATUS_CHANGED, index);mCis[i].registerForAvailable(this, EVENT_ICC_STATUS_CHANGED, index);mCis[i].registerForNotAvailable(this, EVENT_RADIO_UNAVAILABLE, index);mCis[i].registerForIccRefresh(this, EVENT_SIM_REFRESH, index);}}

在上面UiccController的构造方法中可以看到,注册了三个事件EVENT_ICC_STATUS_CHANGED(监听SIM卡的状态变化),EVENT_RADIO_UNAVAILABLE(一旦radio变成不可用状态,就清空SIM卡的信息),EVENT_SIM_REFRESH。index对应的是PhoneId,当上面这三种消息上来时,就知道对应哪个Phone对象,也就对应那张卡。
当接收到EVENT_ICC_STATUS_CHANGED消息后,UiccController调用RIL.java的getIccCardStatus()方法给MODEM发送RIL_REQUEST_GET_SIM_STATUS消息,查询SIM卡的状态。

    public void handleMessage (Message msg) {.....//1.首先从Message中取出PhoneIdInteger index = getCiIndex(msg);.....case EVENT_ICC_STATUS_CHANGED:if (DBG) log("Received EVENT_ICC_STATUS_CHANGED, calling getIccCardStatus");//2.查询当前SIM卡的状态mCis[index].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE, index));break;case EVENT_GET_ICC_STATUS_DONE:if (DBG) log("Received EVENT_GET_ICC_STATUS_DONE");//3.处理查询到的状态信息onGetIccCardStatusDone(ar, index);break;}

二、实例化UiccCard对象

当查询SIM卡的状态完毕后,先从result中解析出IccCardStatus,依据IccCardStatus来创建UiccCard对象,一个UiccCard 对象代表着一张SIM卡;如果UiccCard对象已存在就直接调它的update()方法更新UiccCard的信息。

    private synchronized void onGetIccCardStatusDone(AsyncResult ar, Integer index) {if (ar.exception != null) {Rlog.e(LOG_TAG,"Error getting ICC status. "+ "RIL_REQUEST_GET_ICC_STATUS should "+ "never return an error", ar.exception);return;}if (!isValidCardIndex(index)) {Rlog.e(LOG_TAG,"onGetIccCardStatusDone: invalid index : " + index);return;}IccCardStatus status = (IccCardStatus)ar.result;//1.创建或更新UiccCardif (mUiccCards[index] == null) {//Create new card(Android6.0 调的是4个参数的构造方法)mUiccCards[index] = new UiccCard(mContext, mCis[index], status, index);} else {//Update already existing cardmUiccCards[index].update(mContext, mCis[index] , status);}if (DBG) log("Notifying IccChangedRegistrants");//2.通知监听UiccController的监听者mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null));}

在UiccCard的构造方法中,最终还是调用了update()方法

    public UiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId) {mCardState = ics.mCardState;mPhoneId = phoneId;update(c, ci, ics);}public void update(Context c, CommandsInterface ci, IccCardStatus ics) {synchronized (mLock) {CardState oldState = mCardState;mCardState = ics.mCardState;mUniversalPinState = ics.mUniversalPinState;mGsmUmtsSubscriptionAppIndex = ics.mGsmUmtsSubscriptionAppIndex;mCdmaSubscriptionAppIndex = ics.mCdmaSubscriptionAppIndex;mImsSubscriptionAppIndex = ics.mImsSubscriptionAppIndex;mContext = c;mCi = ci;//更新UiccApplicationsif (DBG) log(ics.mApplications.length + " applications");for ( int i = 0; i < mUiccApplications.length; i++) {if (mUiccApplications[i] == null) {//Create newly added Applicationsif (i < ics.mApplications.length) {mUiccApplications[i] = new UiccCardApplication(this,ics.mApplications[i], mContext, mCi);}} else if (i >= ics.mApplications.length) {//Delete removed applicationsmUiccApplications[i].dispose();mUiccApplications[i] = null;} else {//Update the restmUiccApplications[i].update(ics.mApplications[i], mContext, mCi);}}//创建CatService,用于读取STK的信息createAndUpdateCatService();// Reload the carrier privilege rules if necessary.log("Before privilege rules: " + mCarrierPrivilegeRules + " : " + mCardState);if (mCarrierPrivilegeRules == null && mCardState == CardState.CARDSTATE_PRESENT) {mCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(this,mHandler.obtainMessage(EVENT_CARRIER_PRIVILIGES_LOADED));} else if (mCarrierPrivilegeRules != null && mCardState != CardState.CARDSTATE_PRESENT) {mCarrierPrivilegeRules = null;}sanitizeApplicationIndexes();//先获取radio的状态RadioState radioState = mCi.getRadioState();if (DBG) log("update: radioState=" + radioState + " mLastRadioState="+ mLastRadioState);// No notifications while radio is off or we just powering up//根据radio的状态和卡的状态来判断有没有插拔SIM卡动作if (radioState == RadioState.RADIO_ON && mLastRadioState == RadioState.RADIO_ON) {if (oldState != CardState.CARDSTATE_ABSENT &&mCardState == CardState.CARDSTATE_ABSENT) {if (DBG) log("update: notify card removed");mAbsentRegistrants.notifyRegistrants();mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARD_REMOVED, null));} else if (oldState == CardState.CARDSTATE_ABSENT &&mCardState != CardState.CARDSTATE_ABSENT) {if (DBG) log("update: notify card added");mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARD_ADDED, null));}}mLastRadioState = radioState;}}

在UiccCard.java的update()方法中,实例化了UiccCardApplication对象,或者调UiccCardApplication的update()方法更新状态。mCardState记录着卡的状态,根据新旧mCardState就可以知道CARD_ADDED或者CARD_REMOVED。
三、实例化UiccCardApplication对象

    UiccCardApplication(UiccCard uiccCard,IccCardApplicationStatus as,Context c,CommandsInterface ci) {if (DBG) log("Creating UiccApp: " + as);mUiccCard = uiccCard;mAppState = as.app_state;mAppType = as.app_type;mAuthContext = getAuthContext(mAppType);mPersoSubState = as.perso_substate;mAid = as.aid;mAppLabel = as.app_label;mPin1Replaced = (as.pin1_replaced != 0);mPin1State = as.pin1;mPin2State = as.pin2;mContext = c;mCi = ci;//根据AppType实例化IccFileHandlermIccFh = createIccFileHandler(as.app_type);//根据AppType实例化IccRecordsmIccRecords = createIccRecords(as.app_type, mContext, mCi);if (mAppState == AppState.APPSTATE_READY) {queryFdn();//查询PIN1码的状态queryPin1State();}//注册监听mCi.registerForNotAvailable(mHandler, EVENT_RADIO_UNAVAILABLE, null);}

在UiccCardApplication初始化的时候,会调用自身的createIccRecords()方法,根据AppType创建对应的IccRecords 对象。

    private IccRecords createIccRecords(AppType type, Context c, CommandsInterface ci) {if (type == AppType.APPTYPE_USIM || type == AppType.APPTYPE_SIM) {//实例化SIMRecords对象return TelephonyPluginDelegate.getInstance().makeSIMRecords(this, c, ci);} else if (type == AppType.APPTYPE_RUIM || type == AppType.APPTYPE_CSIM){return new RuimRecords(this, c, ci);} else if (type == AppType.APPTYPE_ISIM) {return new IsimUiccRecords(this, c, ci);} else {// Unknown app type (maybe detection is still in progress)return null;}}

接着会进入TelephonyPluginDelegate.java和TelephonyPluginBase.java的makeSIMRecords()方法

    @TelephonyPluginDelegate.javapublic SIMRecords makeSIMRecords (UiccCardApplication app, Context c, CommandsInterface ci) {return sPlugin.makeSIMRecords (app, c, ci);}@TelephonyPluginBase.javapublic SIMRecords makeSIMRecords (UiccCardApplication app, Context c, CommandsInterface ci) {return new SIMRecords(app, c, ci);}

到这里就可以看到直接new 了一个SIMRecords对象。
四、实例化SIMRecords对象

    public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {super(app, c, ci);mAdnCache = new AdnRecordCache(mFh);mVmConfig = new VoiceMailConstants();//从spn-conf.xml文件中加载SPNmSpnOverride = new SpnOverride();// No load request is made till SIM readymRecordsRequested = false;// recordsToLoad is set to 0 because no requests are made yetmRecordsToLoad = 0;mCi.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null);mCi.registerForIccRefresh(this, EVENT_SIM_REFRESH, null);//初始化成员变量resetRecords();//注册监听,如果已ready,那么开始加载数据mParentApp.registerForReady(this, EVENT_APP_READY, null);mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null);if (DBG) log("SIMRecords X ctor this=" + this);}

等到AppState变成APPSTATE_READY是,UiccCardApplication会在notifyReadyRegistrantsIfNeeded()方法里通知SIMRecords,那么在SIMRecords的handleMessage()方法就会收到EVENT_APP_READY消息。

    public void handleMessage(Message msg) {case EVENT_APP_READY:onReady();break;}@Overridepublic void onReady() {fetchSimRecords();}

五、读取EF文件信息

当执行到fetchSimRecords()方法时,才真正开始加载EF文件信息。
具体的读取SIM卡EF文件信息的过程是由 IccFileHandler 来实现的,根据EF文件的类型,调用不同的方法,loadEFTransparent()和loadEFLinearFixed()最终都会调用RIL.java的iccIOForApp()方法。
读取某一项信息时会调用两次RIL.java的iccIOForApp():
第一次:先读取当前分区的长度lc.mRecordSize。

   Message response = obtainMessage(EVENT_GET_RECORD_SIZE_DONE,new LoadLinearFixedContext(fileid, recordNum, efPath, onLoaded));mCi.iccIOForApp(COMMAND_GET_RESPONSE, fileid, efPath,0, 0,GET_RESPONSE_EF_SIZE_BYTES,null, null, mAid, response);

第二次:再根据lc.mRecordSize去读取具体内容。


case EVENT_GET_RECORD_SIZE_DONE:......mCi.iccIOForApp(COMMAND_READ_RECORD, lc.mEfid, path,lc.mRecordNum,READ_RECORD_MODE_ABSOLUTE,lc.mRecordSize,null, null, mAid,obtainMessage(EVENT_READ_RECORD_DONE, lc));

某一项信息的加载流程如下流程图。

    protected void fetchSimRecords() {mRecordsRequested = true;if (DBG) log("fetchSimRecords " + mRecordsToLoad);//读取IMSImCi.getIMSIForApp(mParentApp.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));//每开始load一条记录,mRecordsToLoad加1,等加载完一条记录后//mRecordsToLoad就减1,当mRecordsToLoad等于0时说明已加载完所有数据了。mRecordsToLoad++;//从EF_ICCID(0x2fe2)读取ICCIDmFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));mRecordsToLoad++;// FIXME should examine EF[MSISDN]'s capability configuration// to determine which is the voice/data/fax linenew AdnRecordLoader(mFh).loadFromEF(EF_MSISDN, getExtFromEf(EF_MSISDN), 1,obtainMessage(EVENT_GET_MSISDN_DONE));mRecordsToLoad++;//读取VoiceMail信息mFh.loadEFLinearFixed(EF_MBI, 1, obtainMessage(EVENT_GET_MBI_DONE));mRecordsToLoad++;mFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));mRecordsToLoad++;// Record number is subscriber profilemFh.loadEFLinearFixed(EF_MWIS, 1, obtainMessage(EVENT_GET_MWIS_DONE));mRecordsToLoad++;// Also load CPHS-style voice mail indicator, which stores// the same info as EF[MWIS]. If both exist, both are updated// but the EF[MWIS] data is preferred// Please note this must be loaded after EF[MWIS]mFh.loadEFTransparent(EF_VOICE_MAIL_INDICATOR_CPHS,obtainMessage(EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE));mRecordsToLoad++;// Same goes for Call Forward Status indicator: fetch both// EF[CFIS] and CPHS-EF, with EF[CFIS] preferred.loadCallForwardingRecords();//从EF_SPN(0x6F46)、EF_SPN_CPHS(0x6f14)、EF_SPN_SHORT_CPHS(0x6f18)三个地址上读取SPNgetSpnFsm(true, null);//从EF_SPDI(0x6fcd)读取SPDImFh.loadEFTransparent(EF_SPDI, obtainMessage(EVENT_GET_SPDI_DONE));mRecordsToLoad++;//从EF_PNN(0x6fc5)读取PNNmFh.loadEFLinearFixed(EF_PNN, 1, obtainMessage(EVENT_GET_PNN_DONE));mRecordsToLoad++;mFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));mRecordsToLoad++;mFh.loadEFTransparent(EF_INFO_CPHS, obtainMessage(EVENT_GET_INFO_CPHS_DONE));mRecordsToLoad++;mFh.loadEFTransparent(EF_CSP_CPHS,obtainMessage(EVENT_GET_CSP_CPHS_DONE));mRecordsToLoad++;mFh.loadEFTransparent(EF_GID1, obtainMessage(EVENT_GET_GID1_DONE));mRecordsToLoad++;mFh.loadEFTransparent(EF_GID2, obtainMessage(EVENT_GET_GID2_DONE));mRecordsToLoad++;loadEfLiAndEfPl();// XXX should seek instead of examining them allif (false) { // XXXmFh.loadEFLinearFixedAll(EF_SMS, obtainMessage(EVENT_GET_ALL_SMS_DONE));mRecordsToLoad++;}if (CRASH_RIL) {String sms = "0107912160130310f20404d0110041007030208054832b0120"+ "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"+ "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"+ "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"+ "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"+ "ffffffffffffffffffffffffffffff";byte[] ba = IccUtils.hexStringToBytes(sms);mFh.updateEFLinearFixed(EF_SMS, 1, ba, null,obtainMessage(EVENT_MARK_SMS_READ_DONE, 1));}if (DBG) log("fetchSimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);}

六、读取数据完毕

读取ICCID 、读取SPDI 、读取VoiceMail、读取SPN、读取PNN;
回到fetchSimRecords()方法,每加载一项,mRecordsToLoad就加1;等到某一项读取数据完毕,handleMessage()方法被执行,就会调onRecordLoaded()方法

    protected void onRecordLoaded() {// One record loaded successfully or failed, In either case// we need to update the recordsToLoad count//每查询完一条记录,mRecordsToLoad减1mRecordsToLoad -= 1;if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested);//当mRecordsToLoad等于0,说明在fetchSimRecords()方法//中启动加载的数据都已经加载完了。if (mRecordsToLoad == 0 && mRecordsRequested == true) {onAllRecordsLoaded();} else if (mRecordsToLoad < 0) {loge("recordsToLoad <0, programmer error suspected");mRecordsToLoad = 0;}}

mRecordsToLoad的值会减1,直到mRecordsToLoad的值为0时,说明在fetchSimRecords()中启动加载的数据都已异步读取完成。就会进入onAllRecordsLoaded()方法。

    @Overrideprotected void onAllRecordsLoaded() {if (DBG) log("record load complete");Resources resource = Resources.getSystem();if (resource.getBoolean(com.android.internal.R.bool.config_use_sim_language_file)) {setSimLanguage(mEfLi, mEfPl);} else {if (DBG) log ("Not using EF LI/EF PL");}setVoiceCallForwardingFlagFromSimRecords();if (mParentApp.getState() == AppState.APPSTATE_PIN ||mParentApp.getState() == AppState.APPSTATE_PUK) {// reset recordsRequested, since sim is not loaded reallymRecordsRequested = false;// lock state, only update languagereturn ;}// Some fields require more than one SIM record to set//获取MCC + MNCString operator = getOperatorNumeric();if (!TextUtils.isEmpty(operator)) {log("onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +operator + "'");log("update icc_operator_numeric=" + operator);mTelephonyManager.setSimOperatorNumericForPhone(mParentApp.getPhoneId(), operator);final SubscriptionController subController = SubscriptionController.getInstance();subController.setMccMnc(operator, subController.getDefaultSmsSubId());} else {log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping");}if (!TextUtils.isEmpty(mImsi)) {log("onAllRecordsLoaded set mcc imsi" + (VDBG ? ("=" + mImsi) : ""));mTelephonyManager.setSimCountryIsoForPhone(mParentApp.getPhoneId(), MccTable.countryCodeForMcc(Integer.parseInt(mImsi.substring(0,3))));} else {log("onAllRecordsLoaded empty imsi skipping setting mcc");}setVoiceMailByCountry(operator);setSpnFromConfig(operator);//通知应用层mRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));}

在onAllRecordsLoaded()方法中会对读取到的数据进行处理和存储,到这里,SIM卡初始化的流程就结束了。

Android 9.0 SIM卡初始化流程相关推荐

  1. Android9.0 SIM卡初始化---更新数据

    Sim卡初始化 启动过程中初始化 SIM卡的一些相关数据 主要的类及其作用: 类名 描述 PhoneGlobals 里面的OnCreate直接调用了 PhoneFactory.makeDefaultP ...

  2. 解决Android 6.0 Sim卡 iccid获取不完整问题

    diff --git a/android/frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/IccUtils. ...

  3. android sd 挂载流程_Android7.0 SD卡挂载流程

    Android7.0 SD卡挂载流程 序言:这是7.0时候的总结,8.0MountService改名成了StorageManageService,懒得再重新总结,直接把7.0的总结贴上 这篇文章是对上 ...

  4. android telephonymanager 电话状态,TelephonyManager类:Android手机及Sim卡状态的获取

    TelephonyManager这个类很有用,可以得到很多关于手机和Sim卡的信息. 直接上注释后的代码,请享用 package net.sunniwell.app; import android.a ...

  5. Android7.0 SD卡挂载流程

    Android7.0 SD卡挂载流程 序言:这是7.0时候的总结,8.0MountService改名成了StorageManageService,懒得再重新总结,直接把7.0的总结贴上 这篇文章是对上 ...

  6. 关于android双卡手机sim卡信息采集适配的心得

    关于android双卡手机sim卡信息采集适配的心得 这几天通过对各个厂商的双卡信息适配的研究,总结了几点规律,写这篇心得主要是为了能够抛砖引玉,和大家一起分享,一起讨论,源码已上传. 一.andro ...

  7. android中对sim卡联系人的增删改查以及监听sim卡联系数据的改变

    sim卡联系人的增删改查主要是通过ContentProvider来进行操作的,在android中对sim卡联系人操作的provider是定义在IccProvider.java这个类中的,这个类位于an ...

  8. Android 2.3 SD卡挂载流程浅析(三)

    在前面两篇博文<Android 2.3 SD卡挂载流程浅析(一)><Android 2.3 SD卡挂载流程浅析(二)>中,主要简单介绍了SD卡的挂载流程以及所涉及的关键文件.在 ...

  9. android获取手机sim卡信息,Android获取手机SIM卡运营商信息的方法

    本文实例讲述了Android获取手机SIM卡运营商信息的方法,对于Android程序设计有非常实用的价值.分享给大家供大家参考之用.具体方法如下: 主要功能代码如下: /** * 获取SIM卡运营商 ...

最新文章

  1. 微信小程序界面跳转方法
  2. 时间复杂度,O(1), O(n), O(logn), O(nlogn) 的区别+样例分析
  3. 城市大脑全球标准研究1:城市大脑产生的时代背景是什么?
  4. 充血模型的ORM能做什么?——ORM组件XCode(十八般武艺)
  5. 刷脸取件遭小学生破解,丰巢智能柜紧急下线相关功能
  6. para1、para2与**kw
  7. 1034 有理数四则运算 (20 分)(c语言)(测试点三)
  8. Xshell配置ssh免密码登录-密钥公钥(Public key)
  9. Redis源码分析(一)--Redis结构解析
  10. [Objective-C]用Block实现链式编程
  11. JavaWeb:Ajax和JSON
  12. 树莓派 无法定位软件包 解决方案
  13. 典型相关分析(SPSS)
  14. Javascript 8421
  15. 离散题目9(判断是否为单射函数)
  16. Python简单实现人脸识别检测, 对照片进行评分
  17. 微带线microstrip和带状线stripline
  18. Constituency Parsing with a Self-Attensive Encoder 论文解读
  19. Vue promise的用法
  20. Cocos2dx 3.1.1 之 加速传感器、监听物理按键

热门文章

  1. Neutrino追问AMA第19期 | AlphaWallet CEO 张中南:Token化能为现有互联网从底层补足集成缺陷
  2. 蓝桥杯比赛时间在什么时候_LOL赛季奖励什么时候发 S10赛季奖励发放时间什么时候...
  3. 新媒体运营,微博运营简介与操作
  4. 那一夜, 我懂了视频帧
  5. Android入门之APP启动流程
  6. 音频测试用粉色噪声发生器
  7. java实现古堡算式
  8. 解决C++运算符重载时=和+(或-、*、/、后置自增自减)无法连用
  9. 第三天:基础入门-抓包封包协议APP小程序PC应用WEB应用
  10. CAB单片机开发指南