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

2024-05-30 12:49

本文主要是介绍Android APN的显示流程源代码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一.名词介绍

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表

        @Overridepublic 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 schemaif (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 serviceif (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 dataResources 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 agreeint 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_DELETEDdb.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);// Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETEDContentValues 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_DELETEDcv = 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 checksumsetApnConfChecksum(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 hereint 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 needtry {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 dbaddStringAttribute(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 versionString 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 checkmMobileStateFilter.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() {@Overridepublic 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中设置布局文件

    @Overridepublic 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-inmApnExt.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-inwhere = 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 sideArrayList<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 editableint sourcetype = cursor.getInt(SOURCE_TYPE_INDEX);/// M: for plug-inname = 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 APNboolean 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-inmApnExt.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 APNsetPreferApnChecked(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 infomMvnoType = mvnoType;mMvnoMatchData = mvnoMatchData;}} else {mnoList.add(pref);}}

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

那么,apn的显示流程就分析完了,如有错误,请帮忙指正
注:以上所有源码中,为了便于阅读和理解,删除了部分代码

这篇关于Android APN的显示流程源代码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1016233

相关文章

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

如何设置vim永久显示行号

《如何设置vim永久显示行号》在Linux环境下,vim默认不显示行号,这在程序编译出错时定位错误语句非常不便,通过修改vim配置文件vimrc,可以在每次打开vim时永久显示行号... 目录设置vim永久显示行号1.临时显示行号2.永www.chinasem.cn久显示行号总结设置vim永久显示行号在li

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Python实现NLP的完整流程介绍

《Python实现NLP的完整流程介绍》这篇文章主要为大家详细介绍了Python实现NLP的完整流程,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 编程安装和导入必要的库2. 文本数据准备3. 文本预处理3.1 小写化3.2 分词(Tokenizatio

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专