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

相关文章

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

Spring Boot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)

《SpringBoot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)》本文将以一个实际案例(用户管理系统)为例,详细解析SpringBoot中Co... 目录引言:为什么学习Spring Boot分层架构?第一部分:Spring Boot的整体架构1.1

nodejs打包作为公共包使用的完整流程

《nodejs打包作为公共包使用的完整流程》在Node.js项目中,打包和部署是发布应用的关键步骤,:本文主要介绍nodejs打包作为公共包使用的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言一、前置准备二、创建与编码三、一键构建四、本地“白嫖”测试(可选)五、发布公共包六、常见踩坑提醒

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

Ubuntu向多台主机批量传输文件的流程步骤

《Ubuntu向多台主机批量传输文件的流程步骤》:本文主要介绍在Ubuntu中批量传输文件到多台主机的方法,需确保主机互通、用户名密码统一及端口开放,通过安装sshpass工具,准备包含目标主机信... 目录Ubuntu 向多台主机批量传输文件1.安装 sshpass2.准备主机列表文件3.创建一个批处理脚

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

C#实现SHP文件读取与地图显示的完整教程

《C#实现SHP文件读取与地图显示的完整教程》在地理信息系统(GIS)开发中,SHP文件是一种常见的矢量数据格式,本文将详细介绍如何使用C#读取SHP文件并实现地图显示功能,包括坐标转换、图形渲染、平... 目录概述功能特点核心代码解析1. 文件读取与初始化2. 坐标转换3. 图形绘制4. 地图交互功能缩放