Android N TelephonyProvider及数据库初始化

作为 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的配置
[java]  view plain  copy
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  2.         package="com.android.providers.telephony"  
  3.         coreApp="true"  
  4.         android:sharedUserId="android.uid.phone">  
  5.     ...  
  6.     <application android:process="com.android.phone"  
  7.     ...  
  8.         <provider android:name="TelephonyProvider"  
  9.                   android:authorities="telephony"  
  10.                   android:exported="true"  
  11.                   android:singleUser="true"  
  12.                   android:multiprocess="false" />  
  13.      ....  

从这段代码,我们可以看出TelephonyProvider是运行在phone进程中的,其multiprocess的值为false,也就意味着若其它进程要访问TelephonyProvider,必须使用IPC机制进行调用。

之前分析 PhoneApp 的启动过程时,我们知道其 onCreate 函数是靠 ActivityThread.java 中,通过 handleBindApplication 函数调用。重新看下这个函数,不难发现在 onCreate 被调用前,先加载了 PhoneAPP 相关的 ContentProvider。
[java]  view plain  copy
  1. private void handleBindApplication(AppBindData data) {  
  2.     ........  
  3.     try {  
  4.         Application app = data.info.makeApplication(data.restrictedBackupMode, null);  
  5.         mInitialApplication = app;  
  6.    
  7.         if (!data.restrictedBackupMode) {  
  8.             if (!ArrayUtils.isEmpty(data.providers)) {  
  9.                 //加载 App 相关的 provider  
  10.                 installContentProviders(app, data.providers);  
  11.                 ...........  
  12.             }  
  13.         }  
  14.    
  15.         try {  
  16.             mInstrumentation.onCreate(data.instrumentationArgs);  
  17.         } catch (Exception e) {  
  18.             .........  
  19.         }  
  20.    
  21.         try {  
  22.             //调用 App 的 onCreate 函数  
  23.             mInstrumentation.callApplicationOnCreate(app);  
  24.         } catch (Exception e) {  
  25.             ..............          
  26.         }  
  27.     } finally {  
  28.         .............  
  29.     }  
  30. }  

此外,由于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
[java]  view plain  copy
  1. @Override  
  2. public boolean onCreate() {  
  3.     // 创建数据库  
  4.     mOpenHelper = new DatabaseHelper(getContext());  
  5.   
  6.     // Call getReadableDatabase() to make sure onUpgrade is called  
  7.     SQLiteDatabase db = mOpenHelper.getReadableDatabase();  
  8.   
  9.     // 版本更新时更新 APN 数据库  
  10.     String newBuildId = SystemProperties.get("ro.build.id"null);  
  11.     if (!TextUtils.isEmpty(newBuildId)) {  
  12.         // Check if build id has changed  
  13.         SharedPreferences sp = getContext().getSharedPreferences(BUILD_ID_FILE,  
  14.                 Context.MODE_PRIVATE);  
  15.         String oldBuildId = sp.getString(RO_BUILD_ID, "");  
  16.         if (!newBuildId.equals(oldBuildId)) {  
  17.             // Get rid of old preferred apn shared preferences  
  18.             SubscriptionManager sm = SubscriptionManager.from(getContext());  
  19.             if (sm != null) {  
  20.                 List<SubscriptionInfo> subInfoList = sm.getAllSubscriptionInfoList();  
  21.                 for (SubscriptionInfo subInfo : subInfoList) {  
  22.                     SharedPreferences spPrefFile = getContext().getSharedPreferences(  
  23.                             PREF_FILE_APN + subInfo.getSubscriptionId(), Context.MODE_PRIVATE);  
  24.                     if (spPrefFile != null) {  
  25.             //版本发生改变后,清除旧的记录信息  
  26.                         SharedPreferences.Editor editor = spPrefFile.edit();  
  27.                         editor.clear();  
  28.                         editor.apply();  
  29.                     }  
  30.                 }  
  31.             }  
  32.   
  33.             // 更新APN相关数据库  
  34.             updateApnDb();  
  35.         } else {  
  36.             if (VDBG) log("onCreate: build id did not change: " + oldBuildId);  
  37.         }  
  38.         sp.edit().putString(RO_BUILD_ID, newBuildId).apply();  
  39.     } else {  
  40.         if (VDBG) log("onCreate: newBuildId is empty");  
  41.     }  
  42.     return true;  
  43. }  


从上面的代码,我们知道TelephonyProvider初始化时的主要工作包括:1. 创建出数据库;2. 根据build_id的值,判断是否需要清楚旧有的存储信息,并更新数据库。
可以看出TelephonyProvider的主要工作,就是围绕数据库的操作展看的。

接着看下这里数据库的创建,TelephonyProvider 通过定义一个 SQLiteOpenHelper,即 DatabaseHelper来封装底层对数据的直接操作
[java]  view plain  copy
  1. private static class DatabaseHelper extends SQLiteOpenHelper {  
  2.     // Context to access resources with  
  3.     private Context mContext;  
  4.   
  5.     /** 
  6.      * DatabaseHelper helper class for loading apns into a database. 
  7.      * 
  8.      * @param context of the user. 
  9.      */  
  10.     public DatabaseHelper(Context context) {  
  11.         super(context, DATABASE_NAME, null, getVersion(context));  
  12.         mContext = context;  
  13.     }  
  14.   
  15.     @Override  
  16.     public void onCreate(SQLiteDatabase db) {  
  17.         // 创建 subInfo 信息对应的 table  
  18.         createSimInfoTable(db);  
  19.         // 创建运营商信息对应的 table  
  20.         createCarriersTable(db, CARRIERS_TABLE);  
  21.         // 初始化数据库  
  22.         initDatabase(db);  
  23.     }  
  24.   
  25. ....  
关于 subInfo 这个 table,我们在之前的文章 subInfo 添加与维护 中已经介绍,这里仅列下运营商信息 (APN)对应的 table,这里需要特别注意下创建时一些字段的默认值影响
[java]  view plain  copy
  1. private void createCarriersTable(SQLiteDatabase db, String tableName) {  
  2.     // Set up the database schema  
  3.     if (DBG) log("dbh.createCarriersTable: " + tableName);  
  4.     db.execSQL("CREATE TABLE " + tableName +  
  5.             "(_id INTEGER PRIMARY KEY," +  
  6.             NAME + " TEXT DEFAULT ''," +  
  7.             NUMERIC + " TEXT DEFAULT ''," +  
  8.             MCC + " TEXT DEFAULT ''," +  
  9.             MNC + " TEXT DEFAULT ''," +  
  10.             APN + " TEXT DEFAULT ''," +  
  11.             USER + " TEXT DEFAULT ''," +  
  12.             SERVER + " TEXT DEFAULT ''," +  
  13.             PASSWORD + " TEXT DEFAULT ''," +  
  14.             PROXY + " TEXT DEFAULT ''," +  
  15.             PORT + " TEXT DEFAULT ''," +  
  16.             MMSPROXY + " TEXT DEFAULT ''," +  
  17.             MMSPORT + " TEXT DEFAULT ''," +  
  18.             MMSC + " TEXT DEFAULT ''," +  
  19.             AUTH_TYPE + " INTEGER DEFAULT -1," +  
  20.             TYPE + " TEXT DEFAULT ''," +  
  21.             CURRENT + " INTEGER," +  
  22.             PROTOCOL + " TEXT DEFAULT 'IP'," +  
  23.             ROAMING_PROTOCOL + " TEXT DEFAULT 'IP'," +  
  24.             CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +  
  25.             BEARER + " INTEGER DEFAULT 0," +  
  26.             BEARER_BITMASK + " INTEGER DEFAULT 0," +  
  27.             MVNO_TYPE + " TEXT DEFAULT ''," +  
  28.             MVNO_MATCH_DATA + " TEXT DEFAULT ''," +  
  29.             SUBSCRIPTION_ID + " INTEGER DEFAULT "  
  30.             + SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," +  
  31.             PROFILE_ID + " INTEGER DEFAULT 0," +  
  32.             MODEM_COGNITIVE + " BOOLEAN DEFAULT 0," +  
  33.             MAX_CONNS + " INTEGER DEFAULT 0," +  
  34.             WAIT_TIME + " INTEGER DEFAULT 0," +  
  35.             MAX_CONNS_TIME + " INTEGER DEFAULT 0," +  
  36.             MTU + " INTEGER DEFAULT 0," +  
  37.             EDITED + " INTEGER DEFAULT " + UNEDITED + "," +  
  38.             USER_VISIBLE + " BOOLEAN DEFAULT 1," +  
  39.             READ_ONLY + " BOOLEAN DEFAULT 0," +  
  40.             PPP_NUMBER + " TEXT DEFAULT ''," +  
  41.             // Uniqueness collisions are used to trigger merge code so if a field is listed  
  42.             // here it means we will accept both (user edited + new apn_conf definition)  
  43.             // Columns not included in UNIQUE constraint: name, current, edited,  
  44.             // user, server, password, authtype, type, protocol, roaming_protocol, sub_id,  
  45.             // modem_cognitive, max_conns, wait_time, max_conns_time, mtu, bearer_bitmask,  
  46.             // user_visible  
  47.             "UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));");  
  48.     if (DBG) log("dbh.createCarriersTable:-");  
  49. }  

接下来我们再看看,initDatabase 初始化数据库的主要过程
[java]  view plain  copy
  1. /** 
  2.   *  This function adds APNs from xml file(s) to db. The db may or may not be empty to begin 
  3.   *  with. 
  4.   */  
  5.  private void initDatabase(SQLiteDatabase db) {  
  6.   
  7.      // Read internal APNS data  
  8.      Resources r = mContext.getResources();  
  9.  // 读取frameworks/base/core/res/res/xml/apns.xml文件  
  10.      XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);  
  11.      int publicversion = -1;  
  12.      try {  
  13.          XmlUtils.beginDocument(parser, "apns");  
  14.  //读取APN配置版本信息  
  15.          publicversion = Integer.parseInt(parser.getAttributeValue(null"version"));  
  16.   
  17.  //解析APN配置信息,并保存到数据表中  
  18.          loadApns(db, parser);  
  19.      } catch (Exception e) {  
  20.          loge("Got exception while loading APN database." + e);  
  21.      } finally {  
  22.          parser.close();  
  23.      }  
  24.   
  25.      // Read external APNS data (partner-provided)  
  26.      XmlPullParser confparser = null;  
  27.      //读取 system/etc/apns-conf.xml 文件    
  28.      File confFile = getApnConfFile();  
  29.   
  30.      FileReader confreader = null;  
  31.      try {  
  32.          confreader = new FileReader(confFile);  
  33.          confparser = Xml.newPullParser();  
  34.          confparser.setInput(confreader);  
  35.          XmlUtils.beginDocument(confparser, "apns");  
  36.   
  37.          // Sanity check. Force internal version and confidential versions to agree  
  38.  // 读取第三方提供的APN配置版本号  
  39.          int confversion = Integer.parseInt(confparser.getAttributeValue(null"version"));  
  40.  //判断第三方提供的APN配置版本号是否与Android自带的APN配置版本号相同    
  41.          if (publicversion != confversion) {  
  42.              log("initDatabase: throwing exception due to version mismatch");  
  43.              throw new IllegalStateException("Internal APNS file version doesn't match "  
  44.                      + confFile.getAbsolutePath());  
  45.          }  
  46.  // 如果版本号相同,解析APN配置信息,并保存到数据表中  
  47.          loadApns(db, confparser);  
  48.      } catch (FileNotFoundException e)  
  49.  ....  
  50.  }  

从上述代码分析,APN 数据的初始化简单来说就是:依次获取内外部 apns-config.xml文件,并解析得到其中定义的数据,最后保存到数据库中。具体解析和数据的保存主要在 loadApns 中完成
[java]  view plain  copy
  1. /* 
  2.  * Loads apns from xml file into the database 
  3.  */  
  4. private void loadApns(SQLiteDatabase db, XmlPullParser parser) {  
  5.     if (parser != null) {  
  6.         try {  
  7.             db.beginTransaction();  
  8.             XmlUtils.nextElement(parser);  
  9.             while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {  
  10.     // 获取 XML 中,每一个APN块的内容  
  11.                 ContentValues row = getRow(parser);  
  12.                 if (row == null) {  
  13.                     throw new XmlPullParserException("Expected 'apn' tag", parser, null);  
  14.                 }  
  15.     // 将获取的 APN 块信息,以键值对的形式,添加到数据库  
  16.                 insertAddingDefaults(db, row);  
  17.                 XmlUtils.nextElement(parser);  
  18.             }  
  19.             db.setTransactionSuccessful();  
  20.         } catch (XmlPullParserException e) {  
  21. ....  
  22.     }  
  23. }  

上面我们提到了 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配置信息展示




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用中提到的文件路径 "packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java" 是一个Java文件的路径。这个文件位于TelephonyProvider这个应用程序的源代码目录中。TelephonyProvider是一个提供与电话功能相关的服务和功能的Android系统应用程序。这个Java文件可能包含了TelephonyProvider应用程序的具体实现代码,用于实现电话服务的各种功能,比如拨号、呼叫记录、短信等。该文件的具体内容需要查看代码才能确定。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Android N TelephonyProvider数据库初始化](https://blog.csdn.net/weixin_39541189/article/details/118173124)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [【Adb shell】---玩转 Android系统 查询 应用包名 命令](https://blog.csdn.net/weixin_29994587/article/details/117484818)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值