一、SystemUI简要介绍
作为Android系统核心应用,SystemUI负责反馈系统及应用状态并与用户保持大量的交互,功能代码逻辑也非常复杂。功能代码模块如下图:
StatusBar:通知消息提示和状态展现
NavigationBar:返回,HOME,Recent
KeyGuard:锁屏模块可以看做单独的应用,提供基本的手机个人隐私保护
Recents:近期应用管理,以堆叠栈的形式展现。
Notification Panel:展示系统或应用通知内容。提供快速系统设置开关。
VolumeUI:来用展示或控制音量的变化:媒体音量、铃声音量与闹钟音量
截屏界面:长按电源键+音量下键后截屏,用以展示截取的屏幕照片/内容
PowerUI:主要处理和Power相关的事件,比如省电模式切换、电池电量变化和开关屏事件等。
RingtonePlayer:铃声播放
StackDivider:控制管理分屏
PipUI:提供对于画中画模式的管理
接下来我们针对状态栏、导航栏、下拉列表通知栏做简要分析。
二、SystemUI代码功能模块简要说明
代码路径:framework/base/packages/SystemUI。那SystemUI是什么时候启动的呢,由谁来调用,答案是在 SystemServer 进程服务里。我们看一下相关代码:
mActivityManagerService.systemReady(() -> {
....................
....................
traceBeginAndSlog("StartSystemUI");
try {
startSystemUi(context, windowManagerF);
} catch (Throwable e) {
reportWtf("starting System UI", e);
}
static final void startSystemUi(Context context, WindowManagerService windowManager) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
windowManager.onSystemUiStarted();
}
可以看到在系统启动完成后或调用startSystemUi方法。然后启动SystemUI。接下来看开SystemUIService中做了哪些操作。看onCreate方法里有一句 ((SystemUIApplication) getApplication()).startServicesIfNeeded(); 在看SystemUIApplication中的这个方法
/**
* The classes of the stuff to start.
*/
private final Class<?>[] SERVICES = new Class[] {
Dependency.class, //包含了很多各种数据操作类,单例获取
NotificationChannels.class,
CommandQueue.CommandQueueStart.class,
KeyguardViewMediator.class,
Recents.class, //近期任务
VolumeUI.class,//音量相关
Divider.class,//分屏
SystemBars.class,//状态栏导航栏,下拉菜单通知面板
StorageNotification.class,
PowerUI.class, //电源界面
RingtonePlayer.class,
KeyboardUI.class,
PipUI.class, //画中画
ShortcutKeyDispatcher.class,
VendorServices.class,
GarbageMonitor.Service.class,
LatencyTester.class,
GlobalActionsComponent.class,
RoundedCorners.class,
};
public void startServicesIfNeeded() {
startServicesIfNeeded(SERVICES);
}
private void startServicesIfNeeded(Class<?>[] services) {
if (mServicesStarted) {
return;
}
if (!mBootCompleted) {
// check to see if maybe it was already completed long before we began
// see ActivityManagerService.finishBooting()
if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
mBootCompleted = true;
if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
}
}
Log.v(TAG, "Starting SystemUI services for user " +
Process.myUserHandle().getIdentifier() + ".");
TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
Trace.TRACE_TAG_APP);
log.traceBegin("StartServices");
final int N = services.length;
for (int i = 0; i < N; i++) {
Class<?> cl = services[i];
if (DEBUG) Log.d(TAG, "loading: " + cl);
log.traceBegin("StartServices" + cl.getSimpleName());
long ti = System.currentTimeMillis();
try {
Object newService = SystemUIFactory.getInstance().createInstance(cl);
mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
}
mServices[i].mContext = this;
mServices[i].mComponents = mComponents;
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
log.traceEnd();
// Warn if initialization of component takes too long
ti = System.currentTimeMillis() - ti;
if (ti > 1000) {
Log.w(TAG, "Initialization of " + cl.getName() + " took " + ti + " ms");
}
if (mBootCompleted) {
mServices[i].onBootCompleted();
}
}
log.traceEnd();
....
}
上面的操作主要对各个模块功能进行创建启动,调用start方法。 这篇文章主要对SystemBars.class做主要分析,这个模块也是 SystemUI中最为复杂的模块,熟悉了找个模块基本了一半的功能。刚提到会调用SystemBar中的onCreate方法。我们看看onCreate方法中做了哪些操作
@Override
public void start() {
if (DEBUG) Log.d(TAG, "start");
createStatusBarFromConfig();
}
private void createStatusBarFromConfig() {
if (DEBUG) Log.d(TAG, "createStatusBarFromConfig");
final String clsName = mContext.getString(R.string.config_statusBarComponent);
if (clsName == null || clsName.length() == 0) {
throw andLog("No status bar component configured", null);
}
Class<?> cls = null;
try {
cls = mContext.getClassLoader().loadClass(clsName);
} catch (Throwable t) {
throw andLog("Error loading status bar component: " + clsName, t);
}
try {
mStatusBar = (SystemUI) cls.newInstance();
} catch (Throwable t) {
throw andLog("Error creating status bar component: " + clsName, t);
}
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mComponents;
mStatusBar.start();
if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName());
}
这个还是很简单的主要创建了StatusBar对象并调用其start方法。所以针对本篇文章主题主要对StartBar这个类扩展然后进行简要分析。
三、状态栏、通知逻辑分析
接着上节的分析,我们看StartBar 的start方法做了哪些操作,这个类非常复杂,有7000多行代码。所以我简要挑重点来讲一下。
@Override
public void start() {
mNetworkController = Dependency.get(NetworkController.class); //网络控制,实现类NetworkControllerImpl 主要对手机wifi、移动网络、以太网、是否飞行模式状态逻辑处理判断
mUserSwitcherController = Dependency.get(UserSwitcherController.class);用户切换功能逻辑控制,安卓是多用户系统,里面有用户添加切换删除等逻辑判断
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);//屏幕周期判断,是否屏幕亮屏关屏
mScreenLifecycle.addObserver(mScreenObserver);
mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);//手机休眠状态回调判断
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mBatteryController = Dependency.get(BatteryController.class);//电池电量,是否省电模式
mAssistManager = Dependency.get(AssistManager.class);//SystemUI帮助类,在这主要是一些动画控制
mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);在这判断是否在dreaming状态,手机空闲状态
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE)); //判断设置安卓是否是暗黑模式
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);//壁纸颜色监听
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mForegroundServiceController = Dependency.get(ForegroundServiceController.class);//前台服务通知管理
mDisplay = mWindowManager.getDefaultDisplay();
updateDisplaySize();
Resources res = mContext.getResources();
mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src);//这个带有scrim的都是一些阴影效果功能控制
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);//是否有删除所有通知功能
mAlwaysExpandNonGroupedNotification =
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);同一个组的通知是否要一直展开
DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));//日期
putComponent(StatusBar.class, this);
// start old BaseStatusBar.start().
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);//锁屏中的一些逻辑判断,锁屏是否可以打开摄像头等
mNotificationData = new NotificationData(this);//通知的实体类,所有通知的数据存储
mMessagingUtil = new NotificationMessagingUtil(mContext);//通知信息帮助工具
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); //主要一些辅助性的功能,帮助盲人
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);//观察用户是否完成了新手引导
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
mSettingsObserver);//勿打扰模式观察
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
mLockscreenSettingsObserver,
UserHandle.USER_ALL);
if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
false,
mSettingsObserver,
UserHandle.USER_ALL);//锁屏是否显示通知观察
}
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
true,
mLockscreenSettingsObserver,
UserHandle.USER_ALL);//锁屏通知隐藏一些重要数据观察
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));//这个重点了,这个是我们监听StausBarService的一些数据回调
mRecents = getComponent(Recents.class);//近期任务
final Configuration currentConfig = res.getConfiguration();
mLocale = currentConfig.locale;
mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);//layout方向,从左到右,从右到左
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);//安卓用户管理
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);//锁屏管理
mLockPatternUtils = new LockPatternUtils(mContext);
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);//继承 IStatusBar.Stub
mCommandQueue.addCallbacks(this);//添加数据监听回调
int[] switches = new int[9];//一些禁用列表
ArrayList<IBinder> binders = new ArrayList<>();
ArrayList<String> iconSlots = new ArrayList<>();图标名称
ArrayList<StatusBarIcon> icons = new ArrayList<>();图标
Rect fullscreenStackBounds = new Rect();
Rect dockedStackBounds = new Rect();
try {
mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
fullscreenStackBounds, dockedStackBounds);//向服务注册
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
createAndAddWindows();//这个重点方法,创建相关的视图
mSettingsObserver.onChange(false); // set up
mCommandQueue.disable(switches[0], switches[6], false /* animate */);
setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
fullscreenStackBounds, dockedStackBounds);
topAppWindowChanged(switches[2] != 0);
// StatusBarManagerService has a back up of IME token and it's restored here.
setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0);
// Set up the initial icon state
int N = iconSlots.size();
for (int i=0; i < N; i++) {
mCommandQueue.setIcon(iconSlots.get(i), icons.get(i));
}
// Set up the initial notification state. 重点注册通知,其他程序通过NotificationManager发送通知我这里接受到,注册监听
try {
mNotificationListener.registerAsSystemService(mContext,
new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
UserHandle.USER_ALL);
} catch (RemoteException e) {
Log.e(TAG, "Unable to register notification listener", e);
}
if (DEBUG) {
Log.d(TAG, String.format(
"init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
icons.size(),
switches[0],
switches[1],
switches[2],
switches[3]
));
}
mCurrentUserId = ActivityManager.getCurrentUser();
setHeadsUpUser(mCurrentUserId);
//用户状态广播
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_PRESENT);
mContext.registerReceiver(mBaseBroadcastReceiver, filter);
//通知相关广播
IntentFilter internalFilter = new IntentFilter();
internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
internalFilter.addAction(BANNER_ACTION_CANCEL);
internalFilter.addAction(BANNER_ACTION_SETUP);
mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
//屏幕锁屏状态
IntentFilter allUsersFilter = new IntentFilter();
allUsersFilter.addAction(
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
null, null);
updateCurrentProfilesCache();
IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
Context.VR_SERVICE));
try {
vrManager.registerListener(mVrStateCallbacks);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register VR mode state listener: " + e);
}
mNonBlockablePkgs = new HashSet<>();
Collections.addAll(mNonBlockablePkgs, res.getStringArray(
com.android.internal.R.array.config_nonBlockableNotificationPackages));
// end old BaseStatusBar.start().
mMediaSessionManager
= (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
// TODO: use MediaSessionManager.SessionListener to hook us up to future updates
// in session state
//系统图标管理也比较重要,如wifi、蓝牙、闹钟等
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
mSettingsObserver.onChange(false); // set up
//弹出式的通知
mHeadsUpObserver.onChange(true); // set up
if (ENABLE_HEADS_UP) {
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true,
mHeadsUpObserver);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
mHeadsUpObserver);
}
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
mUnlockMethodCache.addListener(this);
startKeyguard();
KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback);
putComponent(DozeHost.class, mDozeServiceHost);
notifyUserAboutHiddenNotifications();
//屏幕pinning请求
mScreenPinningRequest = new ScreenPinningRequest(mContext);
mFalsingManager = FalsingManager.getInstance(mContext);
Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
//屏幕配置简体
Dependency.get(ConfigurationController.class).addCallback(this);
}
在onCreate中做了很多初始化的操作,里面包含了很多种状态的监听控制还是很复杂的,接下来我们分析状态栏是怎么加载处理的,还有通知的一些控制实现。进入到 createAndAddWindows()开始分析吧。
public void createAndAddWindows() {
addStatusBarWindow();
}
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);//管理状态栏,导航栏、面板的状态切换
mRemoteInputController = new RemoteInputController(mHeadsUpManager);//弹出式通知快捷输入
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());//这里把整个View添加到window中去
}
我们重点看看makeStatusBarView() 这个方法
protected void makeStatusBarView() {
final Context context = mContext;
updateDisplaySize(); // populates mDisplayMetrics
updateResources();
updateTheme();
inflateStatusBarWindow(context); //mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null); 创建个布局,总的布局,等下我们具体分析下
.....
mNotificationIconAreaController = SystemUIFactory.getInstance()
.createNotificationIconAreaController(context, this);//状态栏左半部分的通知栏控制器
inflateShelf();//初始化面板通知栏的子布局
mNotificationIconAreaController.setupShelf(mNotificationShelf);
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController); //暗黑主题监听控制
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
mStatusBarView = (PhoneStatusBarView) fragment.getView();
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanel);
mStatusBarView.setScrimController(mScrimController);
mStatusBarView.setBouncerShowing(mBouncerShowing);
setAreThereNotifications();
checkBarModes();
/// M: add for plmn display feature @{
attachPlmnPlugin();
///@}
}).getFragmentManager()
.beginTransaction()
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
CollapsedStatusBarFragment.TAG)
.commit();
...
}
我们先看上面这一小段代码,其他先不分析,上面可以看到把super_status_bar中的status_bar_container替换成了CollapsedStatusBarFragment,这个Fragment其实就是状态栏相关的布局了。状态栏中布局主要包含以下
//根布局 status_bar.xml
<com.android.systemui.statusbar.phone.PhoneStatusBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height"
android:id="@+id/status_bar"
android:background="@drawable/system_bar_background"
android:orientation="vertical"
android:focusable="false"
android:descendantFocusability="afterDescendants"
>
<ImageView
android:id="@+id/notification_lights_out"
android:layout_width="@dimen/status_bar_icon_size"
android:layout_height="match_parent"
android:paddingStart="6dip"
android:paddingBottom="2dip"
android:src="@drawable/ic_sysbar_lights_out_dot_small"
android:scaleType="center"
android:visibility="gone"
/>
<LinearLayout android:id="@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="6dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
>
<!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->//状态栏坐车通知栏
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal" />
<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>
</LinearLayout>
<ViewStub
android:id="@+id/emergency_cryptkeeper_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout="@layout/emergency_cryptkeeper_text"
/>
</com.android.systemui.statusbar.phone.PhoneStatusBarView>
首先分析通知图标怎么加载出来,看上面代码有一句
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController); 我们跟进去看这里面的操作
public void initNotificationIconArea(NotificationIconAreaController
notificationIconAreaController) {
ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
mNotificationIconAreaInner =
notificationIconAreaController.getNotificationInnerAreaView();
if (mNotificationIconAreaInner.getParent() != null) {
((ViewGroup) mNotificationIconAreaInner.getParent())
.removeView(mNotificationIconAreaInner);
}
notificationIconArea.addView(mNotificationIconAreaInner);
// Default to showing until we know otherwise.
showNotificationIconArea(false);
}
可以看到NotificationIconAreaController 有个getNotificationInnerAreaView()方法把通知的View添加进去了,我简要的分析找个类的一些功能
public NotificationIconAreaController(Context context, StatusBar statusBar) {
mStatusBar = statusBar;
mNotificationColorUtil = NotificationColorUtil.getInstance(context);
mContext = context;
initializeNotificationAreaViews(context);
}
protected View inflateIconArea(LayoutInflater inflater) {
return inflater.inflate(R.layout.notification_icon_area, null);
}
/**
* Initializes the views that will represent the notification area.
*/
protected void initializeNotificationAreaViews(Context context) {
reloadDimens(context);
LayoutInflater layoutInflater = LayoutInflater.from(context);
mNotificationIconArea = inflateIconArea(layoutInflater);
//这个就是具体的去加载的通知区域的View了.即上面返回的View,这个是个自定义的FrameLayout
mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
R.id.notificationIcons);
mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
}
protected View inflateIconArea(LayoutInflater inflater) {
return inflater.inflate(R.layout.notification_icon_area, null);
}
我们看看通知的数据从哪里来的,我们发现了这个类的下面方法
/**
* Updates the notifications with the given list of notifications to display. NotificationData前面已经说过了所有通知数据都封装在找个类中
*/
public void updateNotificationIcons(NotificationData notificationData) {
updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons,
false /* showAmbient */);
updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons,
NotificationShelf.SHOW_AMBIENT_ICONS);
applyNotificationIconsTint();
}
//这个就是具体的根据数据来更新通知icon了
private void updateIconsForLayout(NotificationData notificationData,
Function<NotificationData.Entry, StatusBarIconView> function,
NotificationIconContainer hostLayout, boolean showAmbient) {
ArrayList<StatusBarIconView> toShow = new ArrayList<>(
mNotificationScrollLayout.getChildCount());
// Filter out ambient notifications and notification children.
for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
if (shouldShowNotificationIcon(ent, notificationData, showAmbient)) {
toShow.add(function.apply(ent));
}
}
}
// In case we are changing the suppression of a group, the replacement shouldn't flicker
// and it should just be replaced instead. We therefore look for notifications that were
// just replaced by the child or vice-versa to suppress this.
ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
ArrayList<View> toRemove = new ArrayList<>();
for (int i = 0; i < hostLayout.getChildCount(); i++) {
View child = hostLayout.getChildAt(i);
if (!(child instanceof StatusBarIconView)) {
continue;
}
if (!toShow.contains(child)) {
boolean iconWasReplaced = false;
StatusBarIconView removedIcon = (StatusBarIconView) child;
String removedGroupKey = removedIcon.getNotification().getGroupKey();
for (int j = 0; j < toShow.size(); j++) {
StatusBarIconView candidate = toShow.get(j);
if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
&& candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
if (!iconWasReplaced) {
iconWasReplaced = true;
} else {
iconWasReplaced = false;
break;
}
}
}
if (iconWasReplaced) {
ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
if (statusBarIcons == null) {
statusBarIcons = new ArrayList<>();
replacingIcons.put(removedGroupKey, statusBarIcons);
}
statusBarIcons.add(removedIcon.getStatusBarIcon());
}
toRemove.add(removedIcon);
}
}
// removing all duplicates
ArrayList<String> duplicates = new ArrayList<>();
for (String key : replacingIcons.keySet()) {
ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
if (statusBarIcons.size() != 1) {
duplicates.add(key);
}
}
replacingIcons.removeAll(duplicates);
hostLayout.setReplacingIcons(replacingIcons);
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
hostLayout.removeView(toRemove.get(i));
}
final FrameLayout.LayoutParams params = generateIconLayoutParams();
for (int i = 0; i < toShow.size(); i++) {
View v = toShow.get(i);
// The view might still be transiently added if it was just removed and added again
hostLayout.removeTransientView(v);
if (v.getParent() == null) {
hostLayout.addView(v, i, params);
}
}
hostLayout.setChangingViewPositions(true);
// Re-sort notification icons
final int childCount = hostLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
View actual = hostLayout.getChildAt(i);
StatusBarIconView expected = toShow.get(i);
if (actual == expected) {
continue;
}
hostLayout.removeView(expected);
hostLayout.addView(expected, i);
}
hostLayout.setChangingViewPositions(false);
hostLayout.setReplacingIcons(null);
}
谁去调用找个方法呢,答案是StatuBar这个类中,这个类在第一节onCreate中初始化了一堆数据,其中就包含了注册用户发送notification的监听回调
private final NotificationListenerWithPlugins mNotificationListener =
new NotificationListenerWithPlugins() {
//每次有新的通知都会回调找个方法,添加或者更新通知
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (true/**DEBUG*/) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
mHandler.post(new Runnable() {
@Override
public void run() {
processForRemoteInput(sbn.getNotification());
String key = sbn.getKey();
mKeysKeptForRemoteInput.remove(key);
boolean isUpdate = mNotificationData.get(key) != null;
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since we're not going to show them
// anyway. This is true also when the summary is canceled,
// because children are automatically canceled by NoMan in that case.
if (!ENABLE_CHILD_NOTIFICATIONS
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
if (DEBUG) {
Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
}
// Remove existing notification to avoid stale data.
if (isUpdate) {
removeNotification(key, rankingMap);
} else {
mNotificationData.updateRanking(rankingMap);
}
return;
}
try {
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
}
} catch (InflationException e) {
handleInflationException(sbn, e);
}
}
});
}
}
//通知移除,
@Override
public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
if (true/**DEBUG*/) Log.d(TAG, "onNotificationRemoved: " + sbn);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
mHandler.post(() -> removeNotification(key, rankingMap));
}
}
//通知排序更新
@Override
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onRankingUpdate");
if (rankingMap != null) {
RankingMap r = onPluginRankingUpdate(rankingMap);
mHandler.post(() -> updateNotificationRanking(r));
}
}
};
大家感兴趣再去研究 removeNotification、addNotification等相关方法,反正最终会调用上面提到的方法去更新状态栏通知,找个逻辑也要讲半天时间紧迫。再接下来看开系统通知怎么加载的吧。
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">
//系统icon容器
<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"/>
//信号相关,手机卡信号,是否飞行模式,wifi
<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>
我简要分析系统icon数据怎么加载到这个容器中,其他的不做讲解了。看到 CollapsedStatusBarFragment中的onViewCreated方法
@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));//把系统icon容器传到DarkIConManager中,
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager); 然后StatusBarIconControl添加找个icon管理器,这两句代码很重要,我们从这两句代码去分析整个加载流程
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();
}
首先看开DarkIconManager,这个是StatusBarIconController的内部类,主要管理创建系统icon控件StatusBarIconView,并把它添加到容器中去
public static class DarkIconManager extends IconManager {
private final DarkIconDispatcher mDarkIconDispatcher;
private int mIconHPadding;
public DarkIconManager(LinearLayout linearLayout) {
super(linearLayout);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
}
//根据系统icon名称 icon数据添加创建View
@Override
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIcon icon) {
StatusBarIconView v = addIcon(index, slot, blocked, icon);
mDarkIconDispatcher.addDarkReceiver(v);
}
@Override
protected LayoutParams onCreateLayoutParams() {
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
return lp;
}
@Override
protected void destroy() {
for (int i = 0; i < mGroup.getChildCount(); i++) {
mDarkIconDispatcher.removeDarkReceiver((ImageView) mGroup.getChildAt(i));
}
mGroup.removeAllViews();
}
@Override
protected void onRemoveIcon(int viewIndex) {
mDarkIconDispatcher.removeDarkReceiver((ImageView) mGroup.getChildAt(viewIndex));
super.onRemoveIcon(viewIndex);
}
@Override
public void onSetIcon(int viewIndex, StatusBarIcon icon) {
super.onSetIcon(viewIndex, icon);
mDarkIconDispatcher.applyDark((ImageView) mGroup.getChildAt(viewIndex));
}
}
/**
* Turns info from StatusBarIconController into ImageViews in a ViewGroup.
*/
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;
}
@VisibleForTesting
protected StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
return new StatusBarIconView(mContext, slot, null, blocked);
}
protected LinearLayout.LayoutParams onCreateLayoutParams() {
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
}
protected void destroy() {
mGroup.removeAllViews();
}
protected void onIconExternal(int viewIndex, int height) {
ImageView imageView = (ImageView) mGroup.getChildAt(viewIndex);
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setAdjustViewBounds(true);
setHeightAndCenter(imageView, height);
}
protected void onDensityOrFontScaleChanged() {
for (int i = 0; i < mGroup.getChildCount(); i++) {
View child = mGroup.getChildAt(i);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
child.setLayoutParams(lp);
}
}
private void setHeightAndCenter(ImageView imageView, int height) {
ViewGroup.LayoutParams params = imageView.getLayoutParams();
params.height = height;
if (params instanceof LinearLayout.LayoutParams) {
((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
}
imageView.setLayoutParams(params);
}
protected void onRemoveIcon(int viewIndex) {
mGroup.removeViewAt(viewIndex);
}
public void onSetIcon(int viewIndex, StatusBarIcon icon) {
StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
view.set(icon);
}
}
那我们哪些系统icon数据怎么产生的呢,在第一节StatusBar中onCreate中有一句
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);PhoneStatusBarPolicy这个类就管理了一系列的系统相关图标的状态加载,我们进去看这个类做了哪些操作
在构造方法中初始化了许多操作,并监听系统交互各种广播状态变化,以便对其显示或隐藏icon
public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController) {
mContext = context;
mIconController = iconController;
mCast = Dependency.get(CastController.class);//是否有截屏录屏、无线投屏这两个功能状态的监听
mHotspot = Dependency.get(HotspotController.class);//热点状态控制监听
mBluetooth = Dependency.get(BluetoothController.class);//蓝牙功能状态监听
mNextAlarm = Dependency.get(NextAlarmController.class);//闹钟功能状态监听
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mUserInfoController = Dependency.get(UserInfoController.class);//用户信息获取监听
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mRotationLockController = Dependency.get(RotationLockController.class);//是否是自动旋屏
mDataSaver = Dependency.get(DataSaverController.class);//后台限制数据流量
mZenController = Dependency.get(ZenModeController.class);//勿打扰模式监听控制
mProvisionedController = Dependency.get(DeviceProvisionedController.class);//用户是否完成新手引导监听
mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);//锁屏状态监听
mLocationController = Dependency.get(LocationController.class);//定位服务状态监听控制
//下面这些状态就是系统图标的标识字符串
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
mSlotBluetooth = context.getString(com.android.internal.R.string.status_bar_bluetooth);
mSlotTty = context.getString(com.android.internal.R.string.status_bar_tty);
mSlotZen = context.getString(com.android.internal.R.string.status_bar_zen);
mSlotVolume = context.getString(com.android.internal.R.string.status_bar_volume);
mSlotAlarmClock = context.getString(com.android.internal.R.string.status_bar_alarm_clock);
mSlotManagedProfile = context.getString(
com.android.internal.R.string.status_bar_managed_profile);
mSlotRotate = context.getString(com.android.internal.R.string.status_bar_rotate);
mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset);
mSlotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver);
mSlotLocation = context.getString(com.android.internal.R.string.status_bar_location);
//监听一些广播与上面状态有关
// listen for broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_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);
// listen for user / profile change.
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG);
} catch (RemoteException e) {
// Ignore
}
//文字传语音状态
// TTY status
updateTTY();
//蓝牙
// bluetooth status
updateBluetooth();
//这个mIconController就是StatusBarIconControllerImpl对象。设置icon和控制icon是否可见,等下我们跟进去看看具体的怎么添加
// Alarm clock
mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null);
mIconController.setIconVisibility(mSlotAlarmClock, false);
// zen
mIconController.setIcon(mSlotZen, R.drawable.stat_sys_zen_important, null);
mIconController.setIconVisibility(mSlotZen, false);
// volume
mIconController.setIcon(mSlotVolume, R.drawable.stat_sys_ringer_vibrate, null);
mIconController.setIconVisibility(mSlotVolume, false);
updateVolumeZen();
// cast
mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
mIconController.setIconVisibility(mSlotCast, false);
// hotspot
mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
mContext.getString(R.string.accessibility_status_bar_hotspot));
mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled());
// managed profile
mIconController.setIcon(mSlotManagedProfile, R.drawable.stat_sys_managed_profile_status,
mContext.getString(R.string.accessibility_managed_profile));
mIconController.setIconVisibility(mSlotManagedProfile, mManagedProfileIconVisible);
// data saver
mIconController.setIcon(mSlotDataSaver, R.drawable.stat_sys_data_saver,
context.getString(R.string.accessibility_data_saver_on));
mIconController.setIconVisibility(mSlotDataSaver, false);
//对上面的添加状态回调,以便设置icon是否可见
mRotationLockController.addCallback(this);
mBluetooth.addCallback(this);
mProvisionedController.addCallback(this);
mZenController.addCallback(this);
mCast.addCallback(mCastCallback);
mHotspot.addCallback(mHotspotCallback);
mNextAlarm.addCallback(mNextAlarmCallback);
mDataSaver.addCallback(this);
mKeyguardMonitor.addCallback(this);
mLocationController.addCallback(this);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskListener);
// Clear out all old notifications on startup (only present in the case where sysui dies)
NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
for (StatusBarNotification notification : noMan.getActiveNotifications()) {
if (notification.getId() == SystemMessage.NOTE_INSTANT_APPS) {
noMan.cancel(notification.getTag(), notification.getId());
}
}
DockedStackExistsListener.register(exists -> {
mDockedStackExists = exists;
updateForegroundInstantApps();
});
}
接下来看看setIcon方法怎么设置的,先看其构造方法,这个类继承自StatusBarIconList,管理系统图标、系统标识的添加移除
public StatusBarIconControllerImpl(Context context) {
//设置有哪些系统图标标识,从配置文件中加载出来
super(context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
Dependency.get(ConfigurationController.class).addCallback(this);
//这个是是否是暗黑主题相关,以便更新系统图标颜色
mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
mContext = context;
loadDimens();
SysUiServiceProvider.getComponent(context, CommandQueue.class)
.addCallbacks(this);
Dependency.get(TunerService.class).addTunable(this, ICON_BLACKLIST);
}
然后去看setIcon方法
@Override
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
//根据标识文字获取到标识的下标,根据标识的下标来得到一个图标数据封装类StatusBarIcon
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);
}
}
我们在看看setIcon方法
@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;
}
//如果icon没有添加就去添加,否则只是更新这个状态
boolean isNew = getIcon(index) == null;
super.setIcon(index, icon);//保存在List中以便下次使用
if (isNew) {
addSystemIcon(index, icon);
} else {
handleSet(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);
//这个就是在Fragment设置进去的DarkIconManager,调用其onIconAdded方法实现添加
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, icon));
}
我们看看setIconVisibility方法,其实根据标识找到icon然后更新其的可见性
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
private void handleSet(int index, StatusBarIcon icon) {
int viewIndex = getViewIndex(index);
mIconLogger.onIconVisibility(getSlot(index), icon.visible);
mIconGroups.forEach(l -> l.onSetIcon(viewIndex, icon));
}
到目前为止我们就分析完了状态栏的相关功能流程了。还是比较复杂的。由于时间问题,对下拉通知栏和导航栏等下次再讲了