目的:
为了访问网络,手机必须设置合适的APN参数。Android中的apn是配置在apns-conf.xml文件中,由手机开机时加载到TelephonyProvider中。然后供设置查看和编辑,供框架使用来进行数据拨号。本文旨在描述这APN加载、显示和编辑的过程。
版本
Android 6.0
前言
APN的英文全称是Access Point Name,中文全称叫接入点,是您在通过手机上网时必须配置的一个参数,它决定了您的手机通过哪种接入方式来访问网络。从运营商角度看,APN就是一个逻辑名字,APN一般都部署在GGSN设备上或者逻辑连接到GGSN上,用户使用GPRS上网时同,都通过GGSN代理出去到外部网络。
GGSN (Gateway GPRS Support Node) 网关GPRS支持节点,GGSN(Gateway GSN,网关GSN)主要是起网关作用,它可以和多种不同的数据网络连接,如ISDN、PSPDN和LAN等。GGSN可以把GSM网中的GPRS分组数据包进行协议转换,从而可以把这些分组数据包传送到远端的TCP/IP或X.25网络。GGSN具有网络控制的信息屏蔽功能,可以选择哪些分组能够进入GPRS网络,以便保证GPRS网络的安全。
一. TelephonyProvider
TelephonyProvider继承自ContentProvider,在android中的代码路径为packages/providers/TelephonyProvider。
public class TelephonyProvider extends ContentProvider
从上面的代码可以看出TelephonyProvider继承自ContentProvider,因此为了了解TelephonyProvider的启动过程,最好先明白ContentProvider是如何加载的。
1-ContentProvider加载方式简要介绍
在ContentProvider对应的AndroidManifest.xml文件中,我们可以通过android:sharedUserId和android:process来指定ContentProvider运行的进程。如果不指定这些参数,那么该ContentProvider运行在定义它的独立进程中(或者说定义它的APK对应的进程中)。
1) ContentProvider和某个进程同属一个进程时,当该进程启动时,会搜索属于该进程的所有ContentProvider,并加载。
2) ContentProvider属于独立的一个进程时,只有需要用到该ContentProvider时,才会去加载。
当一个进程想要操作一个ContentProvider时,先需要获取该ContentProvider的对象,系统是这样处理的:
1) 如果该ContentProvider属于当前主叫进程,因为在进程启动时就已经加载过了,所以系统会直接返回该ContentProvider的对象。
2) 如果该ContentProvider不属于当前主叫进程,那么系统会进行相关处理(由ActivityManagerService进行,以下简称为AMS,所有已加载的ContentProvider信息都已保存在AMS中):
当需要获取某个ContentProvider的对象时,AMS会先判断该ContentProvider是否已被加载。
如果已被加载,且该ContentProvider和当前主叫进程不属一个进程,但是该ContentProvider设置了multiprocess的属性,并且该ContentProvider属于系统级ContentProvider,那么就在当前主叫进程内部新生成该ContentProvider的对象;否则就需要通过IPC机制进行调用。
如果还未被加载,且该ContentProvider和当前主叫进程不属一个进程,但是该ContentProvider设置了multiprocess的属性,并且该ContentProvider属于系统级ContentProvider,那么就在当前主叫进程内部新生成该ContentProvider的对象;否则就需要先创建该ContentProvider所在的进程,然后再通过IPC机制进行调用。
2-TelephonyProvider的加载
我们来截取一段TelephonyProvider对应AndroidManifest.xml的内容:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.providers.telephony" coreApp="true" android:sharedUserId="android.uid.phone"> ...... <application android:process="com.android.phone" android:allowClearUserData="false" android:allowBackup="false" android:label="@string/app_label" android:icon="@mipmap/ic_launcher_phone" android:usesCleartextTraffic="true"> <provider android:name="TelephonyProvider" android:authorities="telephony" android:exported="true" android:singleUser="true" android:multiprocess="false" />
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
从这段代码,我们可以看出TelephonyProvider是运行在phone进程中的,同事其multiprocess的值为false,也就意味着若其它进程要访问TelephonyProvider,必须使用IPC机制进行调用。
由于phone进程是开机就启动的,因此TelephonyProvider在开机的时候,就会被加载到AMS中。
3- TelephonyProvider的主要行为
我们首先来看看TelephonyProvider的onCreate函数。
@Override public boolean onCreate() { mOpenHelper = new DatabaseHelper(getContext()); ...... SQLiteDatabase db = mOpenHelper.getReadableDatabase(); String newBuildId = SystemProperties.get("ro.build.id", null); if (!TextUtils.isEmpty(newBuildId)) { SharedPreferences sp = getContext().getSharedPreferences(BUILD_ID_FILE, Context.MODE_PRIVATE); String oldBuildId = sp.getString(RO_BUILD_ID, ""); if (!newBuildId.equals(oldBuildId)) { SubscriptionManager sm = SubscriptionManager.from(getContext()); if (sm != null) { List<SubscriptionInfo> subInfoList = sm.getAllSubscriptionInfoList(); for (SubscriptionInfo subInfo : subInfoList) { SharedPreferences spPrefFile = getContext().getSharedPreferences( PREF_FILE + subInfo.getSubscriptionId(), Context.MODE_PRIVATE); if (spPrefFile != null) { SharedPreferences.Editor editor = spPrefFile.edit(); editor.clear(); editor.apply(); } } } updateApnDb(); } else { if (VDBG) log("onCreate: build id did not change: " + oldBuildId); } sp.edit().putString(RO_BUILD_ID, newBuildId).apply(); } else { if (VDBG) log("onCreate: newBuildId is empty"); } if (VDBG) log("onCreate:- ret true"); return true; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
从上面的代码,我们知道TelephonyProvider初始化时的主要工作包括:1. 创建出数据库;2. 根据build_id的值,判断是否需要清楚旧有的存储信息,并更新数据库。
可以看出TelephonyProvider的主要工作,就是围绕数据库的操作展看的。
从上面的代码可以看出,与常见的方式类似,TelephonyProvider也是通过定义一个SQLiteOpenHelper,即DatabaseHelper来封装底层对数据的直接操作。
private static class DatabaseHelper extends SQLiteOpenHelper { ...... @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); }
这里需要注意的是,在创建两个table时,其实对某些列定义了默认值。因此,即使apn的配置文件中没有定义一些字段,这些字段也是有值的。
举个例子,在创建运营商的table时,会将user_visible的值默认置为1,read_only的值默认置为0等。
........"user_visible BOOLEAN DEFAULT 1," +"read_only BOOLEAN DEFAULT 0," +
接下来我们可以看看,initDatabase初始化数据库的主要过程:
private void initDatabase(SQLiteDatabase db) { Resources r = mContext.getResources(); XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns); ......... loadApns(db, parser); ......... XmlPullParser confparser = null; ........... loadApns(db, confparser); ............ }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
真实的过程可能比这个稍微复杂一下,但上面的代码应该包含了最主要的逻辑,其实就是找到apns-config.xml文件,然后解析这个xml文件,然后调用loadApns将xml中定义的数据,插入到TelephonyProvider底层的数据库中。
我们可以再简单看一看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();
getRow和insertAddingDefaults的内容过于单纯,就不再进一步分析。
分析到这里,我们应该还剩最后一个疑问,apn对应的xml到底在哪里,长什么样子?
实际上android自带的内部APN配置文件,定义于frameworks/base/core/res/res/xml/apns.xml中,其实是个空文件。
于是,android中可用的APN配置文件,为外部定义的文件。
在Android源码build目录下,通过搜索apns-conf.xml可以找到在各个board中分别有配置:
device/generic/goldfish/data/etc/apns-conf.xml:system/etc/apns-conf.xml
在编译该product时会将device/generic/goldfish/data/etc/apns-conf.xml文件拷贝到system/etc/目录下,最后打包到system.img中。
上面是通常的解释,但实际上各个厂商实际上采用了一种Overlay机制,在编译的时候可以替换资源文件。不同厂商新建了自己的apns-conf.xml文件,放在自己指定的目录下,例如vendor/xxxx/xxxx/xxxx/etc/apns-conf.xml,然后编译时将改路径下的apns-conf.xml文件编入system.img,这才是实际使用的APN xml。
在这一部分的最后,我们举例来看看apns-conf.xml中的内容的形式:
<apn carrier="中国移动彩信 (China Mobile)" mcc="460" mnc="00" apn="cmwap" proxy="10.0.0.172" port="80" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" protocol="IPV4V6" />
这就是apns-conf.xml中,中国移动发送彩信时对应的APN配置。
二. APN显示、修改和新建
PART-I ApnSettings
在上一部分,我们知道在开机时,Android启动Phone进程后,会加载Phone进程中的TelephonyProvider。而TelephonyProvider在创建时,会将apns-conf.xml中的数据添加的到数据库。
以上这些都是看不见的操作。对于用户而言,只能通过设置界面才能看到当前使用的APN,并进行新建和修改的操作。
在Android中,设置是一个系统级的APK。不同厂商的ROM中,设置的界面被组织成不同的形式。因此,我们仅来分析一下,接近原生的比较共性的流程。
一般而言,Settings.apk中有许多activity、fragment。与Telephony有关的界面,将在Setting.apk的AndroidManifest.xml通过android:process字段,指定其运行在Phone进程中。
public class ApnSettings extends SettingsPreferenceFragment implements Preference.OnPreferenceChangeListener {
与Apn操作有关的具体的界面是一个Fragment,当然它也是运行在phone进程中的。在这里,我们不分析太多细节,主要分析一下我们比较关注的显示、新建和编辑过程。
@Override public void onCreate(Bundle icicle) { ........ mSubId = activity.getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, SubscriptionManager.INVALID_SUBSCRIPTION_ID); ...... mSubscriptionInfo = SubscriptionManager.from(activity).getActiveSubscriptionInfo(mSubId); ....... mAddNewApn = new Preference(getActivity()); mAddNewApn.setTitle(R.string.menu_new); mAddNewApn.setOrder(0); ........
可以看到ApnSettings的onCreate中,仅完成部分初始化的工作,并没有与之前的数据库关联起来。
我们接着来看一下,该界面的onResume函数。
@Override public void onResume() { ....... fillList(); ....... }
其中实际的工作,由fillList来完成。
private void fillList() { ........ final String mccmnc = getOperatorNumeric(); ........ ........ PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list"); ......... 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); boolean readOnly = (cursor.getInt(RO_INDEX) == 0); .......... ApnPreference pref = new ApnPreference(getActivity()); pref.setSubId(mSubId); pref.setApnReadOnly(readOnly); pref.setWidgetLayoutResource (R.layout.leui_widget_arrow); pref.setKey(key); pref.setTitle(name); pref.setSummary(apn); pref.setPersistent(false); pref.setOnPreferenceChangeListener(this); ........ cursor.moveToNext(); } cursor.close(); ......... if (mAddNewApn != null) { mAddNewApn.setOrder(1); apnList.addPreference(mAddNewApn); } .......}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
看完fillList,我们终于将UI和底层的数据库关联起来了。现在我们知道,当ApnSetting界面被加载时,对应卡可用APN的主要信息将以Preference的形式显示在界面上。
我们现在可以点击ApnPreference看到更加详细的信息,也可以点击新建APN自己编辑APN信息,也可以选择Prefer APN(拨号时,优先选择的APN)。
在ApnSettings中,onPreferenceChange决定了优选APN。
public boolean onPreferenceChange(Preference preference, Object newValue) { if (newValue instanceof String) { setSelectedApnKey((String) newValue); } return true;}
private void setSelectedApnKey(String key) { mSelectedKey = key; ContentResolver resolver = getContentResolver(); ContentValues values = new ContentValues(); values.put(APN_ID, mSelectedKey); resolver.update(getUri(PREFERAPN_URI), values, null, null);}
在ApnSettings的onPreferenceTreeClick函数中,定义了新建Apn时的操作。
@Overridepublic boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { .................... } else if (preference == mAddNewApn) { addNewApn(); } return true;}
private void addNewApn() { Intent intent = new Intent(Intent.ACTION_INSERT, getUri(Telephony.Carriers.CONTENT_URI)); ......... startActivity(intent);}
在ApnPreference的onClick函数中,定义了点击ApnPreference的操作。
public void onClick(android.view.View v) { ....... Uri url = ContentUris.withAppendedId( Telephony.Carriers.CONTENT_URI, pos); Intent intent = new Intent(Intent.ACTION_EDIT, url); intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId); intent.putExtra("DISABLE_EDITOR", mApnReadOnly); ........... context.startActivity(intent); ..........}
从上面的代码我们知道,不论是点击APN preference,还是点击新建APN按钮,我们都将进入到ApnEditor见面。
之所以确认是进入ApnEditor界面,主要是从Settings.apk的AndroidManifest.xml看出来的。
<activity android:name="ApnEditor" ....> ...... <intent-filter> ....... <action android:name="android.intent.action.EDIT" /> ...... <data android:mimeType = "vnd.android.cursor.item/telephony-carrier" /> </intent-filter> <intent-filter> <action android:name = "android.intent.action.INSERT" /> .......... <data android:mimeType = "vnd.android.cursor.dir/telephony-carrier" /> </intent-filter> </activity>
PART-II ApnEditor
public class ApnEditor extends Activity
可以看到ApnEditor是一个Activity。
我们首先来关注它的onCreate方法。
protected void onCreate(Bundle icicle) { ......... mName = getEditText(R.id.apn_name); mName.setInputType(InputType.TYPE_CLASS_TEXT); mApn = getEditText(R.id.apn_apn); ......... mDisableEditor = intent.getBooleanExtra( "DISABLE_EDITOR", false); if (mDisableEditor) { for (int i = 0; i < EDIT_TEST_IDS.length; i++) { getEditText( EDIT_TEST_IDS[i]).setEnabled(false); } for (int i = 0; i < LIST_IDS.length; i++) { findViewById(LIST_IDS[i]).setEnabled(false); } Log.d(TAG, "ApnEditor form is disabled."); } ............ if (action.equals(Intent.ACTION_EDIT)) { mUri = intent.getData(); } else if (action.equals(Intent.ACTION_INSERT)) { ......... mNewApn = true; ............... mCursor = getContentResolver().query(mUri, sProjection, null, null, null); .......... fillUi(intent.getStringExtra( ApnSettings.OPERATOR_NUMERIC_EXTRA)); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
private void fillUi(String defaultOperatorNumeric) { ......... String type = mCursor.getString(TYPE_INDEX); String numeric = mTelephonyManager. getIccOperatorNumericForData(mSubId); mName.setText(mCursor.getString(NAME_INDEX)); mApn.setText(mCursor.getString(APN_INDEX)); mProxy.setText(mCursor.getString(PROXY_INDEX)); mPort.setText(mCursor.getString(PORT_INDEX)); ...........}
从上面的代码,我们知道ApnEditor的初始化,就是决定自己能否进行实际的编辑工作,同时将数据库中的数据现实到UI界面上。
然后,如果该APN可编辑的话,用户就可以进行修改,然后点击保存了。
@Overridepublic boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case com.android.internal.R.id.home: if (mNewApn) { getContentResolver().delete(mUri, null, null); } finish(); return true; case MENU_SAVE: if (validateAndSave(false)) { finish(); } return true; } return super.onOptionsItemSelected(item);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
private boolean validateAndSave(boolean force) { //对编辑界面的信息做一些检查 String name = checkNotSet(mName.getText().toString()) String apn = checkNotSet(mApn.getText().toString()) String mcc = checkNotSet(mMcc.getText().toString()) String mnc = checkNotSet(mMnc.getText().toString()) ........... //满足要求后,存储UI数据 ContentValues values = new ContentValues() values.put(Telephony.Carriers.NAME, name.length() < 1 ? getResources().getString(R.string.untitled_apn) : name) values.put(Telephony.Carriers.APN, apn) values.put(Telephony.Carriers.PROXY, checkNotSet( mProxy.getText().toString())) ............ //将数据插入到数据库中 getContentResolver().update(mUri, values, null, null) return true}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
结束语
至此我们分析了与APN相关的主要流程,接下来APN的工作主要是用于数据业务了,以后用到再分析。
原文地址:http://blog.csdn.net/gaugamela/article/details/52238454