作为 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
接下来我们再看看,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);
}
....
关于 subInfo 这个 table,我们在之前的文章 subInfo 添加与维护 中已经介绍,这里仅列下运营商信息 (APN)对应的 table,这里需要特别注意下创建时一些字段的默认值影响
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配置信息展示
参考文档:http://blog.csdn.net/gaugamela/article/details/52238454
http://blog.csdn.net/yangwen123/article/details/1052687