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的配置
<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的主要工作,就是围绕数据库的操作展看的。

接着看下这里数据库的创建,TelephonyProvider 通过定义一个 SQLiteOpenHelper,即 DatabaseHelper来封装底层对数据的直接操作
    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