Android 8.0 SystemUI(四):二说顶部 StatusBar

点击蓝字关注“猿湿Xoong”

一个爱折腾爱分享的技术公众号

大家好,我是ptt,本篇是 SystemUI 的第四篇,也是 StatusBar 的第二说。

接着上一说的 StatusBar 之 StatusIcon,这篇说一说 StatusBar 的 Signal Cluster - 状态栏上显示wifi、手机等信号状态的地方。

即上图中「箭头3」指向的地方。

目录

老规矩,先上目录。

大体框架

SignalCluster 包含的图标有:

VPN提示、Ethernet图标、Wifi图标、Airplane提示、NoSim提示,移动网络图标等共6类图标。

在代码中,关于信号图标的核心类是 SignalClusterView.java,这个类和布局文件 res/layout/signal_cluster_view.xml 高度相关。Signal相关的6类图标均在其中。对应布局加载路径如下。

而 status_bar.xml 被 CollapsedStatusBarFragment 加载。在它的onViewCreated 中,通过 id - signal_cluster 找到布局并inflate。

private SignalClusterView mSignalClusterView;

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    ...
    // 此处加载SignalClusterView
    mSignalClusterView = mStatusBar.findViewById(R.id.signal_cluster);
    Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
    ...
}

既然Signal相关的核心类是SignalClusterView,T哥画了一个大概的类图。

根据上图,从SignalClusterView开始辐射研究。

SignalClusterView继承LinearLayout,并实现了接口NetworkController的内部接口SignalCallback。此接口的方法,主要是用来更新对应View。

public interface SignalCallback {
    default void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
            boolean activityIn, boolean activityOut, String description, boolean isTransient) {}
    default void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
            int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
            String description, boolean isWide, int subId, boolean roaming) {}
    default void setSubs(List<SubscriptionInfo> subs) {}
    default void setNoSims(boolean show, boolean simDetected) {}
    default void setEthernetIndicators(IconState icon) {}
    default void setIsAirplaneMode(IconState icon) {}
    default void setMobileDataEnabled(boolean enabled) {}
}

在SignalClusterView的构造方法中,获得了NetworkControllerImpl对象。

...
private final NetworkController mNetworkController;
...
public SignalClusterView(Context context, AttributeSet attrs, int defStyle) {
    ...
    mNetworkController = Dependency.get(NetworkController.class);
    ...
}

NetworkControllerImpl,顾名思义,是接口NetworkController的实现类,聚合了各种信号controller(sub controller),并通过广播监听各类信号变化。当有变化时,调用对应 sub controller 的对应方法更新State。

各类sub controller,根据不同 state 提供对应 Icon。通过注册进来的SignalCallback,不同controller调用不同回调方法。在SignalClusterView中的回调实现中,将Icon图标设置入ImageView中。

sub controller 大体介绍如下。

// 用于wifi状态更新,提供对应图标
WifiSignalController
// 用于移动信号状态更新,提供对应图标
MobileSignalController
// 用于有线网络(网线)状态更新,并提供对应图标
EthernetSignalController

而 SecurityControllerImpl 负责 VPN 状态的管理和获取。

初始化

Signal相关的图标是如何被初始化的?

在SignalClusterView.java中,当布局从xml中加载完成时, find view by id 初始化各个Signal对应的View。

@Override
protected void onFinishInflate() {
    super.onFinishInflate();

    mVpn            = findViewById(R.id.vpn);
    mEthernetGroup  = findViewById(R.id.ethernet_combo);
    mEthernet       = findViewById(R.id.ethernet);
    mEthernetDark   = findViewById(R.id.ethernet_dark);
    mWifiGroup      = findViewById(R.id.wifi_combo);
    mWifi           = findViewById(R.id.wifi_signal);
    mWifiDark       = findViewById(R.id.wifi_signal_dark);
    mWifiActivityIn = findViewById(R.id.wifi_in);
    mWifiActivityOut= findViewById(R.id.wifi_out);
    mAirplane       = findViewById(R.id.airplane);
    mNoSims         = findViewById(R.id.no_sims);
    mNoSimsDark     = findViewById(R.id.no_sims_dark);
    mNoSimsCombo    =             findViewById(R.id.no_sims_combo);
    mWifiAirplaneSpacer =         findViewById(R.id.wifi_airplane_spacer);
    mWifiSignalSpacer =           findViewById(R.id.wifi_signal_spacer);
    mMobileSignalGroup =          findViewById(R.id.mobile_signal_group);

    maybeScaleVpnAndNoSimsIcons();
}

在onAttachedToWindow方法中,进行所有 signal icon 的初始化。但是VPN 和 其他 Signal 有不同。

@Override
protected void onAttachedToWindow() {
    ...
    // 初始化代表vpn的变量
    mVpnVisible = mSecurityController.isVpnEnabled();
    ...

    // apply - 根据状态,进行一遍所有view的更新。
    apply();
    ...
    // 添加回调
    mNetworkController.addCallback(this);
    mSecurityController.addCallback(this);
}

VPN 的状态可以直接通过SecurityController获取到。所以此处直接初始化,并调用apply刷新。

而其他所有 Signal 的初始化,则在NetworkControllerImpl 的 addCallback方法中,每一行都是在初始化。并通过cb回调到SignalClusterView的相关实现。最后一个保存当前回调,待有状态变化时更新。

public void addCallback(SignalCallback cb) {
    cb.setSubs(mCurrentSubscriptions);
    cb.setIsAirplaneMode(new IconState(mAirplaneMode,
            TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
    cb.setNoSims(mHasNoSubs, mSimDetected);
    mWifiSignalController.notifyListeners(cb);
    mEthernetSignalController.notifyListeners(cb);
    for (int i = 0; i < mMobileSignalControllers.size(); i++) {
        MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
        mobileSignalController.notifyListeners(cb);
    }
    mCallbackHandler.setListening(cb, true);
}

状态更新

对于非VPN的Signal更新,是在NetworkControllerImpl中,通过注册广播的方式进行监听 - 除了手机网络是通过PhoneStateListener。收到广播后,调用回调更新,方式比较简单,这里不再累赘。

private void registerListeners() {
    for (int i = 0; i < mMobileSignalControllers.size(); i++) {
        MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
        mobileSignalController.registerListener();
    }
    ...
    // broadcasts
    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_AIRPLANE_MODE_CHANGED);
    filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
    mContext.registerReceiver(this, filter, null, mReceiverHandler);
    ...
}

而对于VPN的监听,则是在SecurityControllerImpl中,通过ConnectivityManager对特定网络注册回调,实现监听。

···
private static final NetworkRequest REQUEST = new NetworkRequest.Builder()
        .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
        .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
        .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
        .build();

···
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        if (DEBUG) Log.d(TAG, "onAvailable " + network.netId);
        updateState();
        fireCallbacks();
    };

    // TODO Find another way to receive VPN lost.  This may be delayed depending on
    // how long the VPN connection is held on to.
    @Override
    public void onLost(Network network) {
        if (DEBUG) Log.d(TAG, "onLost " + network.netId);
        updateState();
        fireCallbacks();
    };
};

...
public SecurityControllerImpl(Context context, SecurityControllerCallback callback) {
    ...
    // TODO: re-register network callback on user change.
    mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
   ...
}

其中REQUEST代表着第三方App的VPN网络类型。

当有满足REQUEST类型的网络时,mNetworkCallback的onAvaiable就会被调用。此时通过注册进入的SecurityControllerCallback,用以更新VPN。

或者,当满足REQUEST类型的网络丢失时,mNetworkCallback的onLost会被调用, 从而更新状态栏,去除VPN图标。

T哥还知道,平日里,此类方法的另一个常用场景是:当应用仅想通过wifi或仅仅想通过移动数据进行网络请求时,则构造一个对应网络类型的request,一个callback,并通过CM的registerNetworkCallback进行注册。待callback的onAvaiable被回调,通过cm.bindProcessToNetwork申请你想要的网络。

最后

这是SystemUI系列的第四篇。觉得T哥写的东西对你有价值,欢迎关注。

推荐阅读

Android 8.0 SystemUI(三):一说顶部 StatusBar

Android SystemUI(二):启动流程和初始化

Android SystemUI(一):图文并茂的介绍 :D

1024G免费IT资源共享!Android、Java、C、C++、Linux、数据库、人工智能等等领域基础及进阶学习资料,后台回复「1024」就能免费获取!

--- End ---

原文发布于微信公众号 - 猿湿Xoong(skypeng-funny)

原文发表时间:2018-08-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android先生

Android App优化之提升你的App启动速度之实例挑战

以之前写的Github App:https://github.com/mingjunli/GithubApp为例.

1053
来自专栏IT大咖说

Oracle中最容易被忽略的那些实用特性

内容来源:2017 年 04 月 08 日,ITPUB管理版版主吕海波在“DBGeeK+PG数据库技术沙龙(4月杭州站)”进行《Oracle中最容易被忽略的那些...

1416
来自专栏增长技术

Android文件存储使用

一般地,通过 Context 和 Environment 相关的方法获取文件存取的路径。

1552
来自专栏QQ音乐技术团队的专栏

[Android] Toast问题深度剖析(一)

伴随着我们开发的深入,Toast 的问题也逐渐暴露出来。本文章就将解释 Toast 这些问题产生的具体原因。

1.8K15
来自专栏码农笔录

Android全能开源项目xUtils3开发教程、简单封装

1492
来自专栏向治洪

android 网络通信框架volly

1. 什么是Volley 在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection...

1945
来自专栏Android干货园

Base封装(一)--我的最简MVP架构

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/73...

1351
来自专栏向治洪

安全退出app,activoty栈管理

前言 由于一个同学问到我如何按照一个流程走好之后回到首页,我以前看到过4个解决方案,后来发现有做个记录和总结的必要,就写了这篇博文。(之前看小强也写过一篇,这...

30710
来自专栏刘望舒

探究RemoteViews的作用和原理

RmoteViews是一个能显示在其他进程的视图。同样也提供了一些基本的操作方法来修改视图的内容。

1161
来自专栏KK的小酒馆

Service的跨进程开发Android开发高级进阶

Service的跨进程通信主要由两种Android提供的方法进行,一个是AIDL,通过创建一个AIDL文件来完成,另一个是利用Messenger,发送Messa...

1182

扫码关注云+社区