作为 Phone 进程的核心 ContentProvider,TelephonyProvider 主要提供了 siminfo 和 apn 相关信息的数据库操作。
一. TelephonyProvider 开机加载
TelephonyProvider 继承自 ContentProvider,在分析 TelephonyProvider 的启动过程前,我们先看下 ContentProvider 是如何加载的。
1-ContentProvider 启动配置
我们在使用 ContentProvider 时,经常会在其对应的 AndroidManifest.xml 文件中,发现 android:sharedUserId 、android:process 及 android:multiprocess 标签,具体释义如下
android:sharedUserId —— 表明数据权限,因为默认情况下,Android给每个APK分配一个唯一的UserID,所以是默认禁止不同APK访问共享数据的。
若要共享数据,第一可以采用Share Preference方法,第二种就可以采用sharedUserId了,将不同APK的sharedUserId都设为一样,则这些APK之间就可以互相共享数据了。
android:process —— 应用程序运行的进程名,它的默认值为<manifest>元素里设置的包名,当然每个组件都可以通过设置该属性来覆盖默认值。
如果你想两个应用程序共用一个进程的话,你可以设置他们的 android:process 相同,但前提条件是他们共享一个用户ID及被赋予了相同证书
android:multiprocess —— 是否允许多进程,默认是false。简单理解就是是否允许在调用者的进程里实例化 provider,而跟定义它的进程没有关系。
通过上述释义,ContentProvider 的启动配置可总结为:
若不指定 process ,则其会在应用启动的时候加载,并在其默认主进程中初始化;若指定 process,则其不会随应用的启动而加载,只有在指定进程调用时才会加载,并在调用者的进程中初始化。
若 multiprocess 为 false,则 ContentProvider 只会有一个实例,并运行在启动的 Process 中,所有调用者共享该实例,调用者与ContentProvider实例可能位于两个不同的Process。
若 multiprocess 为 true,则 ContentProvider可以有多个实例,会由调用者在自己的进程空间实例化一个ContentProvider对象。
PS: 关于进程和 ContentProvider
1) 当 ContentProvider 属于非独有进程时,若该进程启动,则会搜索属于该进程的所有ContentProvider,并加载。
2) 当 ContentProvider 属于独有进程时,则只有需要用到该 ContentProvider 时,才会去加载。
当一个进程想要操作一个ContentProvider时,先需要获取该 ContentProvider 的对象,系统处理规则如下:
1) 如果该ContentProvider属于当前主叫进程,因为在进程启动时就已经加载过了,所以系统会直接返回该 ContentProvider的对象。
2) 如果该ContentProvider不属于当前主叫进程,那么系统会通过ActivityManagerService进行相关处理:
由于所有已加载的ContentProvider信息都已保存在AMS中,当需要获取某个ContentProvider的对象时,AMS会先判断该ContentProvider是否已被加载。
如果已被加载,若该ContentProvider设置了multiprocess的属性,且属于系统级 ContentProvider,那么就在当前主叫进程内部新生成该ContentProvider的对象;否则就需要通过IPC机制进行调用。
如果还未被加载,若该ContentProvider设置了multiprocess的属性,且属于系统级 ContentProvider,那么就在当前主叫进程内部新生成该ContentProvider的对象;否则就需要先创建该ContentProvider所在的进程,然后再通过IPC机制进行调用。
2-TelephonyProvider的加载
通过上述关于ContentProvider加载方式的介绍,我们看下TelephonyProvider对应AndroidManifest.xml的配置
从这段代码,我们可以看出TelephonyProvider是运行在phone进程中的,其multiprocess的值为false,也就意味着若其它进程要访问TelephonyProvider,必须使用IPC机制进行调用。
之前分析 PhoneApp 的启动过程时,我们知道其 onCreate 函数是靠 ActivityThread.java 中,通过 handleBindApplication 函数调用。重新看下这个函数,不难发现在 onCreate 被调用前,先加载了 PhoneAPP 相关的 ContentProvider。
此外,由于phone进程是开机就启动的,因此 TelephonyProvider 在开机的时候,就会被加载到AMS中。
二、siminfo 及 apn 数据库初始化
TelephonyProvider 处理的数据库定义名为 telephony.db,主要存储了 siminfo 及 apn 相关数据信息。 以le_x10为例,其在手机存储位置:
data/user_de/0/com.android.providers.telephony/databases/telephony.db
下面我们看下其初始化函数,这里主要工作包括:1、创建出数据库;2、 根据build_id的值,判断是否需要清楚旧有的存储信息,并更新数据库。
packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java
关于 subInfo 这个 table,我们在之前的文章 subInfo 添加与维护 中已经介绍,这里仅列下运营商信息 (APN)对应的 table,这里需要特别注意下创建时一些字段的默认值影响
接下来我们再看看,initDatabase 初始化数据库的主要过程
从上述代码分析,APN 数据的初始化简单来说就是:依次获取内外部 apns-config.xml文件,并解析得到其中定义的数据,最后保存到数据库中。具体解析和数据的保存主要在 loadApns 中完成
上面我们提到了 APN 的内外部配置文件,实际上查看源码发现该内部文件没任何配置信息:frameworks/base/core/res/res/xml/apns.xml。也就是说,开发中使用的 apns-conf.xml 主要来自外部定义。那么,外部定义的文件放在哪里、版本编译后其在设备中的位置又是哪里呢?在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中。
一. TelephonyProvider 开机加载
TelephonyProvider 继承自 ContentProvider,在分析 TelephonyProvider 的启动过程前,我们先看下 ContentProvider 是如何加载的。
1-ContentProvider 启动配置
我们在使用 ContentProvider 时,经常会在其对应的 AndroidManifest.xml 文件中,发现 android:sharedUserId 、android:process 及 android:multiprocess 标签,具体释义如下
android:sharedUserId —— 表明数据权限,因为默认情况下,Android给每个APK分配一个唯一的UserID,所以是默认禁止不同APK访问共享数据的。
若要共享数据,第一可以采用Share Preference方法,第二种就可以采用sharedUserId了,将不同APK的sharedUserId都设为一样,则这些APK之间就可以互相共享数据了。
android:process —— 应用程序运行的进程名,它的默认值为<manifest>元素里设置的包名,当然每个组件都可以通过设置该属性来覆盖默认值。
如果你想两个应用程序共用一个进程的话,你可以设置他们的 android:process 相同,但前提条件是他们共享一个用户ID及被赋予了相同证书
android:multiprocess —— 是否允许多进程,默认是false。简单理解就是是否允许在调用者的进程里实例化 provider,而跟定义它的进程没有关系。
通过上述释义,ContentProvider 的启动配置可总结为:
若不指定 process ,则其会在应用启动的时候加载,并在其默认主进程中初始化;若指定 process,则其不会随应用的启动而加载,只有在指定进程调用时才会加载,并在调用者的进程中初始化。
若 multiprocess 为 false,则 ContentProvider 只会有一个实例,并运行在启动的 Process 中,所有调用者共享该实例,调用者与ContentProvider实例可能位于两个不同的Process。
若 multiprocess 为 true,则 ContentProvider可以有多个实例,会由调用者在自己的进程空间实例化一个ContentProvider对象。
PS: 关于进程和 ContentProvider
1) 当 ContentProvider 属于非独有进程时,若该进程启动,则会搜索属于该进程的所有ContentProvider,并加载。
2) 当 ContentProvider 属于独有进程时,则只有需要用到该 ContentProvider 时,才会去加载。
当一个进程想要操作一个ContentProvider时,先需要获取该 ContentProvider 的对象,系统处理规则如下:
1) 如果该ContentProvider属于当前主叫进程,因为在进程启动时就已经加载过了,所以系统会直接返回该 ContentProvider的对象。
2) 如果该ContentProvider不属于当前主叫进程,那么系统会通过ActivityManagerService进行相关处理:
由于所有已加载的ContentProvider信息都已保存在AMS中,当需要获取某个ContentProvider的对象时,AMS会先判断该ContentProvider是否已被加载。
如果已被加载,若该ContentProvider设置了multiprocess的属性,且属于系统级 ContentProvider,那么就在当前主叫进程内部新生成该ContentProvider的对象;否则就需要通过IPC机制进行调用。
如果还未被加载,若该ContentProvider设置了multiprocess的属性,且属于系统级 ContentProvider,那么就在当前主叫进程内部新生成该ContentProvider的对象;否则就需要先创建该ContentProvider所在的进程,然后再通过IPC机制进行调用。
2-TelephonyProvider的加载
通过上述关于ContentProvider加载方式的介绍,我们看下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"
- ...
- <provider android:name="TelephonyProvider"
- android:authorities="telephony"
- android:exported="true"
- android:singleUser="true"
- android:multiprocess="false" />
- ....
从这段代码,我们可以看出TelephonyProvider是运行在phone进程中的,其multiprocess的值为false,也就意味着若其它进程要访问TelephonyProvider,必须使用IPC机制进行调用。
之前分析 PhoneApp 的启动过程时,我们知道其 onCreate 函数是靠 ActivityThread.java 中,通过 handleBindApplication 函数调用。重新看下这个函数,不难发现在 onCreate 被调用前,先加载了 PhoneAPP 相关的 ContentProvider。
- private void handleBindApplication(AppBindData data) {
- ........
- try {
- Application app = data.info.makeApplication(data.restrictedBackupMode, null);
- mInitialApplication = app;
- if (!data.restrictedBackupMode) {
- if (!ArrayUtils.isEmpty(data.providers)) {
- //加载 App 相关的 provider
- installContentProviders(app, data.providers);
- ...........
- }
- }
- try {
- mInstrumentation.onCreate(data.instrumentationArgs);
- } catch (Exception e) {
- .........
- }
- try {
- //调用 App 的 onCreate 函数
- mInstrumentation.callApplicationOnCreate(app);
- } catch (Exception e) {
- ..............
- }
- } finally {
- .............
- }
- }
此外,由于phone进程是开机就启动的,因此 TelephonyProvider 在开机的时候,就会被加载到AMS中。
二、siminfo 及 apn 数据库初始化
TelephonyProvider 处理的数据库定义名为 telephony.db,主要存储了 siminfo 及 apn 相关数据信息。 以le_x10为例,其在手机存储位置:
data/user_de/0/com.android.providers.telephony/databases/telephony.db
下面我们看下其初始化函数,这里主要工作包括:1、创建出数据库;2、 根据build_id的值,判断是否需要清楚旧有的存储信息,并更新数据库。
packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java
- @Override
- public boolean onCreate() {
- // 创建数据库
- mOpenHelper = new DatabaseHelper(getContext());
- // Call getReadableDatabase() to make sure onUpgrade is called
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- // 版本更新时更新 APN 数据库
- String newBuildId = SystemProperties.get("ro.build.id", null);
- if (!TextUtils.isEmpty(newBuildId)) {
- // Check if build id has changed
- SharedPreferences sp = getContext().getSharedPreferences(BUILD_ID_FILE,
- Context.MODE_PRIVATE);
- String oldBuildId = sp.getString(RO_BUILD_ID, "");
- if (!newBuildId.equals(oldBuildId)) {
- // Get rid of old preferred apn shared preferences
- SubscriptionManager sm = SubscriptionManager.from(getContext());
- if (sm != null) {
- List<SubscriptionInfo> subInfoList = sm.getAllSubscriptionInfoList();
- for (SubscriptionInfo subInfo : subInfoList) {
- SharedPreferences spPrefFile = getContext().getSharedPreferences(
- PREF_FILE_APN + subInfo.getSubscriptionId(), Context.MODE_PRIVATE);
- if (spPrefFile != null) {
- //版本发生改变后,清除旧的记录信息
- SharedPreferences.Editor editor = spPrefFile.edit();
- editor.clear();
- editor.apply();
- }
- }
- }
- // 更新APN相关数据库
- 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");
- }
- return true;
- }
从上面的代码,我们知道TelephonyProvider初始化时的主要工作包括:1. 创建出数据库;2. 根据build_id的值,判断是否需要清楚旧有的存储信息,并更新数据库。
可以看出TelephonyProvider的主要工作,就是围绕数据库的操作展看的。
- private static class DatabaseHelper extends SQLiteOpenHelper {
- // Context to access resources with
- private Context mContext;
- /**
- * DatabaseHelper helper class for loading apns into a database.
- *
- * @param context of the user.
- */
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, getVersion(context));
- mContext = context;
- }
- @Override
- public void onCreate(SQLiteDatabase db) {
- // 创建 subInfo 信息对应的 table
- createSimInfoTable(db);
- // 创建运营商信息对应的 table
- createCarriersTable(db, CARRIERS_TABLE);
- // 初始化数据库
- initDatabase(db);
- }
- ....
- private void createCarriersTable(SQLiteDatabase db, String tableName) {
- // Set up the database schema
- if (DBG) log("dbh.createCarriersTable: " + tableName);
- db.execSQL("CREATE TABLE " + tableName +
- "(_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," +
- PROTOCOL + " TEXT DEFAULT 'IP'," +
- ROAMING_PROTOCOL + " TEXT DEFAULT 'IP'," +
- CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +
- BEARER + " INTEGER DEFAULT 0," +
- BEARER_BITMASK + " INTEGER DEFAULT 0," +
- MVNO_TYPE + " TEXT DEFAULT ''," +
- MVNO_MATCH_DATA + " TEXT DEFAULT ''," +
- 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 + "," +
- USER_VISIBLE + " BOOLEAN DEFAULT 1," +
- READ_ONLY + " BOOLEAN DEFAULT 0," +
- PPP_NUMBER + " TEXT DEFAULT ''," +
- // 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) + "));");
- if (DBG) log("dbh.createCarriersTable:-");
- }
接下来我们再看看,initDatabase 初始化数据库的主要过程
- /**
- * 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) {
- // Read internal APNS data
- Resources r = mContext.getResources();
- // 读取frameworks/base/core/res/res/xml/apns.xml文件
- XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
- int publicversion = -1;
- try {
- XmlUtils.beginDocument(parser, "apns");
- //读取APN配置版本信息
- publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
- //解析APN配置信息,并保存到数据表中
- 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;
- //读取 system/etc/apns-conf.xml 文件
- File confFile = getApnConfFile();
- FileReader confreader = null;
- 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
- // 读取第三方提供的APN配置版本号
- int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
- //判断第三方提供的APN配置版本号是否与Android自带的APN配置版本号相同
- if (publicversion != confversion) {
- log("initDatabase: throwing exception due to version mismatch");
- throw new IllegalStateException("Internal APNS file version doesn't match "
- + confFile.getAbsolutePath());
- }
- // 如果版本号相同,解析APN配置信息,并保存到数据表中
- loadApns(db, confparser);
- } catch (FileNotFoundException e)
- ....
- }
从上述代码分析,APN 数据的初始化简单来说就是:依次获取内外部 apns-config.xml文件,并解析得到其中定义的数据,最后保存到数据库中。具体解析和数据的保存主要在 loadApns 中完成
- /*
- * Loads apns from xml file into the database
- */
- private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
- if (parser != null) {
- try {
- db.beginTransaction();
- XmlUtils.nextElement(parser);
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- // 获取 XML 中,每一个APN块的内容
- ContentValues row = getRow(parser);
- if (row == null) {
- throw new XmlPullParserException("Expected 'apn' tag", parser, null);
- }
- // 将获取的 APN 块信息,以键值对的形式,添加到数据库
- insertAddingDefaults(db, row);
- XmlUtils.nextElement(parser);
- }
- db.setTransactionSuccessful();
- } catch (XmlPullParserException e) {
- ....
- }
- }
上面我们提到了 APN 的内外部配置文件,实际上查看源码发现该内部文件没任何配置信息:frameworks/base/core/res/res/xml/apns.xml。也就是说,开发中使用的 apns-conf.xml 主要来自外部定义。那么,外部定义的文件放在哪里、版本编译后其在设备中的位置又是哪里呢?在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 配置文件。
PS: Android通过telephony.db数据库中的 carriers表来保存所有的APN配置信息展示