admin 管理员组

文章数量: 1184232

APN

关于Telephony data 准备写三篇,本文是第一部分APN,根据Android O源码,简单总结了Android上APN相关的知识点。分以下三部分:
1. 预置APN数据加载
2. APN字段
3. APN的显示和编辑


APN(access point name)决定了我们手机接入网络的方式,一般我们手机都做了默认配置,也可以根据需要进行手动配置。APN存储在手机的“/data/user_de/0/com.android.providers.telephony/databases/”路径下的telephony.db中(多用户的情况下,只有owner才有这份数据),对应表是carriers。

1. 预置APN数据加载
关于db和表的创建,Subscription文章 TelephonyProvider部分已经写了,这里就不再赘述了。下面说下预置APN数据的加载过程。
我们预置的APN数据也是在db创建的过程中从配置文件读取然后加载到db的。
在db创建的过程中,DatabaseHelper.OnCreate方法会调用DatabaseHelper.initDatabase方法,该方法可以从配置文件中加载我们要预置的apn数据。

        /***  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);//加载frameworks/base/core/res/res/xml/apns.xml 中的配置int publicversion = -1;try {XmlUtils.beginDocument(parser, "apns");publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));loadApns(db, parser);//调用loadApns方法完成加载任务。} catch (Exception e) {loge("Got exception while loading APN database." + e);} finally {parser.close();}// Read external APNS data (partner-provided)XmlPullParser confparser = null;//下面的部分获取指定路径下的APN配置文件。//getApnConfFile方法用于获取配置文件,常用路径有三个://1. etc/apns-conf.xml//2. telephony/apns-conf.xml//3. misc/apns-conf.xml//但是这三个文件并不会全部加载, 只加载最新修改的那个。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());}loadApns(db, confparser);//调用loadApns方法完成加载任务。} 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");}//到这里, 需要预置的APN已经全部加载到db中了, 但是db中可能存入了不必要的数据,//下面的code是清除,更新db里的数据。// Delete USER_DELETEDdb.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);//下面的code用于清除为了解决插入数据时的冲突而加了特殊标记的记录。// 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);}

getApnConfFile方法用于获取配置文件,常用路径有三个,但是这三个文件并不会全部加载, 只加载最新修改的那个。
1. etc/apns-conf.xml
2. telephony/apns-conf.xml
3. misc/apns-conf.xml

        private File getApnConfFile() {// Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);//etc/apns-conf.xmlFile oemConfFile =  new File(Environment.getOemDirectory(), OEM_APNS_PATH);//telephony/apns-conf.xmlFile updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH);//misc/apns-conf.xmlconfFile = getNewerFile(confFile, oemConfFile);confFile = getNewerFile(confFile, updatedConfFile);return confFile;}

2. APN字段

APN字段的定义在Telephony.java中的静态内部类Carriers中,总结如下:

字段名称描述
NAMEAPN记录的名字; 我们手机里内置的运营APN会使用运营商的名字,例如”中国移动(China Mobile)Net”
APNAPN的名字
PROXY代理地址
PORT代理端口
MMSPROXYMMS代理地址
MMSPORTMMS代理端口
SERVER服务器地址
USERAPN用户名
PASSWORDAPN用户名对应的密码
MMSC多媒体消息业务中心, 即彩信业务中心
MCC移动国家码
MNC移动网络码
NUMERIC运营商的数字代码,一般是MCC+MNC的形式,例如46001
AUTH_TYPE鉴权类型,一般使用的鉴权是PAP或CHAP
TYPEAPN的类型,如mms,dun,ims等; 一个APN配置可以支持多种APN type, 不同类型用”,”分割
PROTOCOL连接APN所使用的协议,例如IP,IPV6,IPV4V6,PPP等
ROAMING_PROTOCOL漫游时所使用的协议
CURRENT添加新APN时,如果新APN和当前SIM的mcc,mnc相同,这个字段会被设置为1。 没有看到其他地方使用这个字段, 所以不太清楚这个字段的作用
CARRIER_ENABLED用于标识这个APN是否可用
BEARERRadio Access Technology 信息, 当前可用的有LTE(14)和eHRPD(13)
BEARER_BITMASKRadio Access Technology bitmask, 用于标明当前APN可以包含的RAT; 0表示所有的RAT都可以,否则bitmask和RAT的关系是(1 << (RAT - 1))
MVNO_TYPE移动虚拟网络运营商(Mobile virtual network operator)的类型; 可用的数据有spn, IMSI和GID(Group Identifier Level 1)
MVNO_MATCH_DATAMVNO_TYPE数据, 这个值是和MVNO_TYPE对应的。 例如SPN: A MOBILE, BEN NL, …; IMSI: 302720x94, 2060188, …; GID: 4E, 33, …
SUBSCRIPTION_ID用于表明这个APN属于哪个subscription, 这个值是从siminfo表中获取的
PROFILE_IDprofile是modem侧存储信息的方式, 这个值将APN和modem侧的profile联系起来
MODEM_COGNITIVE用于标明这个APN是否会在modem侧设置(没用过这个字段)
MAX_CONNS该APN支持的最大连接数量
WAIT_TIME使用该APN进行数据连接时, 如果失败, retry要等待的时间
MAX_CONNS_TIME
MTU使用该APN建立的连接,可以传输的最大单元
EDITED该APN是否可以编辑
USER_VISIBLE是否可见,如果不可见, 那么我们在APN菜单里是看不到的

3. APN的显示和编辑

一般在手机的”settings->More->Mobile networks->Access point name”菜单中可以看到当前手机可用的Apn信息,也可以编辑添加Apn。进入菜单后的菜单如下图:

图例中显示了两个apn,点击任意一个可以进入编辑界面; 点击右上角的“+”号,可以添加新apn; 右上角的选项菜单用于将apn恢复成默认配置。
图片所示界面对应的代码是ApnSettings.java类,这是一个PreferenceFragment子类,中间有多层继承关系。ApnSettings作为一个fragment子类有自己的生命周期; 在启动时,OnResume()方法会调用fillList()放法,该方法完成了从db中查询Apn数据的任务,所以如果要完成一些APN的显示定制需求,可以在这个函数中修改sql语句。

    private void fillList() {final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);//mSubscriptionInfo是在onCreate()方法中根据intent中的参数从SubscriptionManager中获取的。//即指定SIM卡对应的Subscription信息。final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId(): SubscriptionManager.INVALID_SUBSCRIPTION_ID;//获取mccmnc信息,这是查询APN信息的重要依据。final String mccmnc = mSubscriptionInfo == null ? "" : tm.getSimOperator(subId);Log.d(TAG, "mccmnc = " + mccmnc);StringBuilder where = new StringBuilder("numeric=\"" + mccmnc +"\" AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND user_visible!=0");//IMS APN是否需要隐藏可以在CarrierConfig中配置,//mHideImsApn是在OnCreate方法中从CarrierConfigManager(CarrierConfigManager.KEY_HIDE_IMS_APN_BOOL)中获取的值if (mHideImsApn) {where.append(" AND NOT (type='ims')");}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);if (cursor != null) {IccRecords r = null;if (mUiccController != null && mSubscriptionInfo != null) {//获取SIM卡对应的IcccRecords记录, 后面会用到。r = mUiccController.getIccRecords(SubscriptionManager.getPhoneId(subId), UiccController.APP_FAM_3GPP);}PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");apnList.removeAll();//下面创建了四个ArrayList<ApnPreference>对象, 用于存储不同类型的APN数据。ArrayList<ApnPreference> mnoApnList = new ArrayList<ApnPreference>();ArrayList<ApnPreference> mvnoApnList = new ArrayList<ApnPreference>();ArrayList<ApnPreference> mnoMmsApnList = new ArrayList<ApnPreference>();ArrayList<ApnPreference> mvnoMmsApnList = new ArrayList<ApnPreference>();mSelectedKey = getSelectedApnKey();cursor.moveToFirst();while (!cursor.isAfterLast()) {String name = cursor.getString(NAME_INDEX);//APN记录的名字String apn = cursor.getString(APN_INDEX);//APN名字String key = cursor.getString(ID_INDEX);//这个是数据库里每项记录的主键IDString type = cursor.getString(TYPES_INDEX);//类型String mvnoType = cursor.getString(MVNO_TYPE_INDEX);//MVNO 类型String mvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX);//MVNO 数据ApnPreference pref = new ApnPreference(getPrefContext());//创建ApnPreference对象保存APN数据pref.setKey(key);pref.setTitle(name);pref.setSummary(apn);pref.setPersistent(false);pref.setOnPreferenceChangeListener(this);pref.setSubId(subId);boolean selectable = ((type == null) || !type.equals("mms"));//将mms类型的APN和非mms类型的APN分开处理pref.setSelectable(selectable);if (selectable) {if ((mSelectedKey != null) && mSelectedKey.equals(key)) {pref.setChecked();}//addApnToList函数负责将不同类型的APN放进前面创建的四个不同ArrayList<ApnPreference>对象中。addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData);} else {addApnToList(pref, mnoMmsApnList, mvnoMmsApnList, r, mvnoType, mvnoMatchData);}cursor.moveToNext();}cursor.close();//如果有MVNO类型的APN, mvnoApnList和mnoMmsApnList就分别重新指向保存了MVNO类型APN的mvnoApnList//和mvnoMmsApnList, 结果是mvnoApnList和mnoMmsApnList之前存储的APN记录不会被显示出来。if (!mvnoApnList.isEmpty()) {mnoApnList = mvnoApnList;mnoMmsApnList = mvnoMmsApnList;// Also save the mvno info}for (Preference preference : mnoApnList) {//添加显示mnoApnList中的APN记录apnList.addPreference(preference);}for (Preference preference : mnoMmsApnList) {//添加显示mnoMmsApnList中的APN记录apnList.addPreference(preference);}}}

addApnToList函数负责将不同类型的APN放进前面创建的四个不同ArrayList<ApnPreference>对象中; 下面是具体代码,相关逻辑比较简单, 在IccRecords,MVNO_type和MVNO_data数据都非空的时候调用ApnSetting.mvnoMatches去比较IccRecords和MVNO_data中的数据,如果匹配,那么这个APN是有效的,会被放进List中。

    private void addApnToList(ApnPreference pref, ArrayList<ApnPreference> mnoList,ArrayList<ApnPreference> mvnoList, IccRecords r, String mvnoType,String 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);}}

添加,编辑APN都是从ApnSettings跳转到ApnEditor,并由ApnEditor.validateAndSave方法将数据写入数据库。
当添加新APN进入ApnEditor时,ApnEditor.onCreate方法根据intent的action(ACTION_INSERT),先在数据库中插入了一条空数据,并将包含数据记录ID的uri保存在mUri中,以便在ApnEditor.validateAndSave中使用。所以ApnEditor.validateAndSave将数据写入数据库时可以使用update方法,这样也同时满足了编辑操作的需求。

“Restore to default”是什么实现的?
当点击”Restore to defualt”菜单时,ApnSettings.onOptionsItemSelected 方法会调用
ApnSettings.restoreDefaultApn方法。

    private boolean restoreDefaultApn() {showDialog(DIALOG_RESTORE_DEFAULTAPN);//显示一个友好的提示框mRestoreDefaultApnMode = true;//标识变量if (mRestoreApnUiHandler == null) {mRestoreApnUiHandler = new RestoreApnUiHandler();//这个是主线程中的handler}if (mRestoreApnProcessHandler == null ||mRestoreDefaultApnThread == null) {mRestoreDefaultApnThread = new HandlerThread(//创建了一个HandlerThread"Restore default APN Handler: Process Thread");mRestoreDefaultApnThread.start();//启动新创建的HandlerThreadmRestoreApnProcessHandler = new RestoreApnProcessHandler(mRestoreDefaultApnThread.getLooper(), mRestoreApnUiHandler);//使用HandlerThread的looper创建了一个handler, mRestoreApnUiHandler用于通知UI。}mRestoreApnProcessHandler.sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START);// 发送EVENT_RESTORE_DEFAULTAPN_START,开始恢复数据。return true;}

RestoreApnProcessHandler会处理EVENT_RESTORE_DEFAULTAPN_START,操作比较简单,只是通过ContentResolver调用delete去删除数据。下面我们看看Uri的内容,已经TelephonyProvider如何做的处理。
Uri处理是通过getUriForCurrSubId(Uri uri)方法,该方法是将SubId和传入的Uri拼接到一块; 而传入的参数是ApnSettings定义好的常量,如下:

    public static final String RESTORE_CARRIERS_URI ="content://telephony/carriers/restore";...private static final Uri DEFAULTAPN_URI = Uri.parse(RESTORE_CARRIERS_URI);

所以完整的Uri是”content://telephony/carriers/restore/subId/*“。
下面的TelephonyProvider中Macher的定义:

    static {s_urlMatcher.addURI("telephony", "carriers", URL_TELEPHONY);s_urlMatcher.addURI("telephony", "carriers/current", URL_CURRENT);----------省略------------/*restore 对应的Uri*/s_urlMatcher.addURI("telephony", "carriers/restore/subId/*", URL_RESTOREAPN_USING_SUBID);...s_currentSetMap = new ContentValues(1);s_currentSetMap.put(CURRENT, "1");}

从上面代码中可以看出,ApnSettings中的Uri对应的是URL_RESTOREAPN_USING_SUBID,下面截取了delete方法中的相关code:

    @Overridepublic synchronized int delete(Uri url, String where, String[] whereArgs){int count = 0;int subId = SubscriptionManager.getDefaultSubscriptionId();String userOrCarrierEdited = ") and (" +EDITED + "=" + USER_EDITED +  " or " +EDITED + "=" + CARRIER_EDITED + ")";String notUserOrCarrierEdited = ") and (" +EDITED + "!=" + USER_EDITED +  " and " +EDITED + "!=" + CARRIER_EDITED + ")";ContentValues cv = new ContentValues();cv.put(EDITED, USER_DELETED);checkPermission();SQLiteDatabase db = getWritableDatabase();int match = s_urlMatcher.match(url);switch (match){----------省略------------case URL_RESTOREAPN_USING_SUBID: {String subIdString = url.getLastPathSegment();//解析subId数据try {subId = Integer.parseInt(subIdString);//转换成int类型} catch (NumberFormatException e) {loge("NumberFormatException" + e);throw new IllegalArgumentException("Invalid subId " + url);}if (DBG) log("subIdString = " + subIdString + " subId = " + subId);// FIXME use subId in query}case URL_RESTOREAPN: {count = 1;restoreDefaultAPN(subId);//调用restoreDefaultAPN(int subId)方法完成restore操作break;}----------省略------------}----------省略------------}

restoreDefaultAPN方法会直接删除carrier表和 shared preferences 中的数据,然后调用DatabaseHelper.initDatabase方法重新初始数据。也就是说APN相关数据全部删除,重新从配置文件中初始化。

    private void restoreDefaultAPN(int subId) {SQLiteDatabase db = getWritableDatabase();try {db.delete(CARRIERS_TABLE, null, null);//直接删除carrier表中的全部数据} catch (SQLException e) {loge("got exception when deleting to restore: " + e);}//下面的code删除了shared preferences文件// delete preferred apn ids and preferred apns (both stored in diff SharedPref) for all// subIdsSharedPreferences spApnId = getContext().getSharedPreferences(PREF_FILE_APN,Context.MODE_PRIVATE);SharedPreferences.Editor editorApnId = spApnId.edit();editorApnId.clear();editorApnId.apply();SharedPreferences spApn = getContext().getSharedPreferences(PREF_FILE_FULL_APN,Context.MODE_PRIVATE);SharedPreferences.Editor editorApn = spApn.edit();editorApn.clear();editorApn.apply();initDatabaseWithDatabaseHelper(db);//该方法会调用DatabaseHelper.initDatabase方法。}

结束!

本文标签: APN