这篇文章主要是分析在Android L 源代码中对手机漫游的处理。当然我这里所说的漫游指的是国际漫游。通常我们判断手机是否在国际漫游,第一个想法就是比较网络上获取的MCC+MNC是否与手机中的IMSI相同,如果不同就判断为漫游了。如果是漫游的话,手机上最直观的可以看到就是两个地方了:

a . 手机的屏幕的状态拦上手机信号角标的左下方是否有”R”显示。

b . Setting --->About phone --->Status --->Roming

当然这是最粗略的比较方法,通常全球的运营商对于漫游有互相签订协议,所以单纯用上面的方法是不够细致的,google 为了解决这个特殊化定制的问题,在Android L 上使用了一个机制来判断手机是否漫游,下面就从解析代码的角度来分析这个机制。

手机注网是一个比较复杂的过程,当然漫游就在这个过程中,在这里,我重点分析漫游这个点,注网的话,后面再另发文。

首先需要看的一个类就是:

Android_L/frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java

在这个类中有方法:

protected void handlePollStateResult (int what, AsyncResult ar) { ...... }

什么时候会调用这个方法呢?RIL层在完成ServiceStateTracker对象发起的查询最新网络服务的状态后,通过ServiceStateTracker创建的Message对象发起的Callback回调。在ServiceStateTracker对象中会调用handlePollStateResult 和 pollStateDone 方法,将查询得来的最新信息保存在ServiceStateTracker的多个属性中 。由于GsmServiceStateTracker extends ServiceStateTracker,GsmServiceStateTracker 中的handlePollStateResult 方法会覆盖ServiceStateTracker中的方法,下面是handlePollStateResult 方法的实现:

/*** Handle the result of one of the pollState()-related requests*/@Overrideprotected void handlePollStateResult (int what, AsyncResult ar) {int ints[];String states[];// Ignore stale requests from last pollif (ar.userObj != mPollingContext) return;if (ar.exception != null) {CommandException.Error err=null;if (ar.exception instanceof CommandException) {err = ((CommandException)(ar.exception)).getCommandError();}if (err == CommandException.Error.RADIO_NOT_AVAILABLE) {// Radio has crashed or turned offcancelPollState();return;}if (!mCi.getRadioState().isOn()) {// Radio has crashed or turned offcancelPollState();return;}if (err != CommandException.Error.OP_NOT_ALLOWED_BEFORE_REG_NW) {loge("RIL implementation has returned an error where it must succeed" +ar.exception);}} else try {switch (what) {case EVENT_POLL_STATE_REGISTRATION: {states = (String[])ar.result;int lac = -1;int cid = -1;int type = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;int regState = ServiceState.RIL_REG_STATE_UNKNOWN;int reasonRegStateDenied = -1;int psc = -1;if (states.length > 0) {try {regState = Integer.parseInt(states[0]);if (states.length >= 3) {if (states[1] != null && states[1].length() > 0) {lac = Integer.parseInt(states[1], 16);}if (states[2] != null && states[2].length() > 0) {cid = Integer.parseInt(states[2], 16);}// states[3] (if present) is the current radio technologyif (states.length >= 4 && states[3] != null) {type = Integer.parseInt(states[3]);}}if (states.length > 14) {if (states[14] != null && states[14].length() > 0) {psc = Integer.parseInt(states[14], 16);}}} catch (NumberFormatException ex) {loge("error parsing RegistrationState: " + ex);}}mGsmRoaming = regCodeIsRoaming(regState);mNewSS.setState(regCodeToServiceState(regState));mNewSS.setRilVoiceRadioTechnology(type);boolean isVoiceCapable = mPhoneBase.getContext().getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);if ((regState == ServiceState.RIL_REG_STATE_DENIED_EMERGENCY_CALL_ENABLED|| regState == ServiceState.RIL_REG_STATE_NOT_REG_EMERGENCY_CALL_ENABLED|| regState == ServiceState.RIL_REG_STATE_SEARCHING_EMERGENCY_CALL_ENABLED|| regState == ServiceState.RIL_REG_STATE_UNKNOWN_EMERGENCY_CALL_ENABLED)&& isVoiceCapable) {mEmergencyOnly = true;} else {mEmergencyOnly = false;}// LAC and CID are -1 if not availmNewCellLoc.setLacAndCid(lac, cid);mNewCellLoc.setPsc(psc);break;}case EVENT_POLL_STATE_GPRS: {states = (String[])ar.result;int type = 0;int regState = ServiceState.RIL_REG_STATE_UNKNOWN;mNewReasonDataDenied = -1;mNewMaxDataCalls = 1;if (states.length > 0) {try {regState = Integer.parseInt(states[0]);// states[3] (if present) is the current radio technologyif (states.length >= 4 && states[3] != null) {type = Integer.parseInt(states[3]);}if ((states.length >= 5 ) &&(regState == ServiceState.RIL_REG_STATE_DENIED)) {mNewReasonDataDenied = Integer.parseInt(states[4]);}if (states.length >= 6) {mNewMaxDataCalls = Integer.parseInt(states[5]);}} catch (NumberFormatException ex) {loge("error parsing GprsRegistrationState: " + ex);}}int dataRegState = regCodeToServiceState(regState);mNewSS.setDataRegState(dataRegState);mDataRoaming = regCodeIsRoaming(regState);mNewSS.setRilDataRadioTechnology(type);if (DBG) {log("handlPollStateResultMessage: GsmSST setDataRegState=" + dataRegState+ " regState=" + regState+ " dataRadioTechnology=" + type);}break;}case EVENT_POLL_STATE_OPERATOR: {String opNames[] = (String[])ar.result;if (opNames != null && opNames.length >= 3) {mNewSS.setOperatorName (opNames[0], opNames[1], opNames[2]);}break;}case EVENT_POLL_STATE_NETWORK_SELECTION_MODE: {ints = (int[])ar.result;mNewSS.setIsManualSelection(ints[0] == 1);break;}}} catch (RuntimeException ex) {loge("Exception while polling service state. Probably malformed RIL response." + ex);}mPollingContext[0]--;if (mPollingContext[0] == 0) {/*** Since the roaming state of gsm service (from +CREG) and* data service (from +CGREG) could be different, the new SS* is set to roaming when either is true.** There are exceptions for the above rule.* The new SS is not set as roaming while gsm service reports* roaming but indeed it is same operator.* And the operator is considered non roaming.** The test for the operators is to handle special roaming* agreements and MVNO's.*/<span style="color:#FF0000;"> boolean roaming = (mGsmRoaming || mDataRoaming);</span>if ((mGsmRoaming && isSameNamedOperators(mNewSS)&& !isSameNamedOperatorConsideredRoaming(mNewSS))|| isOperatorConsideredNonRoaming(mNewSS)) {roaming = false;}mNewSS.setRoaming(roaming);mNewSS.setEmergencyOnly(mEmergencyOnly);pollStateDone();}}

这个方法主要完成了三件事情:

1,RIL返回的查询结果异常处理;

2,根据返回的4种不同网络服务查询类型,作不同处理;

3,调用pollDtateDone方法完成后面的工作。

在这里我们只关注 Roaming , 看方法中对于漫游定义:

boolean roaming = (mGsmRoaming || mDataRoaming);

那么mGsmRoaming 和 mDataRoaming 分别是什么呢?在类的最前面对于变量的定义中有:

    /*** GSM roaming status solely based on TS 27.007 7.2 CREG. Only used by* handlePollStateResult to store CREG roaming result.*/private boolean mGsmRoaming = false;/*** Data roaming status solely based on TS 27.007 10.1.19 CGREG. Only used by* handlePollStateResult to store CGREG roaming result.*/private boolean mDataRoaming = false;

根据Google加的注释可以看到,我们应该需要参考3GPP文档TS 27.007的相关章节,大家可以去3GPP官网下载该文档看看。

对上面两个变量的赋值,主要是在:

mGsmRoaming = regCodeIsRoaming(regState);

mDataRoaming = regCodeIsRoaming(regState);

这里传入的参数 regState 网络状态编码 是一个很重要的参数,手机当前状态的很多属性都是根据这个参数来判断的。同样是在TS 27.007文档的7.2节有定义对应关系。

Defined values
<n>:
0 disable network registration unsolicited result code
1 enable network registration unsolicited result code +CREG: <stat>
2 enable network registration and location information unsolicited result code +CREG:
<stat>[,<lac>,<ci>[,<AcT>]]
<stat>: circuit mode registration status
0 not registered, MT is not currently searching a new operator to register to
1 registered, home network
2 not registered, but MT is currently searching a new operator to register to
3 registration denied
4 unknown
<span style="color:#FF0000;">5 registered, roaming</span>
<lac>: string type; two byte location area code or tracking are a code in hexadecimal format (e.g. "00C3" equals
195 in decimal)
<ci>: string type; four byte GERAN/UTRAN/E-UTRAN cell ID in hexadecimal format
<AcT>: access technology of the registered network

从上面的定义可以看出,code为5的时候是漫游状态,找到方法regCodeIsRoaming()

    /*** code is registration state 0-5 from TS 27.007 7.2* returns true if registered roam, false otherwise*/private boolean regCodeIsRoaming (int code) {return ServiceState.RIL_REG_STATE_ROAMING == code;}

从代码中可以看到,当code为ServiceState.RIL_REG_STATE_ROAMING 时,返回值为true。找到这个常量的定义:

Android_L/frameworks/base/telephony/java/android/telephony/ServiceState.java

  /*** RIL level registration state values from ril.h* ((const char **)response)[0] is registration state 0-6,*              0 - Not registered, MT is not currently searching*                  a new operator to register*              1 - Registered, home network*              2 - Not registered, but MT is currently searching*                  a new operator to register*              3 - Registration denied*              4 - Unknown*              5 - Registered, roaming*             10 - Same as 0, but indicates that emergency calls*                  are enabled.*             12 - Same as 2, but indicates that emergency calls*                  are enabled.*             13 - Same as 3, but indicates that emergency calls*                  are enabled.*             14 - Same as 4, but indicates that emergency calls*                  are enabled.* @hide*/public static final int RIL_REG_STATE_NOT_REG = 0;/** @hide */public static final int RIL_REG_STATE_HOME = 1;/** @hide */public static final int RIL_REG_STATE_SEARCHING = 2;/** @hide */public static final int RIL_REG_STATE_DENIED = 3;/** @hide */public static final int RIL_REG_STATE_UNKNOWN = 4;/** @hide */<span style="color:#FF0000;">  public static final int RIL_REG_STATE_ROAMING = 5;</span>/** @hide */public static final int RIL_REG_STATE_NOT_REG_EMERGENCY_CALL_ENABLED = 10;/** @hide */public static final int RIL_REG_STATE_SEARCHING_EMERGENCY_CALL_ENABLED = 12;/** @hide */public static final int RIL_REG_STATE_DENIED_EMERGENCY_CALL_ENABLED = 13;/** @hide */public static final int RIL_REG_STATE_UNKNOWN_EMERGENCY_CALL_ENABLED = 14;

可以看到代码中这些定义的code值与名称的对应关系,RIL_REG_STATE_ROAMING常量的值为5 。所以当传入的参数regState为5的时候,为漫游。

现在我们关注下面这段代码:

if (mPollingContext[0] == 0) {/*** Since the roaming state of gsm service (from +CREG) and* data service (from +CGREG) could be different, the new SS* is set to roaming when either is true.** There are exceptions for the above rule.* The new SS is not set as roaming while gsm service reports* roaming but indeed it is same operator.* And the operator is considered non roaming.** The test for the operators is to handle special roaming* agreements and MVNO's.*/boolean roaming = (mGsmRoaming || mDataRoaming);if ((mGsmRoaming && isSameNamedOperators(mNewSS)&& !isSameNamedOperatorConsideredRoaming(mNewSS))|| isOperatorConsideredNonRoaming(mNewSS)) {roaming = false;}mNewSS.setRoaming(roaming);mNewSS.setEmergencyOnly(mEmergencyOnly);pollStateDone();}

可以先看看google加在前面的注释,大概的意思是:

由于GSM服务和数据服务的漫游状态可能不同,所以只要这二者其中之一是漫游就将New SS(最新的ServiceState)设置为漫游。

对于上面的规则有一个说明。

当GSM服务被认为是漫游但事实上他们是同一个运营商,且时运营商决定不漫游,这个时候new SS不会设置为漫游。

对于运营商的测试是为了处理特殊的漫游协议和移动虚拟网络运营商。

下面分析下if条件中的几个方法,在这里传入的参数都是mNewSS,简单的说一下这个对象:

ServiceState意思是服务状态,手机插入SIM卡成功启动后,BP Modem会读取SIM卡中的IMSI信息完成SIM卡中信息的验证和运营商移动网络的注册,这样手机才能正常使用运营商提供的服务,代码中ServiceState保存SIM卡注册成功后运营商网络的一些基本服务信息,具体可以看这个类中常量的定义,显然,漫游也在其中。

(1)isSameNamedOperators():从注释中知道如果运营商网络的MCC和SIM卡的MCC一样,同时ons(查看相关的协议文档)和spn(spn是写在sim卡中的值,具体请查找文档)不同则设置为漫游状态,即返回值为true。

 /*** Set roaming state if operator mcc is the same as sim mcc* and ons is different from spn** @param s ServiceState hold current ons* @return true if same operator*/private boolean isSameNamedOperators(ServiceState s) {String spn = SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, "empty");    //获得SimCard中的spn,如果没有,返回“empty”
        String onsl = s.getOperatorAlphaLong();     //获得当前注册的运营商网络的长名String onss = s.getOperatorAlphaShort();    //获取当前注册的运营商网络的短名boolean equalsOnsl = onsl != null && spn.equals(onsl);  //onsl不为空,且spn和onsl相同时,equalsOnsl为trueboolean equalsOnss = onss != null && spn.equals(onss);  //onss不为空,且spn和onss相同时,equalsOnsl为truereturn currentMccEqualsSimMcc(s) && (equalsOnsl || equalsOnss);}

看看这个返回值的逻辑。只有当equalsOnsl或者equalsOnss其中一个为true且currentMccEqualsSimMcc()返回值为true时,上面这个方法才返回true,下面看看

currentMccEqualsSimMcc()这个方法,该方法用来比较SIM卡的MCC和网络上的MCC,即比较simNumeric和operatorNumeric的前三位,相同则返回true。

    /*** Compare SIM MCC with Operator MCC** @param s ServiceState hold current ons* @return true if both are same*/private boolean currentMccEqualsSimMcc(ServiceState s) {String simNumeric = SystemProperties.get(         //获得SIM Number                  TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, "");String operatorNumeric = s.getOperatorNumeric();  //获得Operator Numberboolean equalsMcc = true;try {equalsMcc = simNumeric.substring(0, 3).equals(operatorNumeric.substring(0, 3));   } catch (Exception e){}return equalsMcc;}

下面同时看看isSameNamedOperatorConsideredRoaming()和isOperatorConsideredNonRoaming()这两个方法。

    private boolean isSameNamedOperatorConsideredRoaming(ServiceState s) {String operatorNumeric = s.getOperatorNumeric();String[] numericArray = mPhone.getContext().getResources().getStringArray(<span style="color:#FF0000;">com.android.internal.R.array.config_sameNamedOperatorConsideredRoaming</span>);if (numericArray.length == 0 || operatorNumeric == null) {return false;}for (String numeric : numericArray) {if (operatorNumeric.startsWith(numeric)) {return true;}}return false;}
 /*** Do not set roaming state in case of oprators considered non-roaming.*+ Can use mcc or mcc+mnc as item of config_operatorConsideredNonRoaming.* For example, 302 or 21407. If mcc or mcc+mnc match with operator,* don't set roaming state.** @param s ServiceState hold current ons* @return false for roaming state set*/private boolean isOperatorConsideredNonRoaming(ServiceState s) {String operatorNumeric = s.getOperatorNumeric();String[] numericArray = mPhone.getContext().getResources().getStringArray(<span style="color:#FF0000;">com.android.internal.R.array.config_operatorConsideredNonRoaming</span>);if (numericArray.length == 0 || operatorNumeric == null) {return false;}for (String numeric : numericArray) {if (operatorNumeric.startsWith(numeric)) {return true;}}return false;}

通过比较发现这两个方法的代码逻辑中只有getStringArray()这个方法中传递的参数不同,那我们下意识的跟进这个方法,来到:

Android L/frameworks/base/core/java/android/content/res/Resources.java

    /*** Return the string array associated with a particular resource ID.** @param id The desired resource identifier, as generated by the aapt*           tool. This integer encodes the package, type, and resource*           entry. The value 0 is an invalid identifier.** @throws NotFoundException Throws NotFoundException if the given ID does not exist.** @return The string array associated with the resource.*/public String[] getStringArray(int id) throws NotFoundException {String[] res = mAssets.getResourceStringArray(id);if (res != null) {return res;}throw new NotFoundException("String array resource ID #0x"+ Integer.toHexString(id));}

从注释来看,返会的是一个与特有资源ID相关联的字符数组,我们继续往下跟进getResourceStringArray()这个方法,来到:

Android L/frameworks/base/core/java/android/content/res/AssetManager.java

    /*** Retrieve the string array associated with a particular resource* identifier.* @param id Resource id of the string array*//*package*/ final String[] getResourceStringArray(final int id) {String[] retArray = getArrayStringResource(id);return retArray;}

那AssetManager.java是一个怎样的类呢,注意到代码中对于该类有一个注释。

/*** Provides access to an application's raw asset files; see {@link Resources}* for the way most applications will want to retrieve their resource data.* This class presents a lower-level API that allows you to open and read raw* files that have been bundled with the application as a simple stream of* bytes.*/
public final class AssetManager {......
}

大概的意思是说这个类为应用提供一个通往原始资源文件的通道,通过这种方式应用可以重新获得它们的资源文件。这个类提供了一种轻量级的API,

能够让你打开和读那些已经与应用绑定在一起作为简单字节流的原始资源文件。这个翻译起来比较绕口,楼主英语也是渣,所以就直接看效果了,

再看看getArrayStringResource()这个方法的定义:

private native final String[] getArrayStringResource(int arrayRes);

注意到这是一个native方法,那JNI是如何实现的呢?来到:

/home/simon/Android L/frameworks/base/core/jni/android_util_AssetManager.cpp

代码中有如下函数:

static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,jint arrayResId)
{AssetManager* am = assetManagerForJavaObject(env, clazz);if (am == NULL) {return NULL;}const ResTable& res(am->getResources());const ResTable::bag_entry* startOfBag;const ssize_t N = res.lockBag(arrayResId, &startOfBag);if (N < 0) {return NULL;}jobjectArray array = env->NewObjectArray(N, g_stringClass, NULL);if (env->ExceptionCheck()) {res.unlockBag(startOfBag);return NULL;}Res_value value;const ResTable::bag_entry* bag = startOfBag;size_t strLen = 0;for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {value = bag->map.value;jstring str = NULL;// Take care of resolving the found resource to its final value.ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
#if THROW_ON_BAD_IDif (block == BAD_INDEX) {jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");return array;}
#endifif (value.dataType == Res_value::TYPE_STRING) {const ResStringPool* pool = res.getTableStringBlock(block);const char* str8 = pool->string8At(value.data, &strLen);if (str8 != NULL) {str = env->NewStringUTF(str8);} else {const char16_t* str16 = pool->stringAt(value.data, &strLen);str = env->NewString(str16, strLen);}// If one of our NewString{UTF} calls failed due to memory, an// exception will be pending.if (env->ExceptionCheck()) {res.unlockBag(startOfBag);return NULL;}env->SetObjectArrayElement(array, i, str);// str is not NULL at that point, otherwise ExceptionCheck would have been true.// If we have a large amount of strings in our array, we might// overflow the local reference table of the VM.env->DeleteLocalRef(str);}}res.unlockBag(startOfBag);return array;
}

那上面的函数是什么作用呢,简单来说就是根据之前传入的ID到Android L/frameworks/base/core/res/res目录下获得相应数组,先看下这个目录下是什么文件

会根据MCC和MNC(如果有的话)去找到相应的目录,比如MCC=234,MNC=34,那么就去找到values-mcc234-mnc34这个目录,并去读取这个目录下的配置文件,

<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2013, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
--><resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"><!-- Don't use roaming icon for considered operators --><string-array translatable="false" name="<span style="color:#FF0000;">config_operatorConsideredNonRoaming</span>"><item>23430</item><item>23431</item><item>23432</item><item>23433</item><item>23434</item><item>23486</item></string-array>
</resources>

我们之前所说的isSameNamedOperatorConsideredRoaming()和isOperatorConsideredNonRoaming()这两个方法里对于得到数组传入的参数不同,就体现在当前配置文件中的"name"字段,上面JNI函数返回的是一个数组,数组里的值就是读取的"item", 这里的item是可以根据运营商的需求去手动配置的,这个就体现了个性化定制。在得到数组后,isSameNamedOperatorConsideredRoaming()和isOperatorConsideredNonRoaming()方法中的处理逻辑都是会遍历数组,同时与operatorNumeric作比较,如果相同则返回true.

在一一分析了上面这些方法后,再回头去看handlePollStateResult()方法最后if中的处理逻辑就一目了然了,在这里就不多说了,读者可以简单推理一下。这篇博客的题目为漫游浅析,那么实际上手机厂商对于漫游的处理不一定会采取google原生的方案,通常芯片厂商会有自己的解决方案,所以真正对于漫游的处理可能会复杂点,如果读者有机会参与手机ROM开发,也许会接触到更多这方面的知识,当然android也在不断生长中,第一次写这么多内容,不对的地方望指正。

Android L 漫游浅析相关推荐

  1. 更新ADT到Android L的方法

    android ADT无法更新到20? 哈哈,有办法,windows 进入host   写入例如以下所有,是的所有.然后在到sdk manager中方可更新 #Google Services STAR ...

  2. 不仅是 64 位 Android L 还有这 9 大亮点

    Android L无疑是谷歌今年最重要的新产品之一,其战略意义重大,代表着谷歌统一多种设备平台的决心.显然,Android L有很多值得我们期待和兴奋的地方,以下是最主要的9点: Material D ...

  3. Android L 新特性

    转自:http://blog.jobbole.com/73577/ 每次Android新版的发布,Google都会发布一个API变更报告,概括出对比前一个版本增加.改变.移除的API等信息. 无论如何 ...

  4. Android L 仍需改善的三个问题

    Android L是谷歌今年交出的答卷之一,除了采用新的设计语言,还横跨Android Wear.TV等多个平台,并收紧了部分平台的界面定制权限,可以了解到谷歌在移动平台上的一些战略变化.当然,就设计 ...

  5. android L 关机流程图

    轉載: http://blog.csdn.net/hovan/article/details/42495379 下面是简单的流程图,从Java到kernel层. ShutdownThread.java ...

  6. ubuntu1604编译android5.1(android L)失败error: unsupportedreloc 43等问题

    ubuntu1604编译android5.1(android L)失败error: unsupportedreloc 43等问题 1.    编译的问题 见session_root1 prebuilt ...

  7. [Android L]SEAndroid开放设备文件结点权限(读或写)方法(涵盖常用操作:sys/xxx、proc/xxx、SystemProperties)热门干货

    点击打开链接 温馨提示      建议你先了解一下上一篇博文([Android L]SEAndroid增强Androd安全性背景概要及带来的影响)所讲的内容,先对SEAndroid窥个全貌,然后再继续 ...

  8. 基于MT6752/32平台 Android L版本驱动移植步骤

    基于MT6752/32平台 Android L版本驱动移植步骤 根据MK官网所述,在Android L 版本上Turnkey ABS 架构将会phase out,而Mediatek Turnkey架构 ...

  9. Android L 使用ART能提高多少性能?

    点击打开链接 刚刚结束的 Google I/O 大会上,Android 下一代操作系统「L」带来不少惊喜.新系统运行更快.更省电. 然而开发者对这个新系统也有颇多疑问,比如新的运行模式 ART 对开发 ...

最新文章

  1. 21张让你代码能力突飞猛进的速查表(神经网络、机器学习、可视化等)
  2. C语言--字符串和数字的相互转换
  3. BindingException: Invalid bound statement (not found)问题排查:SpringBoot集成Mybatis重点分析...
  4. WEB前端 实现图片懒加载 echo.js
  5. POI 导出文件以文件流形式返回
  6. 【Java并发编程】:使用synchronized获取互斥锁
  7. class_create和class_device_create
  8. ASP连接sql server实例解析
  9. Atitit 常见概念与技术 dom及其解析 目录 1.1. Dom概念(文档对象模型(Document Object Model))是什么 1 1.1.1. 节点 2 1.1.2. Node 层次
  10. NeatUpload的使用方式
  11. Tensorflow手写数字识别
  12. oso kabuwj severe conime 美女病毒 重要文件.exe : 通过移动设备引发的血案...
  13. 【慧河网络安全组】Web基础和http协议培训题_1
  14. 图像的transformation与registration
  15. 在手机屏幕上移动APP的两种方式
  16. C# 生成带二维表头的Excel表
  17. C++题解:CSP迎国庆热身公益赛T2——猜数游戏(70分)
  18. 创业者必知的股权分配
  19. 【经验分享】如何使用校园账号登录WOS(Web of Science)
  20. c语言查找源字节是否含有子字节,36 R语言的文本处理 | R语言教程

热门文章

  1. deep deepfm wide 区别_CTR预估之WideDeep和DeepFM
  2. Node.js实现登录注册
  3. 数据分析的职业发展及分类
  4. java 关闭输出流_Java OutputStream.close()关闭并释放输出流资源
  5. SH-SY5Y human neuroblastoma cell line: in vitro cell model of dopaminergic neurons in Parkinson’s di
  6. Dell戴尔电脑打开视频软件后屏幕饱和度突然变高
  7. “查找”学习提纲(二)——树型查找和散列查找
  8. Oracle 11g升级至11.2.0.3
  9. 每日一题(十)function showCase(value){ switch(value){ case 'A': console.log('case
  10. 威联通nas的ipv4+ipv6双栈https证书ddns折腾记录