手机可以上网,首先要建立数据连接,建立数据连接之前需要有apn才可以,所以本节先研究APN参数的创建过程。

在DcTracker.java中,创建APN的起点方法为createAllApnList方法。

DcTracker的createAllApnList方法:
/*** Based on the sim operator numeric, create a list for all possible* Data Connections and setup the preferredApn.*/
// 创建APN列表并创建preferredapn
private void createAllApnList() {mAllApnSettings = new ArrayList<ApnSetting>();// 获取到保存SIM数据的对象IccRecords r = mIccRecords.get();// 通过IccRecords获取SIM卡中的MCCMNC,因为要根据MCCMNC来从数据库中读取这个运营商的apnString operator = (r != null) ? r.getOperatorNumeric() : "";if (operator != null) {// 匹配条件和排列顺序String selection = "numeric = '" + operator + "'";String orderBy = "_id";// query only enabled apn.// carrier_enabled : 1 means enabled apn, 0 disabled apn.// selection += " and carrier_enabled = 1";if (DBG) log("createAllApnList: selection=" + selection);// 查询数据库Cursor cursor = mPhone.getContext().getContentResolver().query(Telephony.Carriers.CONTENT_URI, null, selection, null, orderBy);if (cursor != null) {if (cursor.getCount() > 0) {// 创建出apn列表到集合中mAllApnSettings = createApnList(cursor);}cursor.close();}}// 添加emergency的apn到apn集合中addEmergencyApnSetting();// 合并相似的apndedupeApnSettings();if (mAllApnSettings.isEmpty()) {// APN集合为空,mPreferredApn也没用了,因为mPreferredApn肯定在apn集合里面if (DBG) log("createAllApnList: No APN found for carrier: " + operator);mPreferredApn = null;// TODO: What is the right behavior?//notifyNoData(DataConnection.FailCause.MISSING_UNKNOWN_APN);} else {// 获取preferredapn,该apn为用户在UI界面选择的mPreferredApn = getPreferredApn();if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {// 如果此时mPreferredApn的mccmnc与SIM卡中的不一致,则mPreferredApn置为空mPreferredApn = null;// 删除数据库中的数据setPreferredApn(-1);}if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);}if (DBG) log("createAllApnList: X mAllApnSettings=" + mAllApnSettings);setDataProfilesAsNeeded(); // 将apn信息发给modem,没搞懂是什么意思
}
createAllApnList方法主要有3个步骤:
(1)从数据库中读取出符合要求的apn列表;
(2)添加emergency的apn到列表中并合并apn;
(3)获取到preferredApn。
(1)从数据库中读取出符合要求的apn列表
从数据库中按照mccmnc来获取所有的apn,并选择出符合要求的apn。在createApnList方法中,得到符合要求的apn集合。
createApnList方法
// 从数据库中读取出所有符合要求的apn
private ArrayList<ApnSetting> createApnList(Cursor cursor) {ArrayList<ApnSetting> mnoApns = new ArrayList<ApnSetting>(); // 实体运营商ArrayList<ApnSetting> mvnoApns = new ArrayList<ApnSetting>(); // 虚拟运营商IccRecords r = mIccRecords.get();if (cursor.moveToFirst()) {do {ApnSetting apn = makeApnSetting(cursor);if (apn == null) {continue;}if (apn.hasMvnoParams()) { // apn的mvnoType和mvnoMatchData都不为空,说明该apn是一个虚拟运营商的apnif (r != null && ApnSetting.mvnoMatches(r, apn.mvnoType, apn.mvnoMatchData)) { // apn的这两个参数与SIM卡中的参数匹配一致mvnoApns.add(apn); // 添加到虚拟运营商集合中}} else {mnoApns.add(apn); // 添加到实体运营商集合中}} while (cursor.moveToNext()); // 移动到下一个游标处}// 虚拟运营商的apn集合优先级高ArrayList<ApnSetting> result = mvnoApns.isEmpty() ? mnoApns : mvnoApns;if (DBG) log("createApnList: X result=" + result);return result;
}
createApnList方法中,一个一个构建apn对象,并按照虚拟运营商和实体运营商来划分,当有虚拟运营商的apn时,就用虚拟运营商的apn,没有虚拟运营商的apn时,就用实体运营商的apn。
构建apn对象的方法:makeApnSetting
// 利用ApnSetting的构造方法创建出一个apnsetting
private ApnSetting makeApnSetting(Cursor cursor) {String[] types = parseTypes(cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE)));ApnSetting apn = new ApnSetting(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),NetworkUtils.trimV4AddrZeros(cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY))),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT)),NetworkUtils.trimV4AddrZeros(cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC))),NetworkUtils.trimV4AddrZeros(cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY))),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT)),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)),cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)),types,cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL)),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.ROAMING_PROTOCOL)),cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ENABLED)) == 1,cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.BEARER)),cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.BEARER_BITMASK)),cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)),cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MODEM_COGNITIVE)) == 1,cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS)),cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.WAIT_TIME)),cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS_TIME)),cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MVNO_TYPE)),cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MVNO_MATCH_DATA)));return apn;
}

apn有个type参数的获取方法:

/*** @param types comma delimited list of APN types* @return array of APN types*/
// 返回apn的type,以字符串数组的形式返回
private String[] parseTypes(String types) {String[] result;// If unset, set to DEFAULT.if (types == null || types.equals("")) {result = new String[1];result[0] = PhoneConstants.APN_TYPE_ALL; // 当types为空或为null,默认为PhoneConstants.APN_TYPE_ALL类型} else {// 以逗号隔开,因为在配置的时候当存在多个apn type,就是用逗号隔开的result = types.split(",");}return result;
}
获取一个构建的apn对象后,判断该apn是否是用于配置虚拟运营商的apn。
ApnSetting的hasMvnoParams方法:
/*** Returns true if there are MVNO params specified.*/
// 判断apn是否为虚拟运营商的apn
public boolean hasMvnoParams() {return !TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData);
}
如果apn的参数mvnoType和mvnoMatchData都不为空,则说明属于虚拟运营商的apn,接下来就要看看这个apn是否属于这张SIM卡的虚拟apn,即要将apn的虚拟运营商参数与SIM卡中的数据对比。根据 mvnoType的类型,来对比mvnoMatchData是否相同。
ApnSetting的mvnoMatches方法
// SIM卡与apn参数的mvnoMatchData匹配规则
public static boolean mvnoMatches(IccRecords r, String mvnoType, String mvnoMatchData) {if (mvnoType.equalsIgnoreCase("spn")) { //spnif ((r.getServiceProviderName() != null) &&r.getServiceProviderName().equalsIgnoreCase(mvnoMatchData)) {return true;}} else if (mvnoType.equalsIgnoreCase("imsi")) { // imsiString imsiSIM = r.getIMSI();if ((imsiSIM != null) && imsiMatches(mvnoMatchData, imsiSIM)) {return true;}} else if (mvnoType.equalsIgnoreCase("gid")) { // gidString gid1 = r.getGid1();int mvno_match_data_length = mvnoMatchData.length();if ((gid1 != null) && (gid1.length() >= mvno_match_data_length) &&gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) {return true;}}return false;// equals: 如果两个字符串具有相同的字符和长度,返回true,否则返回false,区分大小写// equalsIgnoreCase: 如果两个字符串具有相同的字符和长度,返回true,否则返回false,不区分大小写,要求没有equals那么严格
}
有三个比较的类型,分别是spn、imsi和gid,说白了就是比较内容是否一致。在配置apn的虚拟运营商参数时,可以不用考虑大小写的影响。
其中imsiMatches方法的比较如下:
// MVNO_TYPE为imsi的mvnoMatchData匹配规则
private static boolean imsiMatches(String imsiDB, String imsiSIM) {// Note: imsiDB value has digit number or 'x' character for seperating USIM information// for MVNO operator. And then digit number is matched at same order and 'x' character// could replace by any digit number.// ex) if imsiDB inserted '310260x10xxxxxx' for GG Operator,//     that means first 6 digits, 8th and 9th digit//     should be set in USIM for GG Operator.int len = imsiDB.length();int idxCompare = 0;if (len <= 0) return false;if (len > imsiSIM.length()) return false;// imsiDB的长度要大于等于0且长度小于imsiSIM的长度// 按照顺序对比imsiDB和imsiSIM中的每个字符,如imsiDB出现‘x’或‘X’,可以忽略不计for (int idx=0; idx<len; idx++) {char c = imsiDB.charAt(idx);if ((c == 'x') || (c == 'X') || (c == imsiSIM.charAt(idx))) {continue;} else {return false;}}return true;
}
如果说apn的虚拟运营商参数与SIM卡中的数据吻合,则将该apn加入到虚拟运营商的apn集合中。如果该apn没有虚拟运营商的参数,则加入到实体运营商的apn集合中。最后判断是该用虚拟运营商的apn集合还是用实体运营商的apn集合。
备注:当apn的虚拟运营商参数跟SIM卡中的数据不匹配时,则这个apn就是废弃的。当SIM卡为虚拟运营商,但是apn数据库中没有该mccmnc对应的虚拟运营商apn时,最终的apn就是实体运营商的apn集合了。
(2)添加emergency的apn到列表中并合并apn
DcTracker的addEmergencyApnSetting方法
/*** Add the Emergency APN settings to APN settings list*/
// 添加emergency的apn到apn集合中
private void addEmergencyApnSetting() {if(mEmergencyApn != null) {if(mAllApnSettings == null) {mAllApnSettings = new ArrayList<ApnSetting>();} else {boolean hasEmergencyApn = false;for (ApnSetting apn : mAllApnSettings) { // 如果在数据库中有emergency类型的apn,那么就直接退出了if (ArrayUtils.contains(apn.types, PhoneConstants.APN_TYPE_EMERGENCY)) {hasEmergencyApn = true;break;}}if(hasEmergencyApn == false) { // 如果数据库中没有,那么就将不为null的mEmergencyApn添加到mAllApnSettings中mAllApnSettings.add(mEmergencyApn);} else {log("addEmergencyApnSetting - E-APN setting is already present");}}}
}

DcTracker的dedupeApnSettings方法

// 合并相似的apn
private void dedupeApnSettings() {ArrayList<ApnSetting> resultApns = new ArrayList<ApnSetting>();// coalesce APNs if they are similar enough to prevent// us from bringing up two data calls with the same interfaceint i = 0;while (i < mAllApnSettings.size() - 1) {ApnSetting first = mAllApnSettings.get(i);ApnSetting second = null;int j = i + 1;while (j < mAllApnSettings.size()) {second = mAllApnSettings.get(j);if (apnsSimilar(first, second)) { // 两个apn类似ApnSetting newApn = mergeApns(first, second); // 合并这两个apnmAllApnSettings.set(i, newApn); // 合并后的apn放到apn集合的i位置处first = newApn;mAllApnSettings.remove(j); // apn集合中移除掉被合并的apn} else {j++;}}i++;}
}
上述方法基本上对比了任意两个apn,当满足apnsSimilar方法,就认为这两个apn需要合并了。
apnsSimilar方法:
// Check if neither mention DUN and are substantially similar
// 判断两个apn相似的准则
private boolean apnsSimilar(ApnSetting first, ApnSetting second) {return (first.canHandleType(PhoneConstants.APN_TYPE_DUN) == false && // first的apn不为dun类型second.canHandleType(PhoneConstants.APN_TYPE_DUN) == false && // second的apn不为dun类型Objects.equals(first.apn, second.apn) && // apn名称一样!apnTypeSameAny(first, second) && // 此条件很重要,即只有当first和second的apn type没有重复的时候,才会返回truexorEquals(first.proxy, second.proxy) && // proxy一样,或有一方者为空xorEquals(first.port, second.port) && // port一样,或者有一方为空first.carrierEnabled == second.carrierEnabled && // carrierEnabled一样first.bearerBitmask == second.bearerBitmask && // bearerBitmask一样first.profileId == second.profileId && // profileId一样Objects.equals(first.mvnoType, second.mvnoType) && // mvnoType一样Objects.equals(first.mvnoMatchData, second.mvnoMatchData) && // mvnoMatchData一样xorEquals(first.mmsc, second.mmsc) && // mmsc一样,或者有一方为空xorEquals(first.mmsProxy, second.mmsProxy) && // mmsProxy一样,或者有一方为空xorEquals(first.mmsPort, second.mmsPort)); // mmsPort一样,或者有一方为空
}

apnTypeSameAny方法:

//check whether the types of two APN same (even only one type of each APN is same)
// 两个apnsetting的type类型比较
private boolean apnTypeSameAny(ApnSetting first, ApnSetting second) {if(VDBG) {StringBuilder apnType1 = new StringBuilder(first.apn + ": ");for(int index1 = 0; index1 < first.types.length; index1++) {apnType1.append(first.types[index1]);apnType1.append(",");}StringBuilder apnType2 = new StringBuilder(second.apn + ": ");for(int index1 = 0; index1 < second.types.length; index1++) {apnType2.append(second.types[index1]);apnType2.append(",");}log("APN1: is " + apnType1);log("APN2: is " + apnType2);} // 罗列出apn的所有type// 满足下面三个条件之一,则认为两个apn的type相似:// ①first的apn type存在all类型// ②second的apn type存在all类型// ③frist和second的apn type中有相同的部分for(int index1 = 0; index1 < first.types.length; index1++) {for(int index2 = 0; index2 < second.types.length; index2++) {if(first.types[index1].equals(PhoneConstants.APN_TYPE_ALL) ||second.types[index2].equals(PhoneConstants.APN_TYPE_ALL) ||first.types[index1].equals(second.types[index2])) {if(VDBG)log("apnTypeSameAny: return true");return true;}}}if(VDBG)log("apnTypeSameAny: return false");return false;// 也就是说,两个apnsetting中的apn type有重复的,则返回true,没有重复的,返回false
}
在满足 apnsSimilar方法后,就要进行合并apn了。
mergeApns方法:
// 合并apn的处理方法
private ApnSetting mergeApns(ApnSetting dest, ApnSetting src) {int id = dest.id; // id先暂定为小的那个,因为mAllApnSettings是按照顺序排列的ArrayList<String> resultTypes = new ArrayList<String>();resultTypes.addAll(Arrays.asList(dest.types));// 对apn type的处理,此处很重要// srcType基本都要加入到resultTypes中// 如果srcType中存在default类型,那么resultTypes肯定不存在default类型,则id置为src的id// 因为如果id不是default类型apn的id,那么在ApnSettings界面中选择的preferredapn,在DcTracker中// preferredapn的id为src.id,而mergeApns的apn的id为dest.id,程序会认为id不对,获取不到preferredapn,// 从而去拿另一个default的apn去建立数据连接for (String srcType : src.types) {if (resultTypes.contains(srcType) == false) resultTypes.add(srcType);if (srcType.equals(PhoneConstants.APN_TYPE_DEFAULT)) id = src.id;}String mmsc = (TextUtils.isEmpty(dest.mmsc) ? src.mmsc : dest.mmsc);String mmsProxy = (TextUtils.isEmpty(dest.mmsProxy) ? src.mmsProxy : dest.mmsProxy);String mmsPort = (TextUtils.isEmpty(dest.mmsPort) ? src.mmsPort : dest.mmsPort);String proxy = (TextUtils.isEmpty(dest.proxy) ? src.proxy : dest.proxy);String port = (TextUtils.isEmpty(dest.port) ? src.port : dest.port);String protocol = src.protocol.equals("IPV4V6") ? src.protocol : dest.protocol;String roamingProtocol = src.roamingProtocol.equals("IPV4V6") ? src.roamingProtocol :dest.roamingProtocol;int bearerBitmask = (dest.bearerBitmask == 0 || src.bearerBitmask == 0) ?0 : (dest.bearerBitmask | src.bearerBitmask);return new ApnSetting(id, dest.numeric, dest.carrier, dest.apn,proxy, port, mmsc, mmsProxy, mmsPort, dest.user, dest.password,dest.authType, resultTypes.toArray(new String[0]), protocol,roamingProtocol, dest.carrierEnabled, 0, bearerBitmask, dest.profileId,(dest.modemCognitive || src.modemCognitive), dest.maxConns, dest.waitTime,dest.maxConnsTime, dest.mtu, dest.mvnoType, dest.mvnoMatchData);
}
(3)获取到preferredApn
在获取到apn集合后,就需要看看用哪个apn来进行上网了。当用户使用一张卡时,可能手动选择过使用的apn,因此,当用户再次插拔卡或者重启手机后,getPreferredApn用于找出用户之前选择的APN。
getPreferredApn方法:
// 得到preferredapn的方法
private ApnSetting getPreferredApn() {if (mAllApnSettings == null || mAllApnSettings.isEmpty()) { // 为空判断log("getPreferredApn: mAllApnSettings is " + ((mAllApnSettings == null)?"null":"empty"));return null;}String subId = Long.toString(mPhone.getSubId());Uri uri = Uri.withAppendedPath(PREFERAPN_NO_UPDATE_URI_USING_SUBID, subId);// 从数据库中读取preferredapnCursor cursor = mPhone.getContext().getContentResolver().query(uri, new String[] { "_id", "name", "apn" },null, null, Telephony.Carriers.DEFAULT_SORT_ORDER);if (cursor != null) {mCanSetPreferApn = true;} else {mCanSetPreferApn = false;}// mRequestedApnType就是PhoneConstants.APN_TYPE_DEFAULTlog("getPreferredApn: mRequestedApnType=" + mRequestedApnType + " cursor=" + cursor+ " cursor.count=" + ((cursor != null) ? cursor.getCount() : 0));if (mCanSetPreferApn && cursor.getCount() > 0) {int pos;cursor.moveToFirst();// 获取preferredapn在数据库中的id号pos = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID));for(ApnSetting p : mAllApnSettings) {log("getPreferredApn: apnSetting=" + p);// 遍历apn集合,存在id相同,并且apn type符合preferredapn的要求时,则返回给preferredapnif (p.id == pos && p.canHandleType(mRequestedApnType)) {log("getPreferredApn: X found apnSetting" + p);cursor.close();return p;}}}if (cursor != null) {cursor.close();}log("getPreferredApn: X not found");return null;
}

getPreferredApn方法就是从数据库中读取出preferred的apn,如果存在,且preferredapn的id与之前创建的apn集合中一个apn的id相同,且满足该apn的type参数中有一个default,则成功获取preferredapn。
接着,在 createAllApnList方法中,对该 preferredapn进行判断,如果该apn的mccmnc不是这张卡的mccmnc,则将 preferredapn置为空,且清除数据库中的数据。
setPreferredApn(-1)方法:
private void setPreferredApn(int pos) {if (!mCanSetPreferApn) {log("setPreferredApn: X !canSEtPreferApn");return;}String subId = Long.toString(mPhone.getSubId());Uri uri = Uri.withAppendedPath(PREFERAPN_NO_UPDATE_URI_USING_SUBID, subId);log("setPreferredApn: delete");ContentResolver resolver = mPhone.getContext().getContentResolver();// 删除数据库中的数据resolver.delete(uri, null, null);if (pos >= 0) {// 如果pos为-1,则不执行插入,否则插入新的preferredapn,pos即为idlog("setPreferredApn: insert");ContentValues values = new ContentValues();values.put(APN_ID, pos);resolver.insert(uri, values);}
}

到此为止,APN参数创建完毕。

【Android 数据业务解析】APN参数创建相关推荐

  1. Android系统(96)---Android 数据交换解析框架Gson使用详解

    Android 数据交换解析框架Gson使用详解 Json 是一种文本形式的数据交换格式,比 xml 更为轻量.Json 的解析和生成的方式很多,在 Android 平台上最常用的类库有 Gson 和 ...

  2. 大数据算法_大数据算法解析,如何创建用户画像实现千人千面?

    " 一面科技,一面生活 技术与社交电商的完美融合 打开了全新的幸福视界 " 幸福蜜糖  2020·11·5 大数据算法无处不在 "处在如今的时代中,数据越来越值钱,如何 ...

  3. android 数据业务,【Android架构Telephony篇】之数据业务(一)

    Android: 4.4.4 Desktop: Ubuntu 15.04 这里只做一些准备事情. 不得不说,Android里的Telephony模块还是挺复杂的,要想搞个八九分清楚需要花点功夫.今天把 ...

  4. Android:一篇就够!全面详细解析APN(涉及内容:GGSN,authtype,MVNO,pdp,Apns-conf,supl,hipri,dun)

    每篇一格言: 人生就像滚雪球,关键是要找到足够湿的雪,和足够长的坡. --沃伦巴菲特 目录 1. APN:从概念说起 1.1 从3GPP看APN的定义和角色 1.2 APN包含哪些参数 APN的类型: ...

  5. Android:全面详细的解析Android数据流量统计流程与分析方法(流量检测、流量监控、流量提示)相关类PhoneStateListener,dcTracker,TrafficStats

    作者:GentlemanTsao https://blog.csdn.net/GentelmanTsao 每篇一格言 Stay hungry,stay foolish! --Steve Jobs 文章 ...

  6. 【Android RTMP】音频数据采集编码 ( AAC 音频格式解析 | FLV 音频数据标签解析 | AAC 音频数据标签头 | 音频解码配置信息 )

    文章目录 安卓直播推流专栏博客总结 一. AAC 音频格式解析 二. FLV 音频数据标签解析 1. 分析 FLV 格式中的 AAC 音频格式数据 2. AAC 音频特殊配置 3. AAC 音频数据标 ...

  7. android基础 [超级详细android Activity组件解析(Activity综述,Activity生命周期,Activity启动--携带参数启动)]

    1 章节目录 2 Android Activity综述 2.1 Activity怎么用 2.2 layout - 界面布局 2.3 Java - 控制界面 2.4 AndroidManifest.xm ...

  8. Android数据存储——2.文件存储_C_DOM解析XML文档

    今天学习Android数据存储--文件存储_DOM解析XML文档 位于org.w3c.dom操作XML会比较简单,就是将XML看做是一颗树,DOM就是对这颗树的一个数据结构的描述,但对大型XML文件效 ...

  9. android json.out,Android 之 json数据的解析(jsonReader)

    json数据的解析相对而言,还是比较容易的,实现的代码也十分简单.这里用的是jsonReade方法来进行json数据解析. 1.在解析之前,大家需要知道什么是json数据. json数据存储的对象是无 ...

最新文章

  1. Windows内核读写自旋锁EX_SPIN_LOCK
  2. [LevelDB] 写批处理过程详解
  3. jq绑定的事件不生效
  4. shell编程 之 test命令
  5. 考下这个法律证书,轻松拿编制,年薪高达20W+!
  6. android adt带的ddms的heap功能不显示进程,Android内存泄露之DDMS – Heap工具
  7. 爬虫:查找自己浏览器headers
  8. C语言小程序打印楼梯图案
  9. 计算机设计大赛答辩ppt
  10. 新唐NUC980读取U盘配置
  11. 支持jsp、php的免费空间
  12. 华为p20nfc怎么复制门禁卡_华为荣耀手机的NFC功能怎么用?怎么刷门禁卡
  13. EMAC和PHY层之间的关系以及在通信架构划分情况
  14. Python列表实现矩阵的创建、输入输出、转化转置、加减乘运算并设计一个矩阵计算器GUI界面
  15. mysql中comment(注释)的一些用法
  16. Downloader——Linux中的下载利器
  17. 想分享给各位的故事【如果你想成为很厉害很厉害的人】
  18. 时间差/时间戳转为时分秒
  19. ODBC 连接 ORACLE数据库 代码
  20. c51单片机led奇数偶数亮_编写LED控制程序,完成LED奇数号灯和偶数号灯的交替显示...

热门文章

  1. 16、注册中心-consul
  2. 运筹学基础【四】 之 库存管理
  3. (原创文章)羊毛党何去何从
  4. R语言1----Excel格式数据的导入
  5. 使用UltraISO软碟通离线安装Centos8.3.2011过程中遇到的问题
  6. AutoCAD按坐标打印图纸
  7. xmake v2.0.1 发布
  8. 热力图heatmap
  9. 今年出现了5种电子商务SEO趋势
  10. NachOS线程ID的实现、最大线程数的实现和优先级的添加