开机数据网络链接时序图
手动开关数据网络时序图
前面完成了DcTracker APN list的初始化过程,经过前面这些过程,网络连接所需要的条件就全部准备就绪,接下来就是等待网络接入。
网络接入过程简单分为三个阶段:
触发阶段
----该阶段是由各种不同事件触发的,比如SIM载入完毕、PS域Attach成功、通话结束、APN改变等,该阶段的最终都是要调用setupDataOnConnectableApns()方法;
准备连接阶段
----该阶段是指,在DcTracker收到建立连接的请求之后,需要进行一系列有效性检测,比如APN是否已经激活、PS是否已经就绪、用户是否打开网络开关等,然后创建DataConnection()对象,准备发起连接请求;
发送连接命令阶段
----该阶段是指,在DataConnection收到DcTracker的请求之后,将请求转交给RILJ的过程,经过该阶段后,请求就发送到了RIL以及Modem层,由底层完成信令的发送和接收;
一、触发阶段
有多种事件可以触发网络接入过程,具体来说分为以下几个原因:
//漫游相关
static final String REASON_ROAMING_ON = "roamingOn";
static final String REASON_ROAMING_OFF = "roamingOff";
//PS attach
static final String REASON_DATA_ATTACHED = "dataAttached";
//APN改变
static final String REASON_APN_CHANGED = "apnChanged";
//通话结束
static final String REASON_VOICE_CALL_ENDED = "2GVoiceCallEnded";
//SIM载入完毕
static final String REASON_SIM_LOADED = "simLoaded";
//网络模式改变
static final String REASON_NW_TYPE_CHANGED = "nwTypeChanged";
下面以REASON_DATA_ATTACHED原因来分析网络连接的发起过程:
什么是Data Attach事件呢?其实就是手机开机后ServiceStateTracker的注网.
简单来说,手机(2G/3G)打电话是在CS域上进行,而数据流量是在PS域上进行,PS域附着(Attach)成功之后才可以发起数据激活的信令,然后才可以上网,默认状态下,手机开机后就会发起PS附着的信令给网络,附着成功之后将会接到成功的消息,由于DcTracker当初初始化时在registerForAllEvents()中注册了Attach的监听器:
protected void registerForAllEvents() {
//监听是否PS域Attach状态
mPhone.getServiceStateTracker().registerForDataConnectionAttached(this, DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null);
}
而该消息就是ServiceStateTracker在pollStateDoneGsm()中发出的:
private void pollStateDoneGsm() {
boolean hasGprsAttached =
mSS.getDataRegState() != ServiceState.STATE_IN_SERVICE
&& mNewSS.getDataRegState() == ServiceState.STATE_IN_SERVICE;
....
if (hasGprsAttached) {
mAttachedRegistrants.notifyRegistrants();
}
因此当PS域Attach成功之后,将会收到EVENT_DATA_CONNECTION_ATTACHED消息:
public void handleMessage (Message msg) {
switch (msg.what) {
case DctConstants.EVENT_DATA_CONNECTION_ATTACHED:
onDataConnectionAttached();
break;
}
}
当前的连接状态是IDLE,因此会将REASON_DATA_ATTACHED的参数传递给notifyOffApnsOfAvailability(),里面也没什么内容.
直接来看setupDataOnConnectableApns()的操作,其他诸如SIM载入完毕、通话结束等触发事件也都是通过该方法发起数据连接请求的.
setupDataOnConnectableApns()就是发起数据连接时使用的方法,调用该方法时需要传递一个原因的参数,会在log中打印出来.
二、准备连接阶段
当由于种种原因触发网络连接请求时,就会调用到setupDataOnConnectableApns()方法,并传递各种原因的参数,接下来就需要准备APN并进行各种有效性检测,并最终将请求转交给DataConnection来处理。
protected void setupDataOnConnectableApns(String reason) {
setupDataOnConnectableApns(reason, RetryFailures.ALWAYS);
}
private void setupDataOnConnectableApns(String reason, RetryFailures retryFailures) {
if (VDBG) log("setupDataOnConnectableApns: " + reason);
for (ApnContext apnContext : mPrioritySortedApnContexts) {
ArrayList
if (VDBG) log("setupDataOnConnectableApns: apnContext " + apnContext);
....
if (apnContext.isConnectable()) {
log("isConnectable() call trySetupData");
apnContext.setReason(reason);
trySetupData(apnContext, waitingApns);
}
}
}
在这个方法里面,遍历当前所有的APN参数(mPrioritySortedApnContexts其实等同于mApnContexts,只不过是按照优先级顺序排列了一下而已,项都是一样的),找到可用的APN参数,然后通过trySetupData()发起连接请求,我们来看一下这里如何判断一个APN是否可用,也就是看一下isConnectable()的判断条件:
@ApnContext.java
public boolean isConnectable() {
return isReady() && ((mState == DctConstants.State.IDLE)
|| (mState == DctConstants.State.SCANNING)
|| (mState == DctConstants.State.RETRYING)
|| (mState == DctConstants.State.FAILED));
}
这里isReady()在APN激活后会返回true,同时初始mState == DctConstants.State.IDLE,所以isConnectable()返回true.
private boolean trySetupData(ApnContext apnContext, ArrayList
boolean checkUserDataEnabled =
ApnSetting.isMeteredApnType(apnContext.getApnType(), mPhone.getContext(),
mPhone.getSubId(), mPhone.getServiceState().getDataRoaming()) &&
apnContext.hasNoRestrictedRequests(true /*exclude DUN */);
//判断是否已经ATTACH成功,SIM是否初始化完毕,当前手机服务是否支持,漫游下是否允许上网等;
boolean isDataAllowed = isDataAllowed(failureReason) ||
(failureReason.isFailForSingleReason(DataAllowFailReasonType.ROAMING_DISABLED) &&
!(ApnSetting.isMeteredApnType(apnContext.getApnType(), mPhone.getContext(),
mPhone.getSubId(), mPhone.getServiceState().getDataRoaming())));
if (apnContext.isConnectable() && (isEmergencyApn ||
(isDataAllowed && isDataAllowedForApn(apnContext) &&
mDataEnabledSettings.isDataEnabled(checkUserDataEnabled) && !isEmergency()))) {
if (apnContext.getState() == DctConstants.State.FAILED) {
String str = "trySetupData: make a FAILED ApnContext IDLE so its reusable";
if (DBG) log(str);
apnContext.requestLog(str);
apnContext.setState(DctConstants.State.IDLE);
}
int radioTech = mPhone.getServiceState().getRilDataRadioTechnology();
apnContext.setConcurrentVoiceAndDataAllowed(sst.isConcurrentVoiceAndDataAllowed());
if (apnContext.getState() == DctConstants.State.IDLE) {
if (waitingApns == null) {
waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech);
}
if (waitingApns.isEmpty()) {
notifyNoData(DcFailCause.MISSING_UNKNOWN_APN, apnContext);
notifyOffApnsOfAvailability(apnContext.getReason());
String str = "trySetupData: X No APN found retValue=false";
if (DBG) log(str);
apnContext.requestLog(str);
return false;
} else {
apnContext.setWaitingApns(waitingApns);
if (DBG) {
log ("trySetupData: Create from mAllApnSettings : "
+ apnListToString(mAllApnSettings));
}
}
}
//建立连接
boolean retValue = setupData(apnContext, radioTech);
notifyOffApnsOfAvailability(apnContext.getReason());
if (DBG) log("trySetupData: X retValue=" + retValue);
return retValue;
} else {
....
}
}
这里主要经历了有效性的检查,其中判断了四个情况:
apnContext.isConnectable()
----判断当前APN是否已经被激活;
isEmergencyApn()
----当前APN是否为紧急APN;
isDataAllowed()
----判断是否已经ATTACH成功,SIM是否初始化完毕,当前手机服务是否支持,漫游下是否允许上网等;
mDataEnabledSettings.isDataEnabled(checkUserDataEnabled)
----该条件主要判断用户是否打开了数据开关;
这四个条件我们主要来看最后一个,他里面包含用户数据开关的判断,我们来看一下详情,其中传递的参数checkUserDataEnabled受4G IMS的影响,由于国内暂时都没有部署IMS,因此这里的参数肯定都为true:
public synchronized boolean isDataEnabled(boolean checkUserDataEnabled) {
return (mInternalDataEnabled
&& (!checkUserDataEnabled || mUserDataEnabled)
&& (!checkUserDataEnabled || mPolicyDataEnabled)
&& (!checkUserDataEnabled || mCarrierDataEnabled));
}
这里的判断中最重要的就是mUserDataEnabled(),他的来源:
DcTracker.java
public void update() {
log("update sub = " + mPhone.getSubId());
log("update(): Active DDS, register for all events now!");
onUpdateIcc();
mDataEnabledSettings.setUserDataEnabled(getDataEnabled());
mAutoAttachOnCreation.set(false);
((GsmCdmaPhone)mPhone).updateCurrentCarrierInProvider();
}
public boolean getDataEnabled() {
final int device_provisioned =
Settings.Global.getInt(mResolver, Settings.Global.DEVICE_PROVISIONED, 0);
boolean retVal = "true".equalsIgnoreCase(SystemProperties.get(
"ro.com.android.mobiledata", "true"));
if (TelephonyManager.getDefault().getSimCount() == 1) {
retVal = Settings.Global.getInt(mResolver, Settings.Global.MOBILE_DATA,
retVal ? 1 : 0) != 0;
} else {
int phoneSubId = mPhone.getSubId();
try {
retVal = TelephonyManager.getIntWithSubId(mResolver,
Settings.Global.MOBILE_DATA, phoneSubId) != 0;
} catch (SettingNotFoundException e) {
// use existing retVal
}
}
if (VDBG) log("getDataEnabled: retVal=" + retVal);
if (device_provisioned == 0) {
// device is still getting provisioned - use whatever setting they
// want during this process
//
// use the normal data_enabled setting (retVal, determined above)
// as the default if nothing else is set
final String prov_property = SystemProperties.get("ro.com.android.prov_mobiledata",
retVal ? "true" : "false");
retVal = "true".equalsIgnoreCase(prov_property);
final int prov_mobile_data = Settings.Global.getInt(mResolver,
Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
retVal ? 1 : 0);
retVal = prov_mobile_data != 0;
log("getDataEnabled during provisioning retVal=" + retVal + " - (" + prov_property +
", " + prov_mobile_data + ")");
}
return retVal;
}
这说明他来自于Settings.Global.MOBILE_DATA这个属性值,而这个属性值恰恰就是当用户打开或关闭移动网络时所改变的属性值,当用户打开数据网络时,该值为1,关闭网络时,该值就是0。
回到trySetupData()中来,当前所选取的APN是可用的,而且不是紧急APN,同时假设用户打开了数据流量开关,那么在该方法中就会通过setupData的方法来发送数据连接的请求:
private boolean setupData(ApnContext apnContext, int radioTech) {
ApnSetting apnSetting;
DcAsyncChannel dcac = null;
apnSetting = apnContext.getNextWaitingApn();
int profileId = apnSetting.profileId;
if (profileId == 0) {
profileId = getApnProfileID(apnContext.getApnType());
}
if (dcac == null) {
//创建DcAsyncChannel
dcac = findFreeDataConnection();
if (dcac == null) {
dcac = createDataConnection();
}
if (dcac == null) {
return false;
}
}
apnContext.setDataConnectionAc(dcac);
apnContext.setApnSetting(apnSetting);
apnContext.setState(DctConstants.State.CONNECTING);
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
//通过DcAsyncChannel发起连接请求
Message msg = obtainMessage();
msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
msg.obj = apnContext;
dcac.bringUp(apnContext, getInitialMaxRetry(), profileId, radioTech, mAutoAttachOnCreation, msg);
return true;
}
这个方法内部主要完成了两个任务:
1、更新当前APNContext 参数的状态并把状态发送到系统中(还是通过notifyDataConnection()来完成);
2、通过DcAsyncChannel的bringUp()方法发起连接请求;
我们主要分析第二个任务。
这里显示通过findFreeDataConnection()方法搜索可用的DcAsyncChannel,找不到的话就通过createDataConnection()创建,我们由于第一次上网,因此就需要创建的过程:
private DcAsyncChannel createDataConnection() {
int id = mUniqueIdGenerator.getAndIncrement();
DataConnection conn = DataConnection.makeDataConnection(mPhone, id, this, mDcTesterFailBringUpAll, mDcc);
mDataConnections.put(id, conn);
//创建DcAsyncChannel通道
DcAsyncChannel dcac = new DcAsyncChannel(conn, LOG_TAG);
//申请双向连接
int status = dcac.fullyConnectSync(mPhone.getContext(), this, conn.getHandler());
if (status == AsyncChannel.STATUS_SUCCESSFUL) {
mDataConnectionAcHashMap.put(dcac.getDataConnectionIdSync(), dcac);
} else {
}
return dcac;
}
这里主要完成四个步骤:
1、拿到了一个DataConnection对象;
2、创建了DcAsyncChannel对象;
3、通过fullyConnectSync对DataConnection发起双向连接请求;
4、将DcAsyncChannel返回出来;
DcAsyncChannel的属性其实是AsyncChannel:
通过fullyConnectSync()在DcTracker与DataConnection之间建立了双向的连接通道.
接着在DcTracker中调用dcac.bringUp(apnContext, profileId, radioTech, msg, generation)向DataConnection发送发起联网消息:
@DcAsyncChannel.java
public void bringUp(ApnContext apnContext, int initialMaxRetry, int profileId, int rilRadioTechnology, boolean retryWhenSSChange, Message onCompletedMsg) {
sendMessage(DataConnection.EVENT_CONNECT, new ConnectionParams(apnContext, initialMaxRetry, profileId, rilRadioTechnology, retryWhenSSChange, onCompletedMsg));
}
三、发送连接命令阶段
DcTracker通过DcAsyncChannel向DataConnection发送DataConnection.EVENT_CONNECT消息后,DataConnection会进一步向RIL层发起联网请求:
public final class DataConnection extends StateMachine {}
DataConnection的本质是StateMachine,内部定义了七种状态,其中默认状态为DcInactiveState。
由于当前的DataConnection处于DcInactiveState状态机中,因此将会在该状态机中处理EVENT_CONNECT的消息:
private class DcInactiveState extends State {
public boolean processMessage(Message msg) {
switch (msg.what) {
case EVENT_CONNECT:
ConnectionParams cp = (ConnectionParams) msg.obj;
//初始化连接环境
if (initConnection(cp)) {
//发起连接请求
onConnect(mConnectionParams);
//进入正在激活状态
transitionTo(mActivatingState);
} else {
notifyConnectCompleted(cp, DcFailCause.UNACCEPTABLE_NETWORK_PARAMETER, false);
}
retVal = HANDLED;
break;
}
return retVal;
}
}
在这个方法里先将EVENT_CONNECT中打包的参数在initConnection中解压出来,主要是获得apnContext和mApnSetting
private boolean initConnection(ConnectionParams cp) {
ApnContext apnContext = cp.mApnContext;
if (mApnSetting == null) {
// Only change apn setting if it isn't set, it will
// only NOT be set only if we're in DcInactiveState.
mApnSetting = apnContext.getApnSetting();
}
if (mApnSetting == null || !mApnSetting.canHandleType(apnContext.getApnType())) {
if (DBG) {
log("initConnection: incompatible apnSetting in ConnectionParams cp=" + cp
+ " dc=" + DataConnection.this);
}
return false;
}
mTag += 1;
mConnectionParams = cp;
...
}
然后通过onConnect()方法发起连接请求,再然后就进入DcActivatingState的状态。
private void onConnect(ConnectionParams cp) {
//向Modem注册的回调消息
Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
msg.obj = cp;
//向RIL发起连接请求
mPhone.mCi.setupDataCall(
cp.mRilRat,
cp.mProfileId,
mApnSetting.apn, mApnSetting.user, mApnSetting.password,
authType,
protocol, msg);
}
通过mPhone.mCi.setupDataCall()向RIL层传入ApnSetting相关的参数来发起联网请求,再由RILJ交给Modem然后通过射频向运营商发送。