引言
今天我们主要讲的是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公众号使用。