carrier_config服務(wù)研究

<h2 align = "center">carrier_config相關(guān)研究</h2>


概述: 在PhoneGlobals創(chuàng)建時(shí),會(huì)同時(shí)創(chuàng)建carrier_config 服務(wù),實(shí)現(xiàn)carrier_config 的類是CarrierConfigLoader。CarrierConfigLoader的主要目的是,綁定特定運(yùn)營商的App,從而獲取特定運(yùn)營的配置信息。

1. 相關(guān)類和aidl

  1. frameworks/base/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
  1. packages/services/Telephony/src/com/android/phone/CarrierConfigLoader.java
  1. frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java

2. 初始化

    private CarrierConfigLoader(Context context) {
        mContext = context;

        // 監(jiān)聽開機(jī)
        IntentFilter bootFilter = new IntentFilter();
        bootFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
        context.registerReceiver(mBootReceiver, bootFilter);

        // Register for package updates. Update app or uninstall app update will have all 3 intents,
        // in the order or removed, added, replaced, all with extra_replace set to true.
        // 監(jiān)聽安裝或者卸載
        IntentFilter pkgFilter = new IntentFilter();
        pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        pkgFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        pkgFilter.addDataScheme("package");
        context.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, pkgFilter, null, null);

        int numPhones = TelephonyManager.from(context).getPhoneCount();
        // 運(yùn)營商默認(rèn)配置
        mConfigFromDefaultApp = new PersistableBundle[numPhones];
        // 運(yùn)營商App設(shè)置
        mConfigFromCarrierApp = new PersistableBundle[numPhones];
        mServiceConnection = new CarrierServiceConnection[numPhones];
        // Make this service available through ServiceManager.
        // 讓外部可以通過ServiceManager訪問carrier_config
        ServiceManager.addService(Context.CARRIER_CONFIG_SERVICE, this);
        log("CarrierConfigLoader has started");
        mHandler.sendEmptyMessage(EVENT_CHECK_SYSTEM_UPDATE);
    }

在構(gòu)造函數(shù)初始化完成后,發(fā)送了EVENT_CHECK_SYSTEM_UPDATE事件,Handler對(duì)于該事件的處理如下:

case EVENT_CHECK_SYSTEM_UPDATE:
    SharedPreferences sharedPrefs =
            PreferenceManager.getDefaultSharedPreferences(mContext);
    final String lastFingerprint = sharedPrefs.getString(KEY_FINGERPRINT, null);
    // 如果系統(tǒng)的構(gòu)建信息發(fā)生了變化,即系統(tǒng)更新了,那么就需要清除緩存。
    if (!Build.FINGERPRINT.equals(lastFingerprint)) {
        log("Build fingerprint changed. old: "
                + lastFingerprint + " new: " + Build.FINGERPRINT);
        // 清除緩存
        clearCachedConfigForPackage(null);
        // 更新系統(tǒng)構(gòu)建信息
        sharedPrefs.edit().putString(KEY_FINGERPRINT, Build.FINGERPRINT).apply();
    }
    break;

清除緩存的代碼如下,運(yùn)營商的信息都是通過xml文件保存下來的,packageName為null時(shí)會(huì)清除所有的xml文件。

private boolean clearCachedConfigForPackage(final String packageName) {
    File dir = mContext.getFilesDir();
    File[] packageFiles = dir.listFiles(new FilenameFilter() {
        public boolean accept(File dir, String filename) {
            if (packageName != null) {
                return filename.startsWith("carrierconfig-" + packageName + "-");
            } else {
                // 因?yàn)閜ackageName==null,所以會(huì)清除掉所有以carrierconfig-開頭的文件。
                return filename.startsWith("carrierconfig-");
            }
        }
    });
    if (packageFiles == null || packageFiles.length < 1) return false;
    for (File f : packageFiles) {
        log("deleting " + f.getName());
        f.delete();
    }
    return true;
}

3. 開機(jī)更新運(yùn)營商信息

接收到開機(jī)廣播后mHandler接收到EVENT_SYSTEM_UNLOCKED事件,對(duì)該事件的處理如下:

case EVENT_SYSTEM_UNLOCKED:
                    for (int i = 0; i < TelephonyManager.from(mContext).getPhoneCount(); ++i) {
                        // 如果是單卡,phoneId為0, 雙卡:phoneId 為0,1
                        // 更新每個(gè)卡的運(yùn)營商信息
                        updateConfigForPhoneId(i);
                    }
                    break;

更新內(nèi)存中的運(yùn)營商信息,如果特定運(yùn)營商app不存在,那么清空對(duì)應(yīng)的緩存:

private void updateConfigForPhoneId(int phoneId) {
    // Clear in-memory cache for carrier app config, so when carrier app gets uninstalled, no
    // stale config is left.
    if (mConfigFromCarrierApp[phoneId] != null &&
            getCarrierPackageForPhoneId(phoneId) == null) {
        mConfigFromCarrierApp[phoneId] = null;
    }
    // 重新獲取運(yùn)營商信息
    mHandler.sendMessage(mHandler.obtainMessage(EVENT_FETCH_DEFAULT, phoneId, -1));
}

首先從本地文件中讀取配置信息,如果本地不能存在,那么嘗試綁定android.service.carrier.CarrierService,綁定成功后,從該服務(wù)的接口中讀取信息。

case EVENT_FETCH_DEFAULT:
                    iccid = getIccIdForPhoneId(phoneId);
                    // 從文件中讀取默認(rèn)的配置
                    config = restoreConfigFromXml(DEFAULT_CARRIER_CONFIG_PACKAGE, iccid);
                    if (config != null) {
                        log("Loaded config from XML. package=" + DEFAULT_CARRIER_CONFIG_PACKAGE
                                + " phoneId=" + phoneId);
                        // xml文件存在并加載成功,發(fā)送加載默認(rèn)配置成功事件
                        mConfigFromDefaultApp[phoneId] = config;
                        Message newMsg = obtainMessage(EVENT_LOADED_FROM_DEFAULT, phoneId, -1);
                        newMsg.getData().putBoolean("loaded_from_xml", true);
                        mHandler.sendMessage(newMsg);
                    } else {
                        // xml文件不存在,綁定android.service.carrier.CarrierService,該服務(wù)的實(shí)現(xiàn)不具體分析。
                        if (bindToConfigPackage(DEFAULT_CARRIER_CONFIG_PACKAGE,
                                phoneId, EVENT_CONNECTED_TO_DEFAULT)) {
                            sendMessageDelayed(obtainMessage(EVENT_BIND_DEFAULT_TIMEOUT, phoneId, -1),
                                    BIND_TIMEOUT_MILLIS);
                        } else {
                            // Send bcast if bind fails
                            // 告訴監(jiān)聽者,運(yùn)營商信息已經(jīng)發(fā)生了變化,這個(gè)時(shí)候,特定運(yùn)營商信息為空。
                            broadcastConfigChangedIntent(phoneId);
                        }
                    }
                    break;

上面EVENT_BIND_DEFAULT_TIMEOUT事件比較簡單, 首先unBind服務(wù),再通過broadcastConfigChangedIntent通知監(jiān)聽者。

所以應(yīng)該是在綁定服務(wù)成功后,就獲取運(yùn)營商信息,BIND_TIMEOUT_MILLIS是綁定服務(wù)的超時(shí)時(shí)間。下面看bindToConfigPackage:

/** Binds to the default or carrier config app. */
private boolean bindToConfigPackage(String pkgName, int phoneId, int eventId) {
    log("Binding to " + pkgName + " for phone " + phoneId);
    Intent carrierService = new Intent(CarrierService.CARRIER_SERVICE_INTERFACE);
    carrierService.setPackage(pkgName);
    // 很顯然綁定成功后的邏輯在CarrierServiceConnection類里面。
    mServiceConnection[phoneId] = new CarrierServiceConnection(phoneId, eventId);
    try {
        return mContext.bindService(carrierService, mServiceConnection[phoneId],
                Context.BIND_AUTO_CREATE);
    } catch (SecurityException ex) {
        return false;
    }
}

綁定服務(wù)成功后給mHandler發(fā)送了EVENT_CONNECTED_TO_DEFAULT事件:

case EVENT_CONNECTED_TO_DEFAULT:
    // 移除超時(shí)事件。
    removeMessages(EVENT_BIND_DEFAULT_TIMEOUT);
    carrierId = getCarrierIdForPhoneId(phoneId);
    conn = (CarrierServiceConnection) msg.obj;
    // If new service connection has been created, unbind.
    if (mServiceConnection[phoneId] != conn || conn.service == null) {
        mContext.unbindService(conn);
        break;
    }
    try {
        ICarrierService carrierService = ICarrierService.Stub
                .asInterface(conn.service);
        // 從運(yùn)營商服務(wù)中獲取到配置。
        config = carrierService.getCarrierConfig(carrierId);
        iccid = getIccIdForPhoneId(phoneId);
        // 保存配置到xml文件
        saveConfigToXml(DEFAULT_CARRIER_CONFIG_PACKAGE, iccid, config);
        // 更新內(nèi)存中的配置
        mConfigFromDefaultApp[phoneId] = config;
        sendMessage(obtainMessage(EVENT_LOADED_FROM_DEFAULT, phoneId, -1));
    } catch (Exception ex) {
        // The bound app could throw exceptions that binder will pass to us.
        loge("Failed to get carrier config: " + ex.toString());
    } finally {
        mContext.unbindService(mServiceConnection[phoneId]);
    }
    break;

無論是從本地xml文件中獲取到運(yùn)營商配置,還是從服務(wù)中獲取到運(yùn)營商配置,最終都會(huì)回到mHandler的EVENT_LOADED_FROM_DEFAULT事件:

case EVENT_LOADED_FROM_DEFAULT:
    // If we attempted to bind to the app, but the service connection is null, then
    // config was cleared while we were waiting and we should not continue.
    // 我們是從服務(wù)中獲取到的配置,但是服務(wù)卻斷開了,當(dāng)然不能繼續(xù)了。
    if (!msg.getData().getBoolean("loaded_from_xml", false)
            && mServiceConnection[phoneId] == null) {
        break;
    }
    carrierPackageName = getCarrierPackageForPhoneId(phoneId);
    // 特定的運(yùn)營商app存在,那么需要從特定的運(yùn)營商中再次獲取配置。
    if (carrierPackageName != null) {
        log("Found carrier config app: " + carrierPackageName);
        sendMessage(obtainMessage(EVENT_FETCH_CARRIER, phoneId));
    } else {
        broadcastConfigChangedIntent(phoneId);
    }
    break;

獲取特定運(yùn)營商配置信息和獲取默認(rèn)配置信息的邏輯是一樣的,代碼就不具體列舉了:

  1. 檢查本地緩存文件,緩存存在>>通知變更
  1. 緩存不存在,那么從特定運(yùn)營商的服務(wù)中獲取對(duì)應(yīng)的配置信息,然后緩存到本地,再通知變更。

4. 安裝卸載更新運(yùn)營商信息

理解了上面的更新一個(gè)SIM卡的運(yùn)營商信息的流程,下面就比較簡單了。安裝卸載會(huì)給mHandler發(fā)送EVENT_PACKAGE_CHANGED事件:

case EVENT_PACKAGE_CHANGED:
    carrierPackageName = (String) msg.obj;
    // Only update if there are cached config removed to avoid updating config
    // for unrelated packages.
    // 清除掉被卸載的運(yùn)營商app對(duì)應(yīng)的保存在本地的xml文件。
    if (clearCachedConfigForPackage(carrierPackageName)) {
        int numPhones = TelephonyManager.from(mContext).getPhoneCount();
        for (int i = 0; i < numPhones; ++i) {
            updateConfigForPhoneId(i);
        }
    }
    break;

上面的邏輯和開機(jī)更新運(yùn)營商信息差不多,再結(jié)合前面的分析,我們知道大體的流程如下:

  1. 默認(rèn)運(yùn)營商配置更新

1.1 檢查本地緩存文件, 緩存存在,不需要更新。

  1. 特定運(yùn)營商配置更新

2.1 檢查本地緩存文件,緩存不存在,需要從服務(wù)重新獲取配置信息。同時(shí)發(fā)現(xiàn)特定運(yùn)營商app不存在,清除特定運(yùn)營商配置緩存。

2.2 檢查特定運(yùn)營商app,不存在,通知更新。

5. 主動(dòng)更新

上面所說的都是carrier_config在接收到外部某個(gè)事件后,進(jìn)行更新的邏輯。

carrier_config同樣支持主動(dòng)更新。它為主動(dòng)更新提供了2個(gè)接口:

  1. notifyConfigChangedForSubId(int subId) 主要是提供給 telephony services 更新它當(dāng)前的運(yùn)營商配置用的。
  1. updateConfigForPhoneId(int phoneId, String simState) 根據(jù)提供的sim狀態(tài),決定清除該卡的運(yùn)營商配置還是加載該卡的運(yùn)營商配置。

6. 獲取運(yùn)營商配置的接口

僅僅是從內(nèi)存緩存中讀取配置信息,首先是讀取內(nèi)存預(yù)設(shè)好的配置,如果默認(rèn)運(yùn)營商配置存在,那么默認(rèn)配置會(huì)覆蓋預(yù)設(shè)配置,然后,如果特定運(yùn)營商配置存在,那么特定配置覆蓋默認(rèn)配置。

PersistableBundle getConfigForSubId(int subId) {
    try {
        mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, null);
        // SKIP checking run-time READ_PHONE_STATE since using PRIVILEGED
    } catch (SecurityException e) {
        mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, null);
    }
    int phoneId = SubscriptionManager.getPhoneId(subId);
    // 優(yōu)先級(jí)是 預(yù)設(shè)配置 < 默認(rèn)配置 < 特定配置
    PersistableBundle retConfig = CarrierConfigManager.getDefaultConfig();
    if (SubscriptionManager.isValidPhoneId(phoneId)) {
        PersistableBundle config = mConfigFromDefaultApp[phoneId];
        if (config != null)
            retConfig.putAll(config);
        config = mConfigFromCarrierApp[phoneId];
        if (config != null)
            retConfig.putAll(config);
    }
    return retConfig;
}

7. 服務(wù)的調(diào)用

1) 因?yàn)榉?wù)加入到了ServiceManager里面,所以對(duì)于系統(tǒng)應(yīng)用,可以通過ServiceManager.getService的方法獲取到該服務(wù)的接口。
IBinder carrierConfig = ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE);
ICarrierConfigLoader loader = ICarrierConfigLoader.Stub.asInterface(carrierConfig);
try {
    PersistableBundle defaultConfig = loader.getConfigForSubId(SubscriptionManager.getDefaultSubId());
} catch (RemoteException e) {
    e.printStackTrace();
}
2) 因?yàn)樵摲?wù)也注冊(cè)到系統(tǒng)服務(wù)里面了,所以第三方應(yīng)用可以獲取該服務(wù)接口。
CarrierConfigManager ccm = (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
PersistableBundle defaultConfig = ccm.getConfigForSubId(SubscriptionManager.getDefaultSubId());

總結(jié)

最后需要說明的是,carrier_config 服務(wù)自身有一套很完善的更新邏輯,調(diào)用者一般情況下不需要主動(dòng)去觸發(fā)更新。因?yàn)門elephony已經(jīng)管理好了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評(píng)論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評(píng)論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評(píng)論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評(píng)論 0 291
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評(píng)論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,836評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,808評(píng)論 25 708
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,925評(píng)論 6 342
  • 今天有點(diǎn)累。這段時(shí)間沒休息好,事情也做的雜,當(dāng)主業(yè)沒有占據(jù)大頭,副業(yè)就失去了樂趣,變成瑣碎的待解決麻煩。某天沒有運(yùn)...
    童顏姥姥閱讀 523評(píng)論 2 5