Android N TelephonyProvider及数据库初始化
来源:互联网 发布:php文件上传目录 编辑:程序博客网 时间:2024/06/15 21:48
作为 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阅读全文
0 0
- Android N TelephonyProvider及数据库初始化
- Android 7.0后SettingProvider ContactsProvider TelephonyProvider MediaProvider数据库位置
- Android 7.0后SettingProvider ContactsProvider TelephonyProvider MediaProvider数据库位置
- Android 7.0后SettingProvider ContactsProvider TelephonyProvider MediaProvider数据库位置
- Android 单模改双模。telephonyProvider修改成问题
- ContentProvider分析(一)之TelephonyProvider的初始化
- Android 初始化本地数据库
- Android sqlite 初始化数据库
- Android初始化本地数据库
- Android初始化本地数据库
- N年不开张了,写一个关于Android中用文件初始化sqlite 数据库的文章
- 数据库建表及初始化
- TelephonyProvider 分析
- Android N PhoneAPP 启动关系类初始化
- Calabash Android 安装及初始化
- DbAdapter 创建数据库及初始化 范例
- mysql数据库的初始化及相关配置
- mysql数据库的初始化及相关配置
- Struts2的设计模式,请求流程
- Effective Java 学习笔记——第六章(未完待续)
- html中引入调用另一个html的方法
- DOM节点的创建、插入、删除、查找、替换
- C#中实现label文字循环滚动
- Android N TelephonyProvider及数据库初始化
- AICamera of Caffe2
- vue基础语法
- PHP调试-----打印信息
- 为什么-8补码是1000(关于补码的说明)
- Struts2的拦截器和过滤器的区别
- 关于获取上一月的第一天及最后一天
- python实现数据结构之队列
- 自己生成网络后台接口并利用charles模拟Http请求和响应