基于Android平台的APN加载流程分析

1. APN介绍

Definition of Access Point Name
In the GPRS backbone, an Access Point Name (APN) is a reference to a GGSN. To support inter-PLMN roaming, the internal GPRS DNS functionality is used to translate the APN into the IP address of the GGSN.
——3gpp 23.003

从定义可看出,APN是GGSN的引用,被internal GPRS DNS转换为GGSN的IP地址。

在这里插入图片描述
GGSN全称Gateway GPRS Support Node, 网关GPRS支持节点。

GGSN主要起网关作用,所扮演的角色:

  • 对内:网络传输; (网络接入控制,分组数据的过滤)
  • 对外:路由器(路由选择和分组的转发,IP地址分配)

为了访问网络,手机必须设置合适的APN参数。APN的英文全称是Access Point Name,全称接入点,是手机上网时必现配置的参数。APN决定了用户的手机通过哪种接入方式来访问什么样的网络。

1.1 APN包含参数

一个典型的APN包含的参数有名称、MCCMNC、接入点、类型。除了这些基本参数,还包括其他参数如图所示:
在这里插入图片描述

1.2 APN类型

每个有数据业务的运营商都会设定自己的APN,同一个运营商的APN可能有多条,包括分别用于3G或4G,NET和WAP,不同APN的使用范围和收费会有差别。常见的APN类型有下面几种,用途和优先级各有不同。
在这里插入图片描述

1.3 APN的存储位置与加载位置

终端设备中有一个apns-config.xml文件,负责定义各个运营商规定的默认APN参数。

1.3.1 存储位置

APN在Android系统中以XML的形式存储
文件名:Apns-conf.xml
源码文件路径:

MTK平台(通常):alps\mediatek\frameworks\base\telephony\etc
高通平台(通常):android\vendor\qcom\proprietary\telephony-apps\etc
设备文件路径如图:system/etc/ apns-conf.xml

在这里插入图片描述
当设备开机后,读取XML中的APN并写入到database中。如图为apns-conf.xml一部分内容:
在这里插入图片描述

1.3.2 加载位置

  • 加载到database:TelephonyProvider读取XML并在database中插入apn的table。

  • 加载到UI菜单:根据SIM卡的MCCMNC,去匹配database中同样MCCMNC的APN项,并将匹配到的APN填写到菜单列表。

  • 加载到PDP请求:由DCtracker负责创建/更新waiting APN list,供PDP选用。

2 APN加载流程分析

2.1 APN的创建:从XML到database

从XML到database,通过telephonyprovider实现。

设备开机后,终端启动phone进程,会加载运行在phone进程中的telephonyprovider负责解析apns-config.xml文件,将其中定义的APN参数写入到数据库中。

在telephonyprovider中有initDatabase方法逻辑如下:

private void initDatabase(SQLiteDatabase db)
{
 1.打开APN xml文件etc/apns-conf.xml
 2. 获得文件句柄后,使用FileReader得到文件字符流
 3. 检查APN version一致性
 4.加载XMl中的数据到database,具体见loadApns方法
}
private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
             if (parser != null) {
                 try {
                     db.beginTransaction();
                     XmlUtils.nextElement(parser);
                     while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
                         ContentValues row = getRow(parser);
                         if (row == null) {
                             throw new XmlPullParserException("Expected 'apn' tag", parser, null);
                         }
                         insertAddingDefaults(db, row);
                         XmlUtils.nextElement(parser);
                     }
                     db.setTransactionSuccessful();
                 } catch (XmlPullParserException e) {
                     loge("Got XmlPullParserException while loading apns." + e);
                 } catch (IOException e) {
                     loge("Got IOException while loading apns." + e);
                 } catch (SQLException e) {
                     loge("Got SQLException while loading apns." + e);
                 } finally {
                     db.endTransaction();
                 }
             }
         }
  1. 每次读取parser中的一个element,也就是一条APN数据;
  2. 通过getrow方法将APN转换为Contentvalues;
  3. 最后通过insertAddingDefaults将键值写入database;

写入的table名是CARRIERS_TABLE,该table由createCarriersTable方法创建。

telephonyprovider的内部类DatabaseHelper在oncreate时创建APN table。DatabaseHelper类负责APN database的增删改查工作。

2.2 APN匹配SIM卡与菜单显示

设备插入SIM后,设置菜单中会显示该SIM卡对应的APN菜单。这部分在ApnSettings.java(android\packages\apps\settings\src\com\android\settings) 中的fillList方法实现的。该方法主要根据SIM卡的mccmnc去查询db并将APN填入菜单中。

2.3 PDP时APN选择

设备插入SIM卡后,telephony根据SIM卡的mccmnc,先创建一个包含该SIM卡所有APN的列表(AllApnList)。这一步在DcTracker中实现。Phone进程中的DcTracker构造方法如下:

  1. 调用父类DcTrackerBase的构造方法:
super(p);

DcTrackerBase的构造方法主要逻辑如下,监听SIM卡的变化:

mUiccController = UiccController.getInstance();
mUiccController.registerForIccChanged(this, DctConstants.EVENT_ICC_CHANGED, null);

也就是说,如果SIM卡有变化,UiccController会向DcTrackerBase发送EVENT_ICC_CHANGED消息,DcTrackerBase的handleMessage方法对该消息处理如下:

case DctConstants.EVENT_ICC_CHANGED:
{
onUpdateIcc();
break;
}

会回调DcTracker的onUpdateIcc方法。

  1. 注册telephony.db数据库的变化:
mApnObserver = new ApnChangeObserver();
p.getContext().getContentResolver().registerContentObserver(
Telephony.Carriers.CONTENT_URI, true, mApnObserver);
ApnChangeObserverDcTracker的内部类,定义如下:
private class ApnChangeObserver extends ContentObserver {
	public ApnChangeObserver () {
		super(mDataConnectionTracker);
	}
	@Override
	public void onChange(boolean selfChange) {
		sendMessage(obtainMessage(DctConstants.EVENT_APN_CHANGED));
		}
}

而DcTrackerBase继承Handler,如下:

public abstract class DcTrackerBase extends Handler 

因此,一旦telephony.db数据库发生变化,会调用ApnChangeObserver 的onChange方法,然后调用Handler 的sendMessage方法发送EVENT_APN_CHANGED消息。DcTracker的handleMessage方法对该消息处理如下:

case DctConstants.EVENT_APN_CHANGED:
	onApnChanged();
	break;

因此, 一旦telephony.db数据库发生变化,就会调用DcTracker的onApnChanged方法。

  1. 调用initApnContexts方法初始化不同APN类型对应的网络:
initApnContexts();
  1. 其他
for (ApnContext apnContext : mApnContexts.values())
{
	// Register the reconnect and restart actions.
	IntentFilter filter = new IntentFilter();
	filter.addAction(INTENT_RECONNECT_ALARM + '.' + apnContext.getApnType());
	filter.addAction(INTENT_RESTART_TRYSETUP_ALARM + '.' + apnContext.getApnType());
	mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
}
// Add Emergency APN to APN setting list by default to support EPDN in sim absent cases
initEmergencyApnSetting();
addEmergencyApnSetting();

DcTracker的onUpdateIcc方法主要逻辑如下:
调用IccRecords的registerForRecordsLoaded方法进行注册,监听卡信息是否载入完成。

if (newIccRecords != null)
{
	if (mPhone.getSubId() >= 0) {
		log("New records found.");
		mIccRecords.set(newIccRecords);
		newIccRecords.registerForRecordsLoaded(
		this, DctConstants.EVENT_RECORDS_LOADED, null);
	}
} 
else
{
	onSimNotReady();//SIM卡还未读出
}

如果SIM卡信息载入完成,则IccRecords会向DcTracker发送EVENT_RECORDS_LOADED,DcTracker的handleMessage对该消息处理如下:

case DctConstants.EVENT_RECORDS_LOADED:
	onRecordsLoadedOrSubIdChanged();
	break;

DcTracker的onRecordsLoadedOrSubIdChanged方法主要逻辑如下:

  1. 调用createAllApnList方法创建当前SIM卡的APN:
createAllApnList(); 
  1. 调用setInitialAttachApn方法设置初始使用的APN:
setInitialAttachApn();
  1. 调用setupDataOnConnectableApns方法发起拨号请求:
setupDataOnConnectableApns(Phone.REASON_SIM_LOADED);

当然,setupDataOnConnectableApns 方法也是通过调用trySetupData方法进行拨号的。如果当前SIM卡未打开数据业务,则不会拨号成功。

2.3.1 createAllApnList

createAllApnList函数主要根据卡信息,实际上是卡对应运营商的MCC/MCC,查询数据库,检索出所有匹配的数据;然后,生成并保存对应的ApnSetting。
DcTracker的createAllApnList方法调用流程图如下:

在这里插入图片描述
createAllApnList方法主要逻辑如下:

  1. 在telephony.db数据库的carriers表单中查询当前SIM卡对应的APN信息,
    并调用createApnList方法构造ApnSetting对象。如此,一般一个APN对应一个ApnSetting对象,这样就完成了数据库中APN到ApnSetting对象的映射。
Cursor cursor = mPhone.getContext().getContentResolver().query(
Telephony.Carriers.CONTENT_URI, null, selection, null, orderBy);
if (cursor != null) {
	if (cursor.getCount() > 0) {
		mAllApnSettings = createApnList(cursor, mIccRecords.get());
	}
	cursor.close();
}
  1. 添加emergencyApnSettings并删除重复的APN,
addEmergencyApnSetting();
dedupeApnSettings();
  1. 调用getPreferredApn方法从telephony.db数据库的siminfo表中读出默认的APN,一般第一次为空,如果该APN和当前卡的APN不匹配,则调用setPreferredApn方法删除telephony.db数据库的siminfo表中APN信息。
mPreferredApn = getPreferredApn(mAllApnSettings);
if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {
mPreferredApn = null;
setPreferredApn(-1);
}

setPreferredApn方法如下:

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); //首先调用delete方法删除

if (pos >= 0) { //当前pos为 -1,因此后面的insert方法不执行。
	log("setPreferredApn: insert");
	ContentValues values = new ContentValues();
	values.put(APN_ID, pos);
	resolver.insert(uri, values); //插入默认的APN信息
}

createApnList方法如下:

if (cursor.moveToFirst()) {
	do {
		ApnSetting apn = makeApnSetting(cursor);
		•••
		if (apn.hasMvnoParams()) {
			if (r != null && ApnSetting.mvnoMatches(r, apn.mvnoType, apn.mvnoMatchData)) {
				mvnoApns.add(apn);//虚拟运营商
			}
		} 
		else {
			mnoApns.add(apn); //运营商
		}
	} while (cursor.moveToNext());
}

利用cursor对象循环调用makeApnSetting方法构造ApnSetting对象,makeApnSetting方法就是利用cursor在telephony.db数据库的carriers表单中获取SIM卡对应的APN信息然后利用APN信息构造一个ApnSetting对象, makeApnSetting方法的部分代码如下:

String[] types = parseTypes(
cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE)));
ApnSetting apn = new ApnSetting(
cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),
•••

ApnSetting 是一个比较轻量级的类,主要包含APN的信息,以及相关解析APN信息的方法。parseTypes方法如下:

String[] result;
// If unset, set to DEFAULT.
if (types == null || types.equals("")) {
result = new String[1];
result[0] = PhoneConstants.APN_TYPE_ALL;
} else {
result = types.split(",");
}
return result;

这段代码是解析APN的type字段。APN的type域,决定了它提供的网络能力。telephony.db数据库的carriers表单部分APN的type域如下:
在这里插入图片描述
APN type包含default时,利用这个APN建立的网络就具有Mobile能力,即能够用数据网络访问Internet;当APN type包含mms时,利用这个APN建立的网络就具有发送彩信的能力。从parseTypes函数可以看出,当APN的type为空时,即没有配置时,APN的type被定义为APN_TYPE_ALL。 利用APN_TYPE_ALL建立的网络,将具有全部的网络能力。

正常情况下,这种设计是合理的:
运营商会不同的服务定义不同的网络,于是通过APN的type域进行区分;但是,有的运营商在某些地区会用同一个网络支持所有的功能(例如在非洲的一些国家),此时将APN的type域写成”default, mms, supl, dun, hipri, fota, ims…….”是件繁琐的事,于是,就规定APN的type域为“”时,可以支持所有网络能力。然而,这种设计成为了Android的一个漏洞,在某些场景下,将带来数据连接无法断开的问题。

到此,已经在telephony.db数据库中查询到了SIM卡对应的APN信息,并且构造出了对应的ApnSetting对象。这些对象最终保存在DcTrackerBase的mAllApnSettings变量中,定义如下:

protected ArrayList<ApnSetting> mAllApnSettings = null;

以后很多地方都会使用到mAllApnSettings变量,例如拨号上网。并且默认APN信息保存在mPreferredApn变量中,定义如下:protected ApnSetting mPreferredApn = null;

2.3.4 setInitialAttachApn

setInitialAttachApn主要是从得到的所有ApnSetting中,选择一个用于初始时注册数据网络的APN,并将该APN下发给modem使用。
setInitialAttachApn方法调用流程图如下:
在这里插入图片描述
DcTrackerBase的setInitialAttachApn方法如下:

setInitialAttachApn(mAllApnSettings, mPreferredApn)

一般第一次开机时, mPreferredApn为null。mAllApnSettings为当前SIM卡的对应的APN。setInitialAttachApn方法主要逻辑如下:

  1. 为三个APN变量赋值:
    在这里插入图片描述
  2. 从4个ApnSetting 对象中选择一个进行发起拨号上网请求,优先级依次为iaApnSetting、preferredApn、defaultApnSetting、firstApnSetting:
    在这里插入图片描述
  3. 如果这4个值都为空,就不发起拨号请求;否则调用RIL的setInitialAttachApn方法发起拨号请求:
    在这里插入图片描述
    如图是一份开机拨号上网的log:

在这里插入图片描述

在这份log中, 选择的mPreferredApn对象的信息进行attach请求。RIL的setInitialAttachApn方法如下:

public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, Message result) { 
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_INITIAL_ATTACH_APN, result,
                     mRILDefaultWorkSource);

             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + dataProfile);
             }

            try {
                 if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
                     // v1.5
                    android.hardware.radio.V1_5.IRadio radioProxy15 =
                             (android.hardware.radio.V1_5.IRadio) radioProxy;
                     radioProxy15.setInitialAttachApn_1_5(rr.mSerial,
                             convertToHalDataProfile15(dataProfile));
                 } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
                     // v1.4
                     android.hardware.radio.V1_4.IRadio radioProxy14 =
                             (android.hardware.radio.V1_4.IRadio) radioProxy;
                     radioProxy14.setInitialAttachApn_1_4(rr.mSerial,
                             convertToHalDataProfile14(dataProfile));
                 } else {
                     // v1.3, v1.2, v1.1, and v1.0
                     radioProxy.setInitialAttachApn(rr.mSerial,
                            convertToHalDataProfile10(dataProfile), dataProfile.isPersistent(),
                             isRoaming);
                 }
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setInitialAttachApn", e);
             }
         }
     }

通过radioProxy.setInitialAttachApn给rild守护进程发送RIL_REQUEST_SET_INITIAL_ATTACH_APN消息。

在第4行使用obtainRequest将入参传入的Message生成一个RILRequest,且放入一个list中,他在list中的位置就是mSerial。这个RILRequest的编号就是RIL_REQUEST_SET_INITIAL_ATTACH_APN。在后面根据使用HIDL接口通知RILC设置INITIAL_ATTACH_APN,并将之前得到的mSerial传给RILC。

在这里插入图片描述

2.3.3 setupDataOnConnectableApns

当SIM卡加载完成,并完成上面2个步骤之后,就会调用DcTracker 的setupDataOnConnectableApns方法。setupDataOnConnectableApns方法调用流程图如下:

在这里插入图片描述
setupDataOnConnectableApns逻辑如下:

  1. 逐个循环调用mPrioritySortedApnContexts变量中的ApnContext对象,调用buildWaitingApns方法获取对应的ApnSetting对象:
for (ApnContext apnContext : mPrioritySortedApnContexts) {
	ArrayList<ApnSetting> waitingApns = null;
	••••
	waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech);
  1. 如果该APN已激活,则调用trySetupData方法发起拨号
if (apnContext.isConnectable())
{
	log("setupDataOnConnectableApns: isConnectable() call trySetupData");
	apnContext.setReason(reason);
	trySetupData(apnContext, waitingApns);
}

trySetupData()函数主要做两件事:

  1. 判断APN状态是DctConstants.State.IDLE 的时候调用buildWaitingApns 构建拨号APN列表 并通过apnContext.setWaitingApns(waitingApns)将waitingApns列表设置到apnContext 中。

buildWaitingApns的代码看起来比较复杂,其实上就是有可用的prefer APN时,选择prefer APN;没有可用的prefer APN时,从现有卡对应的APN中,取出支持当前网络类型和无线技术的APN。

   if (apnContext.getState() == DctConstants.State.IDLE) {
              ArrayList<ApnSetting> waitingApns =
                        buildWaitingApns(apnContext.getApnType(), radioTech);
                 if (waitingApns.isEmpty()) {
                    ApnSetting apn = apnContext != null ? apnContext.getApnSetting() : null;
                     mPhone.notifyDataConnectionFailed(apnContext.getApnType(),
                            apn != null ? apn.getApnName() : null,
                             DataFailCause.MISSING_UNKNOWN_APN);
                   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));
                    }
                 }
             }
  1. 调用setupData使用apn进行拨号连接。
boolean retValue = setupData(apnContext, radioTech, requestType);
if (DBG) log("trySetupData: X retValue=" + retValue);
return retValue;

如图是一份开机拨号上网的trySetupData相关log:
在这里插入图片描述
setupData最后的逻辑如下:

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

ApnSetting preferredApn = getPreferredApn();
boolean isPreferredApn = apnSetting.equals(preferredApn);
dataConnection.bringUp(apnContext, profileId, radioTech, msg, generation, requestType,
mPhone.getSubId(), isPreferredApn);

封装EVENT_DATA_SETUP_COMPLETE 消息,然后调用dataConnection的bringUp方法发起拨号过程。如果完成拨号过程,就会回调DcTracker的父类DcTrackerBase的handleMessage方法处理EVENT_DATA_SETUP_COMPLETE 消息,对该消息处理如下:

case DctConstants.EVENT_DATA_SETUP_COMPLETE:
	onDataSetupComplete((AsyncResult) msg.obj);
	break;

调用DcTracker的onDataSetupComplete方法,该方法会将调用setPreferredApn方法将当前已拨号上网的APN写入telephony.db数据库的siminfo表中。

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四儿家的小祖宗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值