1. 程式人生 > >SystemUI之狀態列status icon載入流程

SystemUI之狀態列status icon載入流程

引言

今天我們主要講的是SystemUI狀態列裡面常見的一排icons——status icons,該icons主要用於顯示Bluetooth、Location、Headset等等系統狀態的icon,表示當前手機藍芽、定位、耳機等的功能開啟了,以達到提示使用者的目的。

正文

本文主要從兩個方面講述下status icon功能,主要分為初始化流程和狀態顯示流程
話不多說,我們開始吧。

初始化流程

首先我們看下狀態列的佈局檔案 status_bar.xml

<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            >

            <include layout="@layout/system_icons" />

            <com.android.systemui.statusbar.policy.Clock
                android:id="@+id/clock"
                android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:singleLine="true"
                android:paddingStart="@dimen/status_bar_clock_starting_padding"
                android:paddingEnd="@dimen/status_bar_clock_end_padding"
                android:gravity="center_vertical|start"
                />
        </com.android.keyguard.AlphaOptimizedLinearLayout>

其中我們今天講的status_icons就藏身於 system_icons.xml 之中

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/system_icons"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:gravity="center_vertical">

    <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/statusIcons"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:orientation="horizontal"/>

    <include layout="@layout/signal_cluster_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/signal_cluster_margin_start"/>

    <com.android.systemui.BatteryMeterView android:id="@+id/battery"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        />
</LinearLayout>

android:id="@+id/statusIcons"出現了,人如其名,它其實是一個自定義的LinearLayout——AlphaOptimizedLinearLayout,這個Viewt也是非常簡單的,只是實現瞭如下方法

    @Override
    public boolean hasOverlappingRendering() {
        return false;
    }

該方法用來標記當前view是否存在過度繪製,存在返回ture,不存在返回false,而api裡面預設返回為true,誠然我們status icon是個好同學,不存在過度繪製。

接下來我們看下SystemUI是怎麼載入這個AlphaOptimizedLinearLayout

   @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mStatusBar = (PhoneStatusBarView) view;
        if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
            mStatusBar.go(savedInstanceState.getInt(EXTRA_PANEL_STATE));
        }
        mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
        Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
        mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
        mSignalClusterView = mStatusBar.findViewById(R.id.signal_cluster);
        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
        // Default to showing until we know otherwise.
        showSystemIconArea(false);
        initEmergencyCryptkeeperText();
    }

在 CollapsedStatusBarFragment.java 中我們見到了初始化,這個類相信有狀態列開發經驗的都很熟悉了,與本文相關的程式碼如下
mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
我們接著看這個DarkIconManager初始化裡做了什麼

public DarkIconManager(LinearLayout linearLayout) {
            super(linearLayout);
            mIconHPadding = mContext.getResources().getDimensionPixelSize(
                    R.dimen.status_bar_icon_padding);
            mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
        }

public static class IconManager {
        protected final ViewGroup mGroup;
        protected final Context mContext;
        protected final int mIconSize;

        public IconManager(ViewGroup group) {
            mGroup = group;
            mContext = group.getContext();
            mIconSize = mContext.getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.status_bar_icon_size);
        }

        protected void onIconAdded(int index, String slot, boolean blocked,
                StatusBarIcon icon) {
            addIcon(index, slot, blocked, icon);
        }

        protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
                StatusBarIcon icon) {
            StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
            view.set(icon);
            mGroup.addView(view, index, onCreateLayoutParams());
            return view;
        } 

        public void onSetIcon(int viewIndex, StatusBarIcon icon) {
            StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
            view.set(icon);
        }

    }

在StatusBarIconController.java中可以看到,在onIconAdded--->addIcon兩個函式裡面實現了AlphaOptimizedLinearLayout子view即icon的增加,onSetIcon可能就是重新整理icon狀態的,請記住這兩張面孔

onIconAdded(int index, String slot, boolean blocked,StatusBarIcon icon)
onSetIcon(int viewIndex, StatusBarIcon icon)

到這裡我們就知道了icon新增的流程。
onIconAdded(int index, String slot, boolean blocked,StatusBarIcon icon)
而且在這個函式裡面有引數index和slot,我們應該可以猜到,每個icon應該就是對應著代表順序的index和資料型別為String的slot,那麼到底是不是這樣呢?index和slot又是從哪裡來的呢?我們接著往下看。

在StatusBarIconControllerImpl.java建構函式裡我們找到了實現

public StatusBarIconControllerImpl(Context context) {
        super(context.getResources().getStringArray(
                com.android.internal.R.array.config_statusBarIcons));

StatusBarIconControllerImpl的父類是StatusBarIconList

    protected ArrayList<String> mSlots = new ArrayList<>();
    protected ArrayList<StatusBarIcon> mIcons = new ArrayList<>();

    public StatusBarIconList(String[] slots) {
        final int N = slots.length;
        for (int i=0; i < N; i++) {
            mSlots.add(slots[i]);
            mIcons.add(null);
        }
    }

在這裡我們就找到了index和slot的出處,原來在初始化的時候就已經定義好了所有的slots,然後從framework中加載出來,index就是string-array中的順序。

<string-array name="config_statusBarIcons">
        <item><xliff:g id="id">@string/status_bar_rotate</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_headset</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_data_saver</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_ime</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_sync_failing</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_sync_active</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_location</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_nfc</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_tty</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_speakerphone</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_zen</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_mute</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_volume</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_cdma_eri</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_data_connection</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_clock</xliff:g></item>
    </string-array>

    <string translatable="false" name="status_bar_rotate">rotate</string>
    <string translatable="false" name="status_bar_headset">headset</string>
    <string translatable="false" name="status_bar_data_saver">data_saver</string>
    <string translatable="false" name="status_bar_managed_profile">managed_profile</string>
    <string translatable="false" name="status_bar_ime">ime</string>
    <string translatable="false" name="status_bar_sync_failing">sync_failing</string>
    <string translatable="false" name="status_bar_sync_active">sync_active</string>
    <string translatable="false" name="status_bar_cast">cast</string>
    <string translatable="false" name="status_bar_hotspot">hotspot</string>
    <string translatable="false" name="status_bar_location">location</string>
    <string translatable="false" name="status_bar_bluetooth">bluetooth</string>
    <string translatable="false" name="status_bar_nfc">nfc</string>
    <string translatable="false" name="status_bar_tty">tty</string>
    <string translatable="false" name="status_bar_speakerphone">speakerphone</string>
    <string translatable="false" name="status_bar_zen">zen</string>
    <string translatable="false" name="status_bar_mute">mute</string>
    <string translatable="false" name="status_bar_volume">volume</string>
    <string translatable="false" name="status_bar_wifi">wifi</string>
    <string translatable="false" name="status_bar_cdma_eri">cdma_eri</string>
    <string translatable="false" name="status_bar_data_connection">data_connection</string>
    <string translatable="false" name="status_bar_phone_evdo_signal">phone_evdo_signal</string>
    <string translatable="false" name="status_bar_phone_signal">phone_signal</string>
    <string translatable="false" name="status_bar_battery">battery</string>
    <string translatable="false" name="status_bar_alarm_clock">alarm_clock</string>
    <string translatable="false" name="status_bar_secure">secure</string>
    <string translatable="false" name="status_bar_clock">clock</string>
    <string translatable="false" name="status_bar_mobile">mobile</string>
    <string translatable="false" name="status_bar_vpn">vpn</string>
    <string translatable="false" name="status_bar_ethernet">ethernet</string>
    <string translatable="false" name="status_bar_airplane">airplane</string>

好了,到這裡我們的第一部分初始化流程就講完了

狀態顯示流程

由上面的初始化流程我們可以知道,每個icon都對應了slot,slot數量比較多,我們就挑一個常見的Headset講下,其他的流程都是大致一樣的。

PhoneStatusBarPolicy.java這個類在初始化的時候註冊了大量的監聽

public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController) {
        mContext = context;
        //  初始化headset的slot
        mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset);

        // listen for broadcasts
        IntentFilter filter = new IntentFilter();
        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
        filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
        //  註冊headset狀態變化的action
        filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
        filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
        filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
        filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
        filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
        mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);

這個類就是完成icon新增和狀態監聽的地方,然後當收到對應的action變化的時候,更新headset icon

private void updateHeadsetPlug(Intent intent) {
        boolean connected = intent.getIntExtra("state", 0) != 0;
        boolean hasMic = intent.getIntExtra("microphone", 0) != 0;
        if (connected) {
            String contentDescription = mContext.getString(hasMic
                    ? R.string.accessibility_status_bar_headset
                    : R.string.accessibility_status_bar_headphones);
            mIconController.setIcon(mSlotHeadset, hasMic ? R.drawable.ic_headset_mic
                    : R.drawable.ic_headset, contentDescription);
            mIconController.setIconVisibility(mSlotHeadset, true);
        } else {
            mIconController.setIconVisibility(mSlotHeadset, false);
        }
    }

其中主要的方法如下
mIconController.setIcon(mSlotHeadset, hasMic ? R.drawable.ic_headset_mic
: R.drawable.ic_headset, contentDescription);
mIconController.setIconVisibility(mSlotHeadset, true);

這兩個函式裡面setIcon負責設定icon,而setIconVisibility則根據connected狀態設定icon的可見性,下面我們就看下這裡面的流程是怎麼和初始化流程連線在一起的。

來到StatusBarIconControllerImpl.java這個類中的實現

@Override
    public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
        int index = getSlotIndex(slot);
        StatusBarIcon icon = getIcon(index);
        if (icon == null) {
            icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
                    Icon.createWithResource(mContext, resourceId), 0, 0, contentDescription);
            setIcon(slot, icon);
        } else {
            icon.icon = Icon.createWithResource(mContext, resourceId);
            icon.contentDescription = contentDescription;
            handleSet(index, icon);
        }
    }

邏輯很清晰,首先根據slot找到對應的index,然後用index取得對應的icon,誠然第一次的時候,icon == null,所以通過new StatusBarIcon建立一個,然後呼叫 setIcon(slot, icon),我們接著往下看

    @Override
    public void setIcon(String slot, StatusBarIcon icon) {
        setIcon(getSlotIndex(slot), icon);
    }

    @Override
    public void setIcon(int index, StatusBarIcon icon) {
        if (icon == null) {
            removeIcon(index);
            return;
        }
        boolean isNew = getIcon(index) == null;
        super.setIcon(index, icon);
        if (isNew) {
            addSystemIcon(index, icon);
        } else {
            handleSet(index, icon);
        }
    }

因為我們之前已經添加了icon,此處icon非空,所以不會走return,但是我們剛才getIcon(index) == null的,所以主要看的是addSystemIcon(index, icon)方法

private void addSystemIcon(int index, StatusBarIcon icon) {
        String slot = getSlot(index);
        int viewIndex = getViewIndex(index);
        boolean blocked = mIconBlacklist.contains(slot);

        mIconLogger.onIconVisibility(getSlot(index), icon.visible);
        mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, icon));
    }

有沒有看到一個熟悉的面孔——onIconAdded,對了這個就是我們初始化流程裡面StatusBarIconController中新增icon的入口,好了到這裡setIcon就新增完畢了。

接著就是setIconVisibility函數了,我們再回到StatusBarIconControllerImpl.java中

public void setIconVisibility(String slot, boolean visibility) {
        int index = getSlotIndex(slot);
        StatusBarIcon icon = getIcon(index);
        if (icon == null || icon.visible == visibility) {
            return;
        }
        icon.visible = visibility;
        handleSet(index, icon);
    }

icon已經建立成功了,icon非空,並且第一次是icon.visible != visibility的,自然就會順利的走到handleSet(index, icon)

private void handleSet(int index, StatusBarIcon icon) {
        int viewIndex = getViewIndex(index);
        mIconLogger.onIconVisibility(getSlot(index), icon.visible);
        mIconGroups.forEach(l -> l.onSetIcon(viewIndex, icon));
    }

好了又是一個熟悉的面孔——onSetIcon,這個也是我們初始化流程裡面StatusBarIconController中setIcon的地方,setIconVisibility也設定完畢了。

到這裡,status icon載入流程已經講完,後面有時間還會講下notification icon和signal icon的載入流程,敬請關注。

如有什麼問題歡迎指正。

本文章已經獨家授權ApeClub公眾號使