Android 6.0 狀態欄信號圖標分析

先來一張狀態欄的分區圖。今天要分析的是信號顯示這一小塊,就是圖中的signal_cluster,對應源碼中的View就是SignalClusterView。

這裏寫圖片描述

這是一個自定義View,我們看一下他的定義:

public class SignalClusterView
        extends LinearLayout
        implements NetworkControllerImpl.SignalCallback,
        SecurityController.SecurityControllerCallback, Tunable {}

繼承了線性佈局,實現了三個接口。從接口的名稱就知道我們關心的東東肯定在NetworkControllerImpl.SignalCallback裏面,(由此可見易懂的名稱的重要性!)看看它裏面有哪些內容:

public interface SignalCallback {
        void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
                boolean activityIn, boolean activityOut, String description);

        void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
                int qsType, boolean activityIn, boolean activityOut, int dataActivityId,
                int mobileActivityId, int stackedDataIcon, int stackedVoiceIcon,
                String typeContentDescription, String description,
                boolean isWide, int subId);
        void setSubs(List<SubscriptionInfo> subs);
        void setNoSims(boolean show);

        void setEthernetIndicators(IconState icon);

        void setIsAirplaneMode(IconState icon);

        void setMobileDataEnabled(boolean enabled);
}

函數名都很直觀,就不翻譯了,具體的實現後續用到的時候再來分析吧。

SignalClusterView的關於網絡信號相關的更新肯定就是依賴於上面列舉的幾個接口的回調了。

那又是誰在什麼情況下會調用這些接口中的回調函數呢?從接口名NetworkControllerImpl.SignalCallback知道應該是NetworkControllerImpl這個類(如果不是這種 類。接口 的形式,就直接全局搜函數名吧)。繼續來看這個類的定義:

public class NetworkControllerImpl extends BroadcastReceiver
        implements NetworkController, DemoMode {}

看名稱NetworkControllerImpl就純粹是NetworkController的實現類,但它繼承了廣播接收器,那我們就要看看它到底處理哪些廣播:

IntentFilter filter = new IntentFilter();
        filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
        filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
        filter.addAction(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
        filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
        filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);

看看,都是些網絡狀態相關的廣播事件。

看到這裏,大概就知道狀態欄信號圖標的刷新原理了:

NetworkControllerImpl接收到網絡狀態廣播,通過SignalCallback接口來刷新SignalClusterView。

當然這只是最大概的總結,中間肯定很多的彎彎繞繞,下面我們就順着流程來詳細走一遍吧。

因爲NetworkControllerImpl中處理的廣播事件比較多,我們取一個作爲例子,就選
TelephonyIntents.ACTION_SIM_STATE_CHANGED這個Action吧,既然是讀源碼,那就看看源碼對這個Action的解釋吧:

/**
     * Broadcast Action: The sim card state has changed.
     * The intent will have the following extra values:</p>
     * <dl>
     *   <dt>phoneName</dt><dd>A string version of the phone name.</dd>
     *   <dt>ss</dt><dd>The sim state. One of:
     *     <dl>
     *       <dt>{@code ABSENT}</dt><dd>SIM card not found</dd>
     *       <dt>{@code LOCKED}</dt><dd>SIM card locked (see {@code reason})</dd>
     *       <dt>{@code READY}</dt><dd>SIM card ready</dd>
     *       <dt>{@code IMSI}</dt><dd>FIXME: what is this state?</dd>
     *       <dt>{@code LOADED}</dt><dd>SIM card data loaded</dd>
     *     </dl></dd>
     *   <dt>reason</dt><dd>The reason why ss is {@code LOCKED}; null otherwise.</dd>
     *   <dl>
     *       <dt>{@code PIN}</dt><dd>locked on PIN1</dd>
     *       <dt>{@code PUK}</dt><dd>locked on PUK1</dd>
     *       <dt>{@code NETWORK}</dt><dd>locked on network personalization</dd>
     *   </dl>
     * </dl>

我們注意到IMS這個,註釋竟然是what is this state?哈哈,Google也會這麼註釋。

源碼中接收到這個廣播,直接調用了updateMobileController()來處理,簡單判斷是否有listener監聽後,繼續轉給doUpdateMobileControllers()來處理:

@VisibleForTesting
    void doUpdateMobileControllers() {
        List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
        if (subscriptions == null) {
            subscriptions = Collections.emptyList();
        }
        // If there have been no relevant changes to any of the subscriptions, we can leave as is.
        if (hasCorrectMobileControllers(subscriptions)) {
            // Even if the controllers are correct, make sure we have the right no sims state.
            // Such as on boot, do not need any controllers, because there are no sims,
            // but we still need to update the no sim state.
            updateNoSims();
            return;
        }
        setCurrentSubscriptions(subscriptions);
        updateNoSims();
        recalculateEmergency();
    }

因爲自Android 5.0後就支持雙卡了。這裏先通過SubscriptionManager獲取當前活動的SIM卡信息。最下的三個方法分別更新對應卡的狀態、無卡狀態、緊急呼叫狀態。緊急呼叫在狀態欄圖標上沒什麼體現,我們這裏就不看了。先看看較簡單的updateNoSims:

@VisibleForTesting
    protected void updateNoSims() {
        boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
        if (hasNoSims != mHasNoSims) {
            mHasNoSims = hasNoSims;
            mCallbackHandler.setNoSims(mHasNoSims);
        }
    }

就是更新一下mHasNoSims變量,然後通過handler來更新狀態,如下:

case MSG_NO_SIM_VISIBLE_CHANGED:
                for (SignalCallback signalCluster : mSignalCallbacks) {
                    signalCluster.setNoSims(msg.arg1 != 0);
                }
                break;

這裏就看得到使用到了SignalCallback接口,上文中我們介紹過,是SignalClusterView實現了這個接口:

@Override
    public void setNoSims(boolean show) {
        mNoSimsVisible = show && !mBlockMobile;
        apply();
    }

很簡單的,更新變量,刷新。apply方法沒什麼好分析的,就是把SignalClusterView中包含的View全部依據最新狀態更新一遍。
這就是updateNoSims()的流程了。我們再看看重頭setCurrentSubscriptions方法:

@VisibleForTesting
    void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
        Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
            @Override
            public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) {
                return lhs.getSimSlotIndex() == rhs.getSimSlotIndex()
                        ? lhs.getSubscriptionId() - rhs.getSubscriptionId()
                        : lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
            }
        });
        mCurrentSubscriptions = subscriptions;

        HashMap<Integer, MobileSignalController> cachedControllers =
                new HashMap<Integer, MobileSignalController>(mMobileSignalControllers);
        mMobileSignalControllers.clear();
        final int num = subscriptions.size();
        for (int i = 0; i < num; i++) {
            int subId = subscriptions.get(i).getSubscriptionId();
            // If we have a copy of this controller already reuse it, otherwise make a new one.
            if (cachedControllers.containsKey(subId)) {
                mMobileSignalControllers.put(subId, cachedControllers.remove(subId));
            } else {
                MobileSignalController controller = new MobileSignalController(mContext, mConfig,
                        mHasMobileDataFeature, mPhone, mCallbackHandler,
                        this, subscriptions.get(i), mSubDefaults, mReceiverHandler.getLooper());
                mMobileSignalControllers.put(subId, controller);
                if (subscriptions.get(i).getSimSlotIndex() == 0) {
                    mDefaultSignalController = controller;
                }
                if (mListening) {
                    controller.registerListener();
                }
            }
        }
        if (mListening) {
            for (Integer key : cachedControllers.keySet()) {
                if (cachedControllers.get(key) == mDefaultSignalController) {
                    mDefaultSignalController = null;
                }
                cachedControllers.get(key).unregisterListener();
            }
        }
        mCallbackHandler.setSubs(subscriptions);
        notifyAllListeners();

        // There may be new MobileSignalControllers around, make sure they get the current
        // inet condition and airplane mode.
        pushConnectivityToSignals();
        updateAirplaneMode(true /* force */);
    }

這個函數前面大段都是依據傳入的SubscriptionInfo來更新MobileSignalController。在末尾處連續4句:setSubs的處理方式和setNoSims如出一轍,最終是回調到SignalClusterView,主要是做了清理舊數據的工作;然後notifyAllListener

/**
     * Forces update of all callbacks on both SignalClusters and
     * NetworkSignalChangedCallbacks.
     */
    private void notifyAllListeners() {
        notifyListeners();
        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
            mobileSignalController.notifyListeners();
        }
        mWifiSignalController.notifyListeners();
        mEthernetSignalController.notifyListeners();
}

這列各種notify,最終都是會回調到SignalClusterView的,可見一個SignalClusterView同時被多少個controller管理。這裏按照功能劃分多個controller,很清晰易讀。

我們關注的是信號圖標的更新,繼續查看mobileSignalController的notify:

@Override
    public void notifyListeners() {
        if (mConfig.readIconsFromXml) {
            generateIconGroup();
        }
        MobileIconGroup icons = getIcons();

        String contentDescription = getStringIfExists(getContentDescription());
        String dataContentDescription = getStringIfExists(icons.mDataContentDescription);

        // Show icon in QS when we are connected or need to show roaming.
        boolean showDataIcon = mCurrentState.dataConnected
                || mCurrentState.iconGroup == TelephonyIcons.ROAMING;

        //這裏做過定製,大家關心細節可以自行查看原始代碼
        int SIMEnabled = 1;
        if(getSimSlotIndex() == 0)
            SIMEnabled = Settings.System.getInt(mContext.getContentResolver(), Settings.System.SIM1_ENABLE, 1);
        else if(getSimSlotIndex() == 1)
            SIMEnabled = Settings.System.getInt(mContext.getContentResolver(), Settings.System.SIM2_ENABLE, 1);
        IconState statusIcon;
        //IconState構造函數第一個參數決定了信號圖標是否顯示。
        if(SIMEnabled == 0 && !mCurrentState.airplaneMode) {
            statusIcon = new IconState(true, R.drawable.stat_sys_signal_null_1, contentDescription);
        } else {
            statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,
                    getCurrentIconId(), contentDescription);
        }

        int qsTypeIcon = 0;
        IconState qsIcon = null;
        String description = null;
        // Only send data sim callbacks to QS.
        if (mCurrentState.dataSim) {
            qsTypeIcon = showDataIcon ? icons.mQsDataType : 0;
            qsIcon = new IconState(mCurrentState.enabled
                    && !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
            description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
        }
        boolean activityIn = mCurrentState.dataConnected
                        && !mCurrentState.carrierNetworkChangeMode
                        && mCurrentState.activityIn;
        boolean activityOut = mCurrentState.dataConnected
                        && !mCurrentState.carrierNetworkChangeMode
                        && mCurrentState.activityOut;
        showDataIcon &= mCurrentState.isDefault
                || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
        showDataIcon &= mStyle == STATUS_BAR_STYLE_ANDROID_DEFAULT;
        int typeIcon = showDataIcon ? icons.mDataType : 0;
        int dataActivityId = showMobileActivity() ? 0 : icons.mActivityId;
        int mobileActivityId = showMobileActivity() ? icons.mActivityId : 0;
        mCallbackHandler.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
                activityIn, activityOut, dataActivityId, mobileActivityId,
                icons.mStackedDataIcon, icons.mStackedVoiceIcon,
                dataContentDescription, description, icons.mIsWide,
                mSubscriptionInfo.getSubscriptionId());
    }

嗯,看着一大坨。還是依據變量名我們很快就能猜個大概了:最終的mCallbackHandler.setMobileDataIndicators的後續邏輯也是和setNoSims一樣,這裏帶了炒雞多的參數,都是和狀態欄信號圖標的顯示效果有關的,上面一大坨就是在獲取狀態。從這裏出去很快就到SignalClusterView了。在那邊就依據這些狀態刷新就好了:

@Override
    public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
            int qsType, boolean activityIn, boolean activityOut, int dataActivityId,
            int mobileActivityId, int stackedDataId, int stackedVoiceId,
            String typeContentDescription, String description, boolean isWide, int subId) {
        PhoneState state = getState(subId);
        if (state == null) {
            return;
        }
        state.mMobileVisible = statusIcon.visible && !mBlockMobile;
        state.mMobileStrengthId = statusIcon.icon;
        state.mMobileTypeId = statusType;
        state.mMobileDescription = statusIcon.contentDescription;
        state.mMobileTypeDescription = typeContentDescription;
        state.mIsMobileTypeIconWide = statusType != 0 && isWide;
        state.mDataActivityId = dataActivityId;
        state.mMobileActivityId = mobileActivityId;
        state.mStackedDataId = stackedDataId;
        state.mStackedVoiceId = stackedVoiceId;

        apply();
    }

和setNoSims邏輯也是一樣的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章