关闭

数据业务建立流程之发起网络连接过程(原)

标签: registerForAllEventsonDataConnectionAttasetupDataOnConnectabtrySetupDatasetupData
3412人阅读 评论(4) 收藏 举报
分类:
        经过前面这些过程,网络连接所需要的条件就全部准备就绪,接下来就是等待网络接入。
        我们把网络接入过程简单分为三个阶段:
        触发阶段
            ----该阶段是由各种不同事件触发的,比如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事件呢?其实就是手机的PS域Attach成功,而PS域是和CS域相对应,简单来说,手机(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);
        }
        因此当PS域Attach成功之后,将会收到EVENT_DATA_CONNECTION_ATTACHED消息:
        public void handleMessage (Message msg) {
            switch (msg.what) {
                case DctConstants.EVENT_DATA_CONNECTION_ATTACHED:
                    onDataConnectionAttached();
                    break;
            }
        }
        继续:
        private void onDataConnectionAttached() {
            mAttached.set(true);
            if (getOverallState() == DctConstants.State.CONNECTED) {
                //已经处于连接状态
                startNetStatPoll();
                startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
                notifyDataConnection(Phone.REASON_DATA_ATTACHED);
            } else {
                //初始化后应该处于IDLE状态
                notifyOffApnsOfAvailability(Phone.REASON_DATA_ATTACHED);
            }
            if (mAutoAttachOnCreationConfig) {
                mAutoAttachOnCreation = true;
            }
            setupDataOnConnectableApns(Phone.REASON_DATA_ATTACHED);
        }
        当前的连接状态是IDLE,因此会将REASON_DATA_ATTACHED的参数传递给notifyOffApnsOfAvailability(),里面也没什么内容,我们直接来看setupDataOnConnectableApns()的操作。
        setupDataOnConnectableApns()就是发起数据连接时使用的方法,调用该方法时需要传递一个原因的参数,该参数标明当前建立数据连接的原因(其实目前也没有发现该参数的具体作用)。
        其他诸如SIM载入完毕、通话结束等触发事件也都是通过该方法发起数据连接请求的。


二、准备连接阶段


        当由于种种原因触发网络连接请求时,就会调用到setupDataOnConnectableApns()方法,并传递各种原因的参数,接下来就需要准备APN并进行各种有效性检测,并最终将请求转交给DataConnection来处理。
        private void setupDataOnConnectableApns(String reason) {
            //对于当前环境来说,reason为REASON_DATA_ATTACHED
            for (ApnContext apnContext : mPrioritySortedApnContexts) {
                if (apnContext.getState() == DctConstants.State.FAILED) {
                    apnContext.setState(DctConstants.State.IDLE);
                }
                if (apnContext.isConnectable()) {
                    apnContext.setReason(reason);
                    trySetupData(apnContext);
                }
            }
        }
        在这个方法里面,遍历当前所有的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));
        }
        前面我们说过,激活APN的时候,其实就是让isReady()的判断通过,而mState初始值也是IDLE,因此刚才我们激活的APN参数此时就被挑选出来,并进行trySetupData()的操作:
        @DcTracker.java
        private boolean trySetupData(ApnContext apnContext) {
            boolean isEmergencyApn = apnContext.getApnType().equals(PhoneConstants.APN_TYPE_EMERGENCY);
            boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
            boolean checkUserDataEnabled = !(apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IMS));
            if (apnContext.isConnectable() && (isEmergencyApn || (isDataAllowed(apnContext) && getAnyDataEnabled(checkUserDataEnabled) && !isEmergency()))) {
                int radioTech = mPhone.getServiceState().getRilDataRadioTechnology();
                if (apnContext.getState() == DctConstants.State.IDLE) {
                    ArrayList<ApnSetting> waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech);
                    if (waitingApns.isEmpty()) {
                        notifyNoData(DcFailCause.MISSING_UNKNOWN_APN, apnContext);
                        notifyOffApnsOfAvailability(apnContext.getReason());
                        return false;
                    } else {
                        apnContext.setWaitingApns(waitingApns);
                    }
                }
                //建立连接
                boolean retValue = setupData(apnContext, radioTech);
                notifyOffApnsOfAvailability(apnContext.getReason());
                return retValue;
            } else {
            }
        }
        这里主要经历了有效性的检查,其中判断了四个情况:
        apnContext.isConnectable()
            ----判断当前APN是否已经被激活;
        isEmergencyApn()
            ----当前APN是否为紧急APN;
        isDataAllowed()
            ----判断是否已经ATTACH成功,SIM是否初始化完毕,当前手机服务是否支持,漫游下是否允许上网等;
        getAnyDataEnabled()
            ----该条件主要判断用户是否打开了数据开关;
        这四个条件我们主要来看最后一个,他里面包含用户数据开关的判断,我们来看一下详情,其中传递的参数checkUserDataEnabled受4G IMS的影响,由于国内暂时都没有部署IMS,因此这里的参数肯定都为true:
        public boolean getAnyDataEnabled(boolean checkUserDataEnabled) {
            synchronized (mDataEnabledLock) {
                if (!(mInternalDataEnabled && (!checkUserDataEnabled || mUserDataEnabled) && (!checkUserDataEnabled || sPolicyDataEnabled)))
                    return false;


                for (ApnContext apnContext : mApnContexts.values()) {
                    if (isDataAllowed(apnContext)) {
                        return true;
                    }
                }
                return false;
            }
        }
        这里的判断中最重要的就是mUserDataEnabled(),他的来源:
            mUserDataEnabled = Settings.Global.getInt( mPhone.getContext().getContentResolver(), Settings.Global.MOBILE_DATA, 1) == 1;
        这说明他来自于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、更新当前APN参数的状态并把状态发送到系统中(还是通过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:
        public class DcAsyncChannel extends AsyncChannel {}
        根据《AsyncChannel的使用和原理》中的介绍,通过fullyConnectSync()可以一次性申请到双向的AsyncChannel,对于当前环境来说,就是在DcTracker与DataConnection之间建立了双向的连接通道,而且把通道的地址传递出来,而在setupData()中我们看到,DcTracker拿到该通道后,就进行了bringUp()的调用;
        private boolean setupData(ApnContext apnContext, int radioTech) {
            DcAsyncChannel dcac = null;
            //通过DcAsyncChannel发起连接请求
            Message msg = obtainMessage();
            msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
            msg.obj = apnContext;
            dcac.bringUp(apnContext, getInitialMaxRetry(), profileId, radioTech, mAutoAttachOnCreation, msg);
            return true;
        }
        而且在调用bringUp时传递了一个Message的参数,该参数中携带了两个参数,EVENT_DATA_SETUP_COMPLETE的消息和APN的内容。
        然后我们来看bringUp:
        @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));
        }
        这里就是将刚才的两个参数封装后通过sendMessage()发送出去,那么这个消息是发送给谁了呢?

        其实就是发送给DcAsyncChannel通道的另一端DataConnection了,那么DataConnection是如何处理该消息的呢?


三、发送连接命令阶段


        当消息流进入DataConnection时,网络请求的过程在上层也就进入了最后的发送阶段,接下来我们就来看这最后的阶段如何完成。
        DataConnection是在刚才的createDataConnection()方法中通过makeDataConnection()方法创建的:
        @DataConnection.java
        static DataConnection makeDataConnection(PhoneBase phone, int id, DcTrackerBase dct, DcTesterFailBringUpAll failBringUpAll, DcController dcc) {
            //创建DataConnection方法
            DataConnection dc = new DataConnection(phone, "DC-" + mInstanceNumber.incrementAndGet(), id, dct, failBringUpAll, dcc);
            dc.start();
            return dc;
        }
        先来看该类的属性:
        public final class DataConnection extends StateMachine {}
        然后来看该对象的构造方法:
        private DataConnection(PhoneBase phone, String name, int id, DcTrackerBase dct, DcTesterFailBringUpAll failBringUpAll, DcController dcc) {
            super(name, dcc.getHandler());
            mPhone = phone;
            mDct = dct;
            mDcTesterFailBringUpAll = failBringUpAll;
            mDcController = dcc;
            mId = id;
            mCid = -1;
            mDcRetryAlarmController = new DcRetryAlarmController(mPhone, this);
            ServiceState ss = mPhone.getServiceState();
            mRilRat = ss.getRilDataRadioTechnology();
            mDataRegState = mPhone.getServiceState().getDataRegState();
            int networkType = ss.getDataNetworkType();
            mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_MOBILE, networkType, NETWORK_TYPE, TelephonyManager.getNetworkTypeName(networkType));
            mNetworkInfo.setRoaming(ss.getRoaming());
            mNetworkInfo.setIsAvailable(true);


            //各种状态机初始化
            addState(mDefaultState);
            addState(mInactiveState, mDefaultState);
            addState(mActivatingState, mDefaultState);
            addState(mRetryingState, mDefaultState);
            addState(mActiveState, mDefaultState);
            addState(mDisconnectingState, mDefaultState);
            addState(mDisconnectingErrorCreatingConnection, mDefaultState);
            //默认状态为DcInactiveState
            setInitialState(mInactiveState);


            mApnContexts = new ArrayList<ApnContext>();
        }
        从他的属性和构造方法可以看到,该类其实是一个状态机,内部定义了七种状态,其中默认状态为DcInactiveState。这些状态分别代表了一个连接从非激活状态到激活状态再到断开状态所经历的过程。
        我们只需知道当前初始状态为DcInactiveState即可。
        前面讲到,DcTracker在准备完所有APN和条件后,会把APN参数等信息打包到EVENT_CONNECT消息中,通过DcAsyncChannel发送给DataConnection,接下来我们看DataConnection对该消息的处理。
        由于当前的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中解压出来,然后就通过onConnect()方法发起连接请求,再然后就进入DcActivatingState的状态。
        我们主要来看如何通过onConnect()方法发起连接请求:
        private void onConnect(ConnectionParams cp) {
            //向Modem注册的回调消息
            Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
            msg.obj = cp;
            //向RIL发起连接请求
            mPhone.mCi.setupDataCall(
                    Integer.toString(cp.mRilRat + 2),
                    Integer.toString(cp.mProfileId),
                    mApnSetting.apn, mApnSetting.user, mApnSetting.password,
                    Integer.toString(authType),
                    protocol, msg);
        }
        到这里我们终于看到与Modem的交互了,其实发起数据连接的最终都是通过RILJ的setupDataCall的接口来实现的,该接口传递了一些必要的连接参数,包括:当前的接入技术、APN的优先级、APN的参数、以及回调消息。
        至此,该请求就由RILJ交给Modem然后通过射频向运营商发送。
        下面是该过程的流程图:
    
3
0

猜你在找
深度学习基础与TensorFlow实践
【在线峰会】前端开发重点难点技术剖析与创新实践
【在线峰会】一天掌握物联网全栈开发之道
【在线峰会】如何高质高效的进行Android技术开发
机器学习40天精英计划
Python数据挖掘与分析速成班
微信小程序开发实战
JFinal极速开发企业实战
备战2017软考 系统集成项目管理工程师 学习套餐
Python大型网络爬虫项目开发实战(全套)
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:259359次
    • 积分:3166
    • 等级:
    • 排名:第10162名
    • 原创:63篇
    • 转载:0篇
    • 译文:0篇
    • 评论:95条
    最新评论