Android 12 版本 Data Call 移动数据业务流程分析

目录

前言

基础知识

APN

初始化ApnContext

DataConnection

 开启移动数据业务

总流程图

前言

本文档将基于Android 12  AOSP源码进行流程分析,围绕DcTracker的核心处理机制和关键业务流程,疏通Android手机的移动数据业务基本原理和关键流程,我把流程图放在了最后。

之前我疏忽了,Android12仍然采用的是DcTracker,但从13开始已经默认使用DataNetworkController作为数据栈了,14开始将会彻底删除老版本的数据栈DcTracker,下次会给大家带来最新的DataNetworkController的解析。

基础知识

TeleService系统应用在创建GsmCdmaPhone对象时,在该Phone对象的构造方法内会同时创建DcTracker对象。DcTracker自身继承于Handler,在构造方法中主要实现了以下三个业务:

  • 初始化ApnContext
  • 注册各类监听(广播接收器、Observer, Handler消息注册)
  • 创建DcController对象
/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
public DcTracker(Phone phone, @TransportType int transportType) {
    super();
    mPhone = phone;
    ......
    mResolver = mPhone.getContext().getContentResolver();
    ......
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_SCREEN_ON);
    filter.addAction(Intent.ACTION_SCREEN_OFF);
    filter.addAction(INTENT_DATA_STALL_ALARM);
    filter.addAction(INTENT_PROVISIONING_APN_ALARM);
    filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
    filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
    filter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);

    mDataEnabledSettings = mPhone.getDataEnabledSettings();
    //注意这个把自身作为handler传过去方便之后传递消息,当数据业务开关状态改变时会通过这个通知DCTracker
    mDataEnabledSettings.registerForDataEnabledChanged(this,  DctConstants.EVENT_DATA_ENABLED_CHANGED, null);
    mDataEnabledSettings.registerForDataEnabledOverrideChanged(this,  DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED);

    mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);

    mHandlerThread = new HandlerThread("DcHandlerThread");
    mHandlerThread.start();
    Handler dcHandler = new Handler(mHandlerThread.getLooper());
    //创建DcController对象
    mDcc = DcController.makeDcc(mPhone, this, mDataServiceManager, dcHandler.getLooper(),
            tagSuffix);

    registerForAllEvents();
    mApnObserver = new ApnChangeObserver();
    phone.getContext().getContentResolver().registerContentObserver(
            Telephony.Carriers.CONTENT_URI, true, mApnObserver);
    //初始化各类ApnContext
    initApnContexts();

    addDefaultApnSettingsAsNeeded();

    mSettingsObserver = new SettingsObserver(mPhone.getContext(), this);
    registerSettingsObserver();

    mThrottleStatusCallback = new ThrottleStatusChangedCallback();
    mDataThrottler.registerForThrottleStatusChanges(mThrottleStatusCallback);
}

APN

APN(Access Point Name)是Android手机实现移动数据上网业务必须配置的参数,用来决定手机使用哪一种方式访问网络,其配置信息保存在telephony.db中的名为carriers的表中。关键字段如下:

字段

说明

name

APN配置名称

numeric

运营商编号

apn

APN接入点, 中国移动cmwap和cmnet

proxy

代理服务器地址

port

端口号

mmsproxy

彩信代理服务器地址

mmsport

彩信代理服务器端口号

mmsc

彩信接入服务地址

type

APN接入类型

APN配置信息一旦有问题就会导致无法上网。那如果需要做国内手机的Android定制化开发,或是国外的定制化开发,应该怎么增加APN配置呢?有三个方法:

  • 定制化项目本地xml配置文件
  • 在线更新
  • APN配置管理界面手动增加

这里厂商常用的是前两个方法,我们不可能要求用户自己手动添加配置,因此厂商在定制开发过程中,会将APN配置信息提前配好,在TelephonyProvider的initDatabase方法里将APN信息插入表中.

比如我手头上的某手机厂商项目中,使用了前两个方法:

private File getApnConfFile() {
    //加载本地预置配置文件
    File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
    File oemConfFile =  new File(Environment.getOemDirectory(), OEM_APNS_PATH);
    File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH);
    File productConfFile = new File(Environment.getProductDirectory(), PARTNER_APNS_PATH);
    //厂商的在线更新配置文件,这样更新重启手机后会更新表中的数据
    File onlineConfFile = new File(ONLINE_UPDATED_APNS_PATH);

    confFile = pickSecondIfExists(confFile, oemConfFile);
    confFile = pickSecondIfExists(confFile, productConfFile);
    confFile = pickSecondIfExists(confFile, updatedConfFile);
            
     confFile = getNewerFile(confFile, onlineConfFile);
     return confFile;
}

本地配置中截取部分APN配置信息如下:



  

初始化ApnContext

在DcTracker构造方法里将调用initApnContexts方法初始化ApnContext,每个ApnContext对象用来保存对应APN网络类型的配置参数、连接状态等等。

private void initApnContexts() {        
    PersistableBundle carrierConfig; 
    //查看是否有运营商配置服务   
    CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 
    if (configManager != null) {            
        carrierConfig = configManager.getConfigForSubId(mPhone.getSubId());
    } else {            
        carrierConfig = null;
     }        
     initApnContexts(carrierConfig);
}

private void initApnContexts(PersistableBundle carrierConfig) {
    // 从本地资源中加载网络配置
    final Collection types =
            new ApnConfigTypeRepository(carrierConfig).getTypes();
    for (ApnConfigType apnConfigType : types) {
        ApnContext apnContext = new ApnContext(mPhone, apnConfigType.getType(), mLogTag, this,
                apnConfigType.getPriority());
        ......
        mPrioritySortedApnContexts.add(apnContext);
        ......
    }
    mPrioritySortedApnContexts.sort((c1, c2) -> c2.getPriority() - c1.getPriority());
}

在初始化ApnContext时,会创建ApnConfigTypeRepository对象,sDefaults对象是里面的一个静态对象,在静态代码块完成初始化,在初始化时就会添加很多默认的配置参数,包括我们关注的网络配置信息:

sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
                "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
                "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
});

从默认配置参数可以得到13个ApnConfigType对象,从而创建出13个ApnContext对象,字符串数组中每个字符串中":'之前代表一种网络配置,":"后面的数字代表着该配置的优先级,所以优先级从高到低依次是

hipri -> mcx -> xcap -> mmx -> supl -> dun -> fota -> ims -> cbs -> ia -> emergency -> default -> enterprise。

手机上网将建立default类型的数据连接,当彩信来时,因为彩信建立的是mms类型,优先级比default高,所以会断开default连接而创建mms连接,因此,在发送和接收彩信的同时不能上网 。

DataConnection

DataConnection是用来在Telephony业务模型中管理移动数据业务的类,一个DataConnection对象代表手机移动数据业务的一个数据连接。

DataConnection继承与StateMachine类,是一个典型的状态机,内部定义了6个状态类,全部继承与State类,每个状态类内部封装了对应状态下的较复杂的逻辑处理。

State子类

说明

DcDefaultState

默认状态

DcInactiveState

不活动状态

DcActivatingState

正在激活状态

DcActiveState

激活状态

DcDisconnectingState

正在断开中状态

DcDisconnectionErrorCreatingConnection

在创建连接后正处于断开中状态

源自状态机模式带来的优势,DataConnection切换不同连接状态时,不必操心切换状态的复杂逻辑处理,只需要通过使用内部的handler发送消息切到对应状态,之后交给对应状态类内部实现的processMessage方法处理即可。

在DataConnection的构造方法里面通过addState方法添加这6个状态对象,并且给除了DcDefaultState以外的每个状态指定父状态为DcDefaultState,这样就形成了一个树形结构,当一个State对象不能处理一个消息时就会向上交给父节点处理。

 Android 12 版本 Data Call 移动数据业务流程分析_第1张图片

 开启移动数据业务

手机可以通过以下两个交互界面开启或关闭移动数据业务:

  • 通知栏快捷控制
  • Settings -> Mobile network设置界面

流程分析:

打开或关闭移动数据业务的入口是TelephonyManager.setDataEnabledForReason方法,传入两个参数——切换原因和切换状态。(以前版本用的setDataEnabled方法已过时)

    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
    public void setDataEnabledForReason(@DataEnabledReason int reason, boolean enabled) {
        setDataEnabledForReason(getSubId(), reason, enabled);
    }

主要有四种理由:

  • DATA_ENABLED_REASON_USER(用户操作控制)
  • DATA_ENABLED_REASON_POLICY(由于policy数据业务被控制,通常是被限制)
  • DATA_ENABLED_REASON_CARRIER(运营商特殊设置控制)
  • DATA_ENABLED_REASON_THERMAL(热服务控制)

TelephonyManager的私有方法通过IBinder远程调用 ITelephony(PhoneInterfaceManager).setDataEnabledForReason 方法,接下来我们重点关注用户操作控制这一条链路:

    /packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java
    @Override
    public void setDataEnabledForReason(int subId, @TelephonyManager.DataEnabledReason int reason, boolean enabled, String callingPackage) {
        //检查权限
        ......
       phone.getDataEnabledSettings().setDataEnabled(reason, enabled);
        ......
    }
/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
public synchronized void setDataEnabled(@TelephonyManager.DataEnabledReason int reason,  boolean enabled) {        
    switch (reason) {
            case TelephonyManager.DATA_ENABLED_REASON_USER:                
                setUserDataEnabled(enabled);
                break;
            //其他理由
            ......
        }
    }
}
private synchronized void setUserDataEnabled(boolean enabled) {
    // By default the change should propagate to the group.       
     setUserDataEnabled(enabled, true);
}
public synchronized void setUserDataEnabled(boolean enabled,   boolean notifyMultiSimSettingController) {
    ......
    //先更新数据库设置
    boolean changed = GlobalSettingsHelper.setInt(mPhone.getContext(),  Settings.Global.MOBILE_DATA, mPhone.getSubId(), (enabled ? 1 : 0));        
    if (changed) {
        ......    
        //还记得DcTacker构造方法里面的添加监听方法吗,DcTacker就是在这里面接收到数据业务状态改变的通知
        updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED); 
        ......
    }
}

DataEnabledSettings使用从DcTracker传过来的handler对象(也就是DcTracker本身)发送消息,DcTracker在handleMessage方法里开始处理消息:

/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@Override
public void handleMessage (Message msg) {
    ......
    switch (msg.what) {
        ......
        case DctConstants.EVENT_DATA_ENABLED_CHANGED:
            ar = (AsyncResult) msg.obj;
            if (ar.result instanceof Pair) {
                Pair p = (Pair) ar.result;
                boolean enabled = p.first; //开关状态
                int reason = p.second; // 切换理由
                onDataEnabledChanged(enabled, reason);
            }
            break;
        ......
    }
}

private void onDataEnabledChanged(boolean enable,  @DataEnabledChangedReason int enabledChangedReason) {
    ......
    if (enable) {  //开启移动数据业务
        reevaluateDataConnections();
        setupDataOnAllConnectableApns(Phone.REASON_DATA_ENABLED, RetryFailures.ALWAYS);
    } else { //关闭移动数据业务
        ......
        cleanUpAllConnectionsInternal(true, cleanupReason);
    }
}

 onDataEnabledChanged方法内有两个逻辑处理逻辑分支:开启移动数据业务和关闭移动数据业务。我们继续走开启移动数据业务逻辑setupDataOnAllConnectableApns

protected void setupDataOnAllConnectableApns(String reason, RetryFailures retryFailures) {
    for (ApnContext apnContext : mPrioritySortedApnContexts) { //按照优先级开始遍历调用
        setupDataOnConnectableApn(apnContext, reason, retryFailures);
    }
}

protected void setupDataOnConnectableApn(ApnContext apnContext, String reason,
                                         RetryFailures retryFailures) {
    ......
    if (apnContext.isConnectable()) {//该ApnContext可连接,
        apnContext.setReason(reason);
        trySetupData(apnContext, REQUEST_TYPE_NORMAL, null);
    }
}

 如上代码,DcTracker会按照优先级开始遍历ApnContext,并判断当前ApnContext是否处于可连接状态,ApnContext定义见上面的基础知识。接着我们继续关注 trySetupData方法的内部逻辑:

private void trySetupData(ApnContext apnContext, @RequestNetworkType int requestType,  @Nullable Message onHandoverCompleteMsg) {
    ......
    DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
    //判断是否允许传输数据
    boolean isDataAllowed = isDataAllowed(apnContext, requestType, dataConnectionReasons);
    if (!isDataAllowed) {
        ......
        return;
    }

    if (apnContext.getState() == DctConstants.State.FAILED) {
        String str = "trySetupData: make a FAILED ApnContext IDLE so its reusable";
        ApnContext.requestLog(apnContext, str);
        apnContext.setState(DctConstants.State.IDLE);
    }
    //获取驻网的移动数据业务RadioTechnology
    int radioTech = getDataRat();
    ......
    apnContext.setConcurrentVoiceAndDataAllowed(mPhone.getServiceStateTracker() .isConcurrentVoiceAndDataAllowed());
    //判断该apnContext是否处于空闲状态
    if (apnContext.getState() == DctConstants.State.IDLE) {
        ArrayList waitingApns =
                buildWaitingApns(apnContext.getApnType(), radioTech);
        if (waitingApns.isEmpty()) {
            //未找到对应APN配置信息
            return;
        } else {
            apnContext.setWaitingApns(waitingApns);
        }
    }

    if (!setupData(apnContext, radioTech, requestType)
            && requestType == REQUEST_TYPE_HANDOVER) {
        sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false, false);
    }
}

继续分析setupData方法:

private boolean setupData(ApnContext apnContext, int radioTech,
                          @RequestNetworkType int requestType) {
    ApnContext.requestLog(apnContext, "setupData. requestType=" + requestTypeToString(requestType));
    ApnSetting apnSetting;
    DataConnection dataConnection = null;
    //从缓存的ApnSettings集合中获取对象,每个ApnSettings保存从我们上面说的apn配置数据表中的一行数据,包含了nmc,mmsc等等
    apnSetting = apnContext.getNextApnSetting();
    
    if (dataConnection == null) {
        ......
        //先查询是否缓存起来且不在使用状态的DataConnection对象,没有则再创建一个
        dataConnection = findFreeDataConnection();
        if (dataConnection == null) {
            dataConnection = createDataConnection();
        }
        ......
    }
    final int generation = apnContext.incAndGetConnectionGeneration(); //计数器

    apnContext.setDataConnection(dataConnection);
    apnContext.setApnSetting(apnSetting); //将apnSetting与该apnContext对象绑定
    apnContext.setState(DctConstants.State.CONNECTING); //设置为"连接中"状态

    Message msg = obtainMessage();
    msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
    msg.obj = new Pair(apnContext, generation);

    ApnSetting preferredApn = getPreferredApn();
    boolean isPreferredApn = apnSetting.equals(preferredApn);
    dataConnection.bringUp(apnContext, profileId, radioTech, msg, generation, requestType, mPhone.getSubId(), isPreferredApn); //激活移动数据业务
    return true;
}

接下来我们重点关注DataConnection,DataConnection的定义见基础知识部分。

createDataConnection方法会调用DataConnection.makeDataConnection静态方法创建DataConnection对象并启动状态机。最后分析bringUp方法:

public void bringUp(ApnContext apnContext, int profileId, int rilRadioTechnology, Message onCompletedMsg, int connectionGeneration,
                    @RequestNetworkType int requestType, int subId, boolean isApnPreferred) {
    if (mApnSetting == null) {
        mApnSetting = apnContext.getApnSetting();
    }

    sendMessage(DataConnection.EVENT_CONNECT,
            new ConnectionParams(apnContext, profileId, rilRadioTechnology, onCompletedMsg,
                    connectionGeneration, requestType, subId, isApnPreferred));
}

根据传入的参数创建ConnectionParams对象,然后通过sendMessage方法发送DataConnection.EVENT_CONNECT类型的消息。根据我们在基础知识部分讲的那样,根据树状结构,是DataConnection.mInactiveState进行处理,处理逻辑如下:

case EVENT_CONNECT:
     ConnectionParams cp = (ConnectionParams) msg.obj;
     ......
     //调用主类的私有方法connect
     int cause = connect(cp);
     ......
     //切换到正在激活状态
     transitionTo(mActivatingState);
     return HANDLED;

DataConnection对象的connect方法的主要处理逻辑如下。

private @DataFailureCause int connect(ConnectionParams cp) {
    ......
    mCreateTime = -1;
    mLastFailTime = -1;
    mLastFailCause = DataFailCause.NONE;

    //创建数据连接完成的消息
    Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
    msg.obj = cp;

    DataProfile dp = new DataProfile.Builder()
            .setApnSetting(mApnSetting)
            .setPreferred(cp.mIsPreferredApn)
            .build();

    boolean isModemRoaming = mPhone.getServiceState().getDataRoamingFromRegistration();

    boolean isUnmeteredApnType = !ApnSettingUtils.isMeteredApnType(
            cp.mApnContext.getApnTypeBitmask(), mPhone);

    boolean allowRoaming = mPhone.getDataRoamingEnabled()
            || (isModemRoaming && (!mPhone.getServiceState().getDataRoaming()
            || isUnmeteredApnType));
    ......
    allocatePduSessionId(psi -> {
        this.setPduSessionId(psi);
        mDataServiceManager.setupDataCall(
                ServiceState.rilRadioTechnologyToAccessNetworkType(cp.mRilRat), dp, isModemRoaming,
                allowRoaming, reason, linkProperties, psi, null, td, matchAllRuleAllowed, msg
        );
        ......
    });
    return DataFailCause.NONE;
}

走到了DataServiceManager.setupDataCall方法里,

/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
            boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
            @Nullable NetworkSliceInfo sliceInfo, @Nullable TrafficDescriptor trafficDescriptor,
            boolean matchAllRuleAllowed, Message onCompleteMessage) {

        CellularDataServiceCallback callback = new CellularDataServiceCallback("setupDataCall");
        if (onCompleteMessage != null) {
            mMessageMap.put(callback.asBinder(), onCompleteMessage);
        }
        ......
            mIDataService.setupDataCall(mPhone.getPhoneId(), accessNetworkType, dataProfile,
                    isRoaming, allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
                    trafficDescriptor, matchAllRuleAllowed, callback);
        ......
    }

DataServiceManager在这里通过IBinder方式远程调用phone进程的实现方法,处于DataService的内部类IDataServiceWrapper里面,通过handler发送DATA_SERVICE_REQUEST_SETUP_DATA_CALL消息:

frameworks/base/telephony/java/android/telephony/data/DataService.java
public abstract class DataService extends Service {
    private class IDataServiceWrapper extends IDataService.Stub {
        @Override
        public void setupDataCall(int slotIndex, int accessNetworkType, DataProfile dataProfile,
                                  boolean isRoaming, boolean allowRoaming, int reason,
                                  LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
                                  TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed,
                                  IDataServiceCallback callback) {
            mHandler.obtainMessage(DATA_SERVICE_REQUEST_SETUP_DATA_CALL, slotIndex, 0,
                            new SetupDataCallRequest(accessNetworkType, dataProfile, isRoaming,
                                    allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
                                    trafficDescriptor, matchAllRuleAllowed, callback))
                    .sendToTarget();
        }
    }
    private class DataServiceHandler extends Handler {
         @Override
        public void handleMessage(Message message) {
            ......
            case DATA_SERVICE_REQUEST_SETUP_DATA_CALL:
                serviceProvider.setupDataCall(......);
                break;
        }
    }
}

这个serviceProvider实际上有两种,分别由DataService的两个继承类CellularDataServiceIwlanDataService实现提供,这两个类分别代表了蜂窝网数据业务和无线网数据业务。我们这里只看蜂窝网:

/frameworks/opt/telephony/src/java/com/android/internal/telephony/data/CellularDataService.java
 private class CellularDataServiceProvider extends DataService.DataServiceProvider {
     @Override
    public void setupDataCall(int accessNetworkType, DataProfile dataProfile,
                              boolean isRoaming, boolean allowRoaming, int reason, LinkProperties linkProperties,
                              int pduSessionId, NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
                              boolean matchAllRuleAllowed, DataServiceCallback callback) {
        ......
        mPhone.mCi.setupDataCall(accessNetworkType, dataProfile, isRoaming, allowRoaming,
                reason, linkProperties, pduSessionId, sliceInfo, trafficDescriptor,
                matchAllRuleAllowed, message);
    }
 }

mCi是RILJ对象,最终由RIL完成Data Call移动数据业务的处理。这个RIL(Radio Interface Layer)是指无线通信接口层,主要运行在HAL上,HAL层我们就暂时不做分析了,这个RIL横跨了HAL层和Framework层,是telephony和modem沟通的桥梁。RIL处理成功后,DataConnection的状态mActivatingState将转换为mActiveState。

总流程图

这样framework层的移动数据业务启动流程就分析完毕了。开启业务流程图如下所示。

Android 12 版本 Data Call 移动数据业务流程分析_第2张图片

欢迎大家关注我,我今后会努力给大家带来更多的Android源码的解析和实用组件的。

谢谢大家 -v-

你可能感兴趣的:(Android源码分析,android,状态模式,程序人生)