您的位置:首页 > 移动开发 > Android开发

Android APN的显示流程源代码分析

2018-02-26 22:59 495 查看

一.名词介绍

1.PLMN(Public Land Mobile Network,公共陆地移动网络)由政府或它所批准的经营者,为公众提供陆地移动通信业务目的而建立和经营的网络。一句话:一个移动通信网络,比如中国的PLMN主要有三种,中国移动中国联通和中国电信。在手机开发中,PLMN一般指网络代号,而PLMN代号=MCC+MNC。如下是常见的PLMN参照表

MCC     MNC     运营商
460     00      中国移动
460     01      中国联通
460     02      中国移动
460     03      中国电信
460     04      中国卫通
460     05      中国电信
460     06      中国联通
460     07      中国移动
460     20      中国铁通


2.MCC(Mobile Country Code,移动国家码)MCC的资源由国际电联(ITU)统一分配和管理,唯一识别移动用户所属的国家,共3位,中国为460。

3.MNC(Mobile Network Code,移动网络号码),用于识别移动客户所属的移动网络,2~3位数字组成.

MCC MNC PLMN都是常量,一般只能从SIM卡或者配置文件读取,而不可以随意指定。

4.SPN(Service Provider Name,运营商名称),其内容是写在USIM/SIM卡中EFspn字段中的,与当前注册的网络无关。如中国移动卡,漫游到任何网络,其SPN都是「CMCC」或「中国移动」

5.APN(Access Point Name,接入点名称)

手机可以接入各种网络,例如:Internet、WAP网站、集团企业内部网络。而不同的接入点所能访问的范围网络以及接入的方式是不同的,网络侧如何知道手机激活以后要访问哪个网络从而分配哪个网段的IP呢,这就要靠APN来区分了,即APN决定了手机最终连接的网络是哪个。

二.APN的配置文件结构

配置文件的基本格式:由若干如下形式的节点组成

<apn carrier="Cosmote Internet"
mcc="202"
mnc="01"
apn=""
type="ia"
protocol="IPV4V6"
mvno_type="gid"
mvno_match_data="FF"
/>

<apn carrier="Cosmote Internet"
mcc="202"
mnc="01"
apn="internet"
type="default,supl"
protocol="IPV4V6"
roaming_protocol="IP"
mvno_type="gid"
mvno_match_data="FF"
/>


APN配置的几个属性解释:

carrier:apn list和apn详细信息中的apn名称

apn

现在我们涉及到的APN具体有两种,一种是通过手机浏览器上网使用的,另一种是通过客户端软件来登陆服务器。

国内的情况:中国联通的2G业务WAP浏览器中使用的APN为“UNIWAP”,3G业务WAP浏览器使用的APN为“3GWAP”;中国联通的2G上公网使用的APN为“UNINET”,3G业务上网卡及上公网使用的APN为“3GNET”。 中国移动上内网的APN为“CMWAP”,上网卡及上公网使用的APN为“CMNET”。 中国电信上内网的APN为“CTWAP”,上网卡及上公网使用的APN为“CTNET”。

好在现在国内销售的手机都已经将APN配置预先做好了,因此您不用为了APN的配置而太担心。

type:主要有5种

a.default

The Mobile data connection. When active, all data traffic will use this network type’s interface by default (it has a default route)

手机默认数据连接,激活时所有数据传输都会默认使用这种网络类型的接口

b.mms

An MMS-specific Mobile data connection. This network type may use the

same network interface as {@link #TYPE_MOBILE} or it may use a different

one. This is used by applications needing to talk to the carrier’s

Multimedia Messaging Service servers.

使用彩信服务时,必须有mms类型的接入点,不必选中,应用程序会自动使用此接入

c.supl

A SUPL-specific Mobile data connection. This network type may use the

same network interface as {@link #TYPE_MOBILE} or it may use a different

one. This is used by applications needing to talk to the carrier’s

Secure User Plane Location servers for help locating the device.

是Secure User Plane Location“安全用户面定位”的简写,这种网络类型可能被需要和载体的Secure User Plane Location servers传输数据的应用使用来帮助定位,使用场景:需要自动切换wap与net接入点的、需要把手机当临时AP(热点)的

d.dun

A DUN-specific Mobile data connection. This network type may use the

same network interface as {@link #TYPE_MOBILE} or it may use a different

one. This is sometimes by the system when setting up an upstream connection

for tethering so that the carrier is aware of DUN traffic.

Dial Up Networking拨号网络的简称,此连接用于执行一个拨号网络网桥,使载体能知道拨号网络流量的应用程序

适用场合:需要使用运营商无线热点的,CMCC、ChinaNet等

e.hipri

A High Priority Mobile data connection. This network type uses the

same network interface as {@link #TYPE_MOBILE} but the routing setup

is different.

高优先级网络,路由设置和默认方式不同。

这些属性不都是必要的,可以不写或者使用空字符串,此时在apn详细信息中显示的就是unset或者未设置。5个类型中default和mms比较重要,其他的可以不用详细了解。以上英文部分摘自android.net.ConnectivityManager

三.APN的显示流程代码分析(以MTK提供的源码为例,重点)

APN的显示流程大概是这样的:首次开机,创建数据库对应表,从配置文件(XML文件)读取配置到数据库,进入APN列表时根据sim卡参数从数据库筛选出正确的APN显示

1.新建db

手机第一次开机时,会读取该配置文件,对xml进行解析,并存储到数据库中,解析XML的并存储到数据库的代码一般在

packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java文件中

数据库的保存位置为:

/data/data/com.android.providers.telephony/databases/ telephony.db/Carriers表(7.0之前)

/data/user_de/0/com.android.providers.telephony/databases/telephony.db/Carriers表(7.0之后,包括7.0)

第一步新建数据库和数据库表。

新建DB代码:

public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, getVersion(context));
mContext = context;
mVersion = getVersion(mContext);
if (DBG) log("Version: [" + getVersion(mContext) + "]");

if (!BSP_PACKAGE) {
try {
mTelephonyProviderExt =
MPlugin.createInstance(ITelephonyProviderExt.class.getName(), mContext);
} catch (Exception e) {
e.printStackTrace();
}
}
}


网络上有人说

super(context, DATABASE_NAME, null, getVersion(context));


是创建数据库的地方,但是根据源码的解释,是在调用getReadable或者getWritabledatabase时才真正创建数据库的,原文(可以跟踪下源码):

package android.database.sqlite;
...
public abstract class SQLiteOpenHelper {
...
/**
* Create a helper object to create, open, and/or manage a database.
* This method always returns very quickly.  The database is not actually
* created or opened until one of {@link #getWritableDatabase} or
* {@link #getReadableDatabase} is called.
*
* @param context to use to open or create the database
* @param name of the database file, or null for an in-memory database
* @param factory to use for creating cursor objects, or null for the default
* @param version number of the database (starting at 1); if the database is older,
*     {@link #onUpgrade} will be used to upgrade the database; if the database is
*     newer, {@link #onDowngrade} will be used to downgrade the database
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
...
}


2.新建CARRIERS表

@Override
public void onCreate(SQLiteDatabase db) {
if (DBG) log("dbh.onCreate:+ db=" + db);
createSimInfoTable(db);
createCarriersTable(db, CARRIERS_TABLE);//创建表
initDatabase(db);
if (DBG) log("dbh.onCreate:- db=" + db);
}


具体的创建代码

private void createCarriersTable(SQLiteDatabase db, String tableName) {
// Set up the database schema
if (DBG) log("dbh.createCarriersTable start");
String columns = "(_id INTEGER PRIMARY KEY," +
NAME + " TEXT DEFAULT ''," +
NUMERIC + " TEXT DEFAULT ''," +
MCC + " TEXT DEFAULT ''," +
MNC + " TEXT DEFAULT ''," +
APN + " TEXT DEFAULT ''," +
USER + " TEXT DEFAULT ''," +
SERVER + " TEXT DEFAULT ''," +
PASSWORD + " TEXT DEFAULT ''," +
PROXY + " TEXT DEFAULT ''," +
PORT + " TEXT DEFAULT ''," +
MMSPROXY + " TEXT DEFAULT ''," +
MMSPORT + " TEXT DEFAULT ''," +
MMSC + " TEXT DEFAULT ''," +
AUTH_TYPE + " INTEGER DEFAULT -1," +
TYPE + " TEXT DEFAULT ''," +
CURRENT + " INTEGER," +
SOURCE_TYPE + " INTEGER DEFAULT 0," +
CSD_NUM + " TEXT DEFAULT ''," +
PROTOCOL + " TEXT DEFAULT IP," +
ROAMING_PROTOCOL + " TEXT DEFAULT IP,";

/// M: add for OMACP service
if (OMACP_SUPPORT) {
columns += OMACP_ID + " TEXT DEFAULT ''," +
NAP_ID + " TEXT DEFAULT ''," +
PROXY_ID + " TEXT DEFAULT '',";
}

columns += CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +
BEARER + " INTEGER DEFAULT 0," +
BEARER_BITMASK + " INTEGER DEFAULT 0," +
SPN + " TEXT DEFAULT ''," +
IMSI + " TEXT DEFAULT ''," +
PNN +  " TEXT DEFAULT ''," +
PPP +  " TEXT DEFAULT ''," +
MVNO_TYPE + " TEXT DEFAULT ''," +
MVNO_MATCH_DATA + " TEXT DEFAULT '',";

columns += SUBSCRIPTION_ID + " INTEGER DEFAULT " +
SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," +
PROFILE_ID + " INTEGER DEFAULT 0," +
MODEM_COGNITIVE + " BOOLEAN DEFAULT 0," +
MAX_CONNS + " INTEGER DEFAULT 0," +
WAIT_TIME + " INTEGER DEFAULT 0," +
MAX_CONNS_TIME + " INTEGER DEFAULT 0," +
MTU + " INTEGER DEFAULT 0," +
EDITED + " INTEGER DEFAULT " + UNEDITED + "," +
READ_ONLY + " BOOLEAN DEFAULT 0," +
USER_VISIBLE + " BOOLEAN DEFAULT 1, " +

// Uniqueness collisions are used to trigger merge code so if a field is listed
// here it means we will accept both (user edited + new apn_conf definition)
// Columns not included in UNIQUE constraint: name, current, edited,
// user, server, password, authtype, type, protocol, roaming_protocol, sub_id,
// modem_cognitive, max_conns, wait_time, max_conns_time, mtu, bearer_bitmask,
// user_visible
"UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));";

db.execSQL("CREATE TABLE " + tableName + columns);

db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_DM_TABLE);
db.execSQL("CREATE TABLE " + CARRIERS_DM_TABLE + columns);
if (DBG) log("dbh.createCarriersTable:-");
}


代码比较多但是很简单,最终就是执行了一个sql语句罢了

3.解析xml导入数据库

在上一步中看到创建数据库之后执行了initDatabase(db);该方法就是解析xml导入数据库的流程

/**
*  This function adds APNs from xml file(s) to db. The db may or may not be empty to begin
*  with.
*/
private void initDatabase(SQLiteDatabase db) {
if (VDBG) log("dbh.initDatabase:+ db=" + db);
// Read internal APNS data
Resources r = mContext.getResources();
XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
int publicversion = -1;
try {
XmlUtils.beginDocument(parser, "apns");
publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
loadApns(db, parser);
} catch (Exception e) {
loge("Got exception while loading APN database." + e);
} finally {
parser.close();
}

// Read external APNS data (partner-provided)
XmlPullParser confparser = null;
File confFile = getApnConfFile();

FileReader confreader = null;
if (DBG) log("confFile = " + confFile);
try {
confreader = new FileReader(confFile);
confparser = Xml.newPullParser();
confparser.setInput(confreader);
XmlUtils.beginDocument(confparser, "apns");

// Sanity check. Force internal version and confidential versions to agree
int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
if (publicversion != confversion) {
log("initDatabase: throwing exception due to version mismatch");
throw new IllegalStateException("Internal APNS file version doesn't match "
+ confFile.getAbsolutePath());
}

db.beginTransaction();
try {
loadApns(db, confparser);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} catch (FileNotFoundException e) {
// It's ok if the file isn't found. It means there isn't a confidential file
// Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
} catch (Exception e) {
loge("initDatabase: Exception while parsing '" + confFile.getAbsolutePath() + "'" +
e);
} finally {
// Get rid of user/carrier deleted entries that are not present in apn xml file.
// Those entries have edited value USER_DELETED/CARRIER_DELETED.
if (VDBG) {
log("initDatabase: deleting USER_DELETED and replacing "
+ "DELETED_BUT_PRESENT_IN_XML with DELETED");
}

// Delete USER_DELETED
db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);

// Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETED
ContentValues cv = new ContentValues();
cv.put(EDITED, USER_DELETED);
db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null);

// Change CARRIER_DELETED_BUT_PRESENT_IN_XML to CARRIER_DELETED
cv = new ContentValues();
cv.put(EDITED, CARRIER_DELETED);
db.update(CARRIERS_TABLE, cv, IS_CARRIER_DELETED_BUT_PRESENT_IN_XML, null);

if (confreader != null) {
try {
confreader.close();
} catch (IOException e) {
// do nothing
}
}

// Update the stored checksum
setApnConfChecksum(getChecksum(confFile));
}
if (VDBG) log("dbh.initDatabase:- db=" + db);

}


可以看出,这里解析一共有两次

一次是

Read internal APNS data

一次是

Read external APNS data (partner-provided)

但是流程都是类似的拿到XmlPullParser XML解析对象,拿到文件对象,调用loadApns解析xml。

loadApns的代码如下

/*
* Loads apns from xml file into the database
*
* @param db the sqlite database to write to
* @param parser the xml parser
*
*/
private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
if (parser != null) {
try {
db.beginTransaction();
XmlUtils.nextElement(parser);

/// M: for 02839078, when switch between multi-user, there will be two phone
// process at the same time for a short while, calling to SubscriptionManager
// API may become IPC call, which takes a lot of time in "while loop" and leads
// to ANR, so call it only once here
int subId = SubscriptionManager.getDefaultSubscriptionId();

while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
ContentValues row = getRow(parser);
if (row != null) {
if (!BSP_PACKAGE) {
// Add operator customized configuration in onLoadApns if need
try {
mTelephonyProviderExt.onLoadApns(row);
} catch (Exception e) {
e.printStackTrace();
}
}
/// M: for 02839078, pass the subId instead of getting it every time
// insertAddingDefaults(db, row);
insertAddingDefaults(db, row, subId);

XmlUtils.nextElement(parser);
} else {
//throw new XmlPullParserException("Expected 'apn' tag", parser, null);
break;  // do we really want to skip the rest of the file?
}
}
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();
}
}
}


android的xml解析大致有三种:

pull解析 Sax解析和Dom解析,可以看到上面采用的是pull解析

具体的解析方式对比可以参照:

Android XML数据解析http://www.runoob.com/w3cnote/android-tutorial-xml.html

如果xml没有读到end节点(parser.getEventType() != XmlPullParser.END_DOCUMENT),使用getRow方法从xml读出数据,再调用insertAddingDefaults方法将读到的数据插入创建的数据库表格,之后遍历下一个节点

getRow方法(解析一个xml apn节点变成一个map):

/**
* Gets the next row of apn values.
*
* @param parser the parser
* @return the row or null if it's not an apn
*/
private ContentValues getRow(XmlPullParser parser) {
if (!"apn".equals(parser.getName())) {
return null;
}

ContentValues map = new ContentValues();

String mcc = parser.getAttributeValue(null, "mcc");
String mnc = parser.getAttributeValue(null, "mnc");
String numeric = mcc + mnc;

map.put(NUMERIC, numeric);
map.put(MCC, mcc);
map.put(MNC, mnc);
map.put(NAME, parser.getAttributeValue(null, "carrier"));

// do not add NULL to the map so that default values can be inserted in db
addStringAttribute(parser, "apn", map, APN);
addStringAttribute(parser, "user", map, USER);
addStringAttribute(parser, "server", map, SERVER);
addStringAttribute(parser, "password", map, PASSWORD);
addStringAttribute(parser, "proxy", map, PROXY);
addStringAttribute(parser, "port", map, PORT);
addStringAttribute(parser, "mmsproxy", map, MMSPROXY);
addStringAttribute(parser, "mmsport", map, MMSPORT);
addStringAttribute(parser, "mmsc", map, MMSC);
addStringAttribute(parser, "type", map, TYPE);
addStringAttribute(parser, "protocol", map, PROTOCOL);
addStringAttribute(parser, "roaming_protocol", map, ROAMING_PROTOCOL);

addIntAttribute(parser, "authtype", map, AUTH_TYPE);
addIntAttribute(parser, "bearer", map, BEARER);
addIntAttribute(parser, "profile_id", map, PROFILE_ID);
addIntAttribute(parser, "max_conns", map, MAX_CONNS);
addIntAttribute(parser, "wait_time", map, WAIT_TIME);
addIntAttribute(parser, "max_conns_time", map, MAX_CONNS_TIME);
addIntAttribute(parser, "mtu", map, MTU);

addBoolAttribute(parser, "carrier_enabled", map, CARRIER_ENABLED);
addBoolAttribute(parser, "modem_cognitive", map, MODEM_COGNITIVE);
addBoolAttribute(parser, "user_visible", map, USER_VISIBLE);
addBoolAttribute(parser, "read_only", map, READ_ONLY);

int bearerBitmask = 0;
String bearerList = parser.getAttributeValue(null, "bearer_bitmask");
if (bearerList != null) {
bearerBitmask = ServiceState.getBitmaskFromString(bearerList);
}
map.put(BEARER_BITMASK, bearerBitmask);

String ppp = parser.getAttributeValue(null, "ppp");
if (ppp != null) {
map.put(Telephony.Carriers.PPP, ppp);
}

//keep for old version
String spn = parser.getAttributeValue(null, "spn");
if (spn != null) {
map.put(Telephony.Carriers.SPN, spn);
}
String imsi = parser.getAttributeValue(null, "imsi");
if (imsi != null) {
map.put(Telephony.Carriers.IMSI, imsi);
}
String pnn = parser.getAttributeValue(null, "pnn");
if (pnn != null) {
map.put(Telephony.Carriers.PNN, pnn);
}

String mvno_type = parser.getAttributeValue(null, "mvno_type");
if (mvno_type != null) {
String mvno_match_data = parser.getAttributeValue(null, "mvno_match_data");
if (mvno_match_data != null) {
map.put(MVNO_TYPE, mvno_type);
map.put(MVNO_MATCH_DATA, mvno_match_data);
}
}

return map;
}


至此,xml中的数据完全导入到数据库中

4.界面显示

界面显示的逻辑在一个fragment

packages/apps/Settings/src/com/android/settings/ApnSettings.java

逻辑也很清晰

在OnCreate进行变量初始化

public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Activity activity = getActivity();
final int subId = activity.getIntent().getIntExtra(SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);

mMobileStateFilter = new IntentFilter(
TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
/// M: for Airplane mode check
mMobileStateFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);

setIfOnlyAvailableForAdmins(true);

mSubscriptionInfo = SubscriptionManager.from(activity).getActiveSubscriptionInfo(subId);
mUiccController = UiccController.getInstance();

/// M: for [SIM Hot Swap] @{
mSimHotSwapHandler = new SimHotSwapHandler(getActivity().getApplicationContext());
mSimHotSwapHandler.registerOnSimHotSwap(new OnSimHotSwapListener() {
@Override
public void onSimHotSwap() {
Log.d(TAG, "onSimHotSwap, finish activity");
if (getActivity() != null) {
getActivity().finish();
}
}
});
/// @}

CarrierConfigManager configManager = (CarrierConfigManager)
getSystemService(Context.CARRIER_CONFIG_SERVICE);
PersistableBundle b = configManager.getConfig();
mHideImsApn = b.getBoolean(CarrierConfigManager.KEY_HIDE_IMS_APN_BOOL);
mAllowAddingApns = b.getBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL);
mUserManager = UserManager.get(activity);
}


onActivityCreated中设置布局文件

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getEmptyTextView().setText(R.string.apn_settings_not_available);
mUnavailable = isUiRestricted();
setHasOptionsMenu(!mUnavailable);
if (mUnavailable) {
setPreferenceScreen(new PreferenceScreen(getPrefContext(), null));
getPreferenceScreen().removeAll();
return;
}

addPreferencesFromResource(R.xml.apn_settings);
}


onResume注册监听并填充list,其中填充list是重点,即fillList方法。

@Override

public void onResume() {
Log.v("chj","onResume");
super.onResume();

if (mUnavailable) {
return;
}

getActivity().registerReceiver(mMobileStateReceiver, mMobileStateFilter);

if (!mRestoreDefaultApnMode) {

fillList();
/// M: In case dialog not dismiss as activity is in background, so when resume back,
// need to remove the dialog @{
removeDialog(DIALOG_RESTORE_DEFAULTAPN);
/// @}
}

/// M: for plug-in
mApnExt.updateTetherState();
}


fillList方法的代码如下:

private void fillList() {
final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
final String mccmnc = mSubscriptionInfo == null ? ""
: tm.getSimOperator(mSubscriptionInfo.getSubscriptionId());

String where = "numeric=\"" + mccmnc +
"\" AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND user_visible!=0";

/// M: for plug-in
where = mApnExt.getFillListQuery(where, mccmnc);

Log.d(TAG, "fillList where: " + where);
/// M: for CU default APN set.
/*
Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] {
"_id", "name", "apn", "type", "mvno_type", "mvno_match_data"}, where.toString(),
null, Telephony.Carriers.DEFAULT_SORT_ORDER);
*/
String order = mApnExt.getApnSortOrder(Telephony.Carriers.DEFAULT_SORT_ORDER);
Log.d(TAG, "fillList sort: " + order);
Cursor cursor = getContentResolver().query(
Telephony.Carriers.CONTENT_URI,
new String[] { "_id", "name", "apn", "type", "mvno_type", "mvno_match_data",
"sourcetype","read_only" }, where, null, order);
/// @}

if (cursor != null) {
Log.d(TAG, "fillList, cursor count: " + cursor.getCount());
IccRecords r = null;
if (mUiccController != null && mSubscriptionInfo != null) {
r = mUiccController.getIccRecords(SubscriptionManager.getPhoneId(
mSubscriptionInfo.getSubscriptionId()), UiccController.APP_FAM_3GPP);
}
PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");
apnList.removeAll();

/// M: for plug-in, use Preference instead ApnPreference for the
// convenience of plug-in side
ArrayList<Preference> mnoApnList = new ArrayList<Preference>();
ArrayList<Preference> mvnoApnList = new ArrayList<Preference>();
ArrayList<Preference> mnoMmsApnList = new ArrayList<Preference>();
ArrayList<Preference> mvnoMmsApnList = new ArrayList<Preference>();

mSelectedKey = getSelectedApnKey();
cursor.moveToFirst();

while (!cursor.isAfterLast()) {
String name = cursor.getString(NAME_INDEX);
String apn = cursor.getString(APN_INDEX);
String key = cursor.getString(ID_INDEX);
String type = cursor.getString(TYPES_INDEX);
String mvnoType = cursor.getString(MVNO_TYPE_INDEX);
String mvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX);
/// M: check source type, some types are not editable
int sourcetype = cursor.getInt(SOURCE_TYPE_INDEX);

/// M: for plug-in
name = mApnExt.updateApnName(name, sourcetype);

ApnPreference pref = new ApnPreference(getPrefContext());

pref.setKey(key);
pref.setTitle(name);
pref.setSummary(apn);
pref.setPersistent(false);
pref.setOnPreferenceChangeListener(this);
boolean isEdit = mApnExt.isAllowEditPresetApn(type, apn, mccmnc, sourcetype); //true 表示允许编辑
boolean isReadOnly = cursor.getInt(READ_ONLY_INDEX) == 1; //true表示不允许编辑
pref.setApnEditable(isEdit && !isReadOnly);

pref.setSubId(mSubscriptionInfo == null ? null : mSubscriptionInfo
.getSubscriptionId());

/// M: for ALPS02500557, do not select emergency APN
boolean selectable = ((type == null) || (!type.equals("mms")
&& !type.equals("ia") && !type.equals("ims")&& !type.equals("emergency")))
/// M: for plug-in
&& mApnExt.isSelectable(type);
pref.setSelectable(selectable);
Log.d(TAG, "mSelectedKey = " + mSelectedKey + " key = " + key + " name = " + name +
" selectable=" + selectable);
if (selectable) {
/// M: select prefer APN later, as the apn list are not solid now @{
/*
if ((mSelectedKey != null) && mSelectedKey.equals(key)) {
pref.setChecked();
}
*/
/// @}
addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData);
/// M: For CT feature,when apns-conf.xml add type extra value "supl",
//     selectable maybe ture when 46011 mms, so need this method.
mApnExt.customizeUnselectableApn(type, mnoApnList, mvnoApnList,
mSubscriptionInfo == null ? null : mSubscriptionInfo
.getSubscriptionId());
} else {
addApnToList(pref, mnoMmsApnList, mvnoMmsApnList, r, mvnoType, mvnoMatchData);
/// M: for plug-in
mApnExt.customizeUnselectableApn(type, mnoMmsApnList, mvnoMmsApnList,
mSubscriptionInfo == null ? null : mSubscriptionInfo
.getSubscriptionId());
}
cursor.moveToNext();
}
cursor.close();

if (!mvnoApnList.isEmpty()) {
mnoApnList = mvnoApnList;
mnoMmsApnList = mvnoMmsApnList;
// Also save the mvno info
}

for (Preference preference : mnoApnList) {
apnList.addPreference(preference);
}
for (Preference preference : mnoMmsApnList) {
apnList.addPreference(preference);
}

/// M: always set a prefer APN
setPreferApnChecked(mnoApnList);

/// M: update screen enable state according to airplane mode, SIM radio status, etc.
updateScreenEnableState(getActivity());
}
}


其逻辑分析如下:

从sim卡中得到卡里记录的mcc mnc,并将mcc mnc写入查询语句,比如插入一张移动卡,查询语句应该是类似这样的

select * from carriers where mcc = ‘460’ and mnc = ‘00’

查询的apn放入cursor对象,遍历cursor,进行过滤和显示

首先清空界面list数据

创建四个list用于存储apn

mnoApnList:存储母运营商apn(mno)

mvnoApnList:存储虚拟运营商apn(mvno)

mnoMmsApnList:存储母运营商Mms apn(mno)

mvnoMmsApnList:存储虚拟运营商Mms apn(mvno)

带Mms和不带Mms的list区别在与是否可选择,mms的是不可选择的,而不带mms的界面item后面有个radiobutton,可以选择,表示当前优先使用的apn

mno和mvno的区别在于是否有mvno_match_data和mvno_type,mno是没有的之后会详细说

代码继续解释:得到cursor的内容,存储到ApnPreference对象用于之后显示

判断apn是否可选择,根据结果设置显示式样

如果可选择,调用addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData);将apn填充到mnoApnList, mvnoApnList

否则填充到mnoMmsApnList, mvnoMmsApnList

最后判断mvnoApnList.isEmpty,如果是空的,则说明是母运营商(mvno_match_data和mvno_type为空),显示的其实是mnoApnList,如果不是空,则显示mvnoApnList,mnoMmsApnList和mvnoMmsApnList的逻辑是一样的。

最后再看下addApnToList方法

private void addApnToList(ApnPreference pref, ArrayList<Preference> mnoList,
ArrayList<Preference> mvnoList, IccRecords r, String mvnoType,
String mvnoMatchData) {
Log.d(TAG, "mvnoType = " + mvnoType + ", mvnoMatchData = " + mvnoMatchData);
if (r != null && !TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData)) {
if (ApnSetting.mvnoMatches(r, mvnoType, mvnoMatchData)) {
mvnoList.add(pref);
// Since adding to mvno list, save mvno info
mMvnoType = mvnoType;
mMvnoMatchData = mvnoMatchData;
}
} else {
mnoList.add(pref);
}
}


代码中可以看出mvnoList和mnoList的区别就在mMvnoType和mMvnoMatchData是否为空,而上面一步中mvnoApnList.isEmpty的判断也是由addApnToList决定的。

那么,apn的显示流程就分析完了,如有错误,请帮忙指正

注:以上所有源码中,为了便于阅读和理解,删除了部分代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  APN Android