之前发了下拉通知栏开关修改的一篇文章。
这篇文章呢,主要介绍一下Android状态栏信号图标显示的流程。
便于在Android源生上开发的碰到问题的朋友,希望能对读者有所帮助。内容可能比较长
首先还是先介绍布局。

一、状态栏信号布局

路径:android\frameworks\base\packages\SystemUI\res\layout\status_bar.xml
如下代码就是状态栏的布局,包含:时钟、通知、system_icons等。

<?xml version="1.0" encoding="utf-8"?><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"        >                <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>
~
~
system_icons:信号区域:signal_cluster_view、状态栏电池, statusIcons(蓝牙、飞行模式statusIcons)。

system_icons内容如下图:

<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>

~

signal_cluster_view有对应的布局,代码上写了,内容如下,内容较多、我就只截取部分展示了。其中mobile_signal_group就是信号区域了,它也对应一个布局 mobile_signal_group.xmlmobile_signal_group中主要是信号图标、数据流量箭头等位置显示了。

   <LinearLayout       android:id="@+id/mobile_signal_group"       android:layout_height="wrap_content"       android:layout_width="wrap_content"       >   LinearLayout>   <FrameLayout       android:id="@+id/no_sims_combo"       android:layout_height="wrap_content"       android:layout_width="wrap_content"       android:contentDescription="@string/accessibility_no_sims">       <com.android.systemui.statusbar.AlphaOptimizedImageView           android:theme="?attr/lightIconTheme"           android:id="@+id/no_sims"           android:layout_height="wrap_content"           android:layout_width="wrap_content"           android:src="@drawable/stat_sys_no_sims"           />       <com.android.systemui.statusbar.AlphaOptimizedImageView           android:theme="?attr/darkIconTheme"           android:id="@+id/no_sims_dark"           android:layout_height="wrap_content"           android:layout_width="wrap_content"           android:src="@drawable/stat_sys_no_sims"           android:alpha="0.0"           />   FrameLayout>

~

mobile_signal_group.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:systemui="http://schemas.android.com/apk/res-auto"    android:id="@+id/mobile_combo"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:orientation="horizontal"    >    <ImageView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="2dp"        android:id="@+id/data_inout"        android:visibility="gone"        />    <FrameLayout        android:layout_height="17dp"        android:layout_width="wrap_content">        <ImageView            android:id="@+id/mobile_in"            android:layout_height="wrap_content"            android:layout_width="wrap_content"            android:src="@drawable/ic_activity_down"            android:visibility="gone"            android:paddingEnd="2dp"            />        <ImageView            android:id="@+id/mobile_out"            android:layout_height="wrap_content"            android:layout_width="wrap_content"            android:src="@drawable/ic_activity_up"            android:paddingEnd="2dp"            android:visibility="gone"            />    FrameLayout>    <FrameLayout        android:layout_width="wrap_content"        android:layout_height="wrap_content"        >        <FrameLayout            android:id="@+id/mobile_signal_single"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            >            <com.android.systemui.statusbar.AnimatedImageView                android:theme="@style/DualToneLightTheme"                android:id="@+id/mobile_signal"                android:layout_height="wrap_content"                android:layout_width="wrap_content"                systemui:hasOverlappingRendering="false"                />            <com.android.systemui.statusbar.AnimatedImageView                android:theme="@style/DualToneDarkTheme"                android:id="@+id/mobile_signal_dark"                android:layout_height="wrap_content"                android:layout_width="wrap_content"                android:alpha="0.0"                systemui:hasOverlappingRendering="false"                />            <ImageView                android:id="@+id/mobile_type"                android:layout_height="wrap_content"                android:layout_width="wrap_content"                />            <ImageView                android:id="@+id/mobile_roaming"                android:layout_width="wrap_content"                android:layout_height="17dp"                android:paddingStart="22dp"                android:paddingTop="1.5dp"                android:paddingBottom="3dp"                android:scaleType="fitCenter"                android:src="@drawable/stat_sys_roaming"                android:contentDescription="@string/accessibility_data_connection_roaming"                android:visibility="gone" />        FrameLayout>        <LinearLayout            android:id="@+id/mobile_signal_stacked"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:orientation="vertical"            android:visibility="gone"            >            <ImageView                android:id="@+id/mobile_signal_data"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                />            <ImageView                android:id="@+id/mobile_signal_voice"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                />        LinearLayout>    FrameLayout>LinearLayout>

~

~

二、状态栏信号显示流程

接下来是信号的显示流程,android 8.0开始,与之前的android版本信号显示不太一样了。8.0源生代码中信号图标都是代码画出来的,并非与之前版本一样,直接使用图片显示了。所以,在开发中,我需要自己去修改,定制自己的信号样式,显示规格。
SignalClusterView.java
路径:frameworks\base\package\SystemUI\src\com\android\systemui\statusbar\SignalClusterView.java。它对应布局signal_cluster_view.xml,刚刚上面介绍过。
SignalClusterView.java完成控件的初始化、赋予图片显示。
下面,来说说它的逻辑。
SignalClusterView.java中有个内部类:PhoneState。
PhoneState中的apply()方法中对信号控件进行赋值。
其中mMobile就是信号控件
如下代码。

 public boolean apply(boolean isSecondaryIcon) {            if (mMobileVisible && !mIsAirplaneMode) {                if (mLastMobileStrengthId != mMobileStrengthId) {                    if (mReadIconsFromXML) {                       setIconForView(mMobile, mMobileStrengthId);                        setIconForView(mMobileDark, mMobileStrengthId);                    } else {                        mMobile.getDrawable().setLevel(mMobileStrengthId);                        mMobileDark.getDrawable().setLevel(mMobileStrengthId);                    }                    mLastMobileStrengthId = mMobileStrengthId;                }                if (mLastMobileTypeId != mMobileTypeId) {                    mMobileType.setImageResource(mMobileTypeId);                    mLastMobileTypeId = mMobileTypeId;                }                mDataActivity.setImageResource(mDataActivityId);                if (mStackedDataId != 0 && mStackedVoiceId != 0) {                    mStackedData.setImageResource(mStackedDataId);                    mStackedVoice.setImageResource(mStackedVoiceId);                    mMobileSingleGroup.setVisibility(View.GONE);                    mMobileStackedGroup.setVisibility(View.VISIBLE);                } else {                    mStackedData.setImageResource(0);                    mStackedVoice.setImageResource(0);                    mMobileSingleGroup.setVisibility(View.VISIBLE);                    mMobileStackedGroup.setVisibility(View.GONE);                }                mMobileGroup.setContentDescription(mMobileTypeDescription                        + " " + mMobileDescription);                mMobileGroup.setVisibility(View.VISIBLE);            } else {                mMobileGroup.setVisibility(View.GONE);            }            // When this isn't next to wifi, give it some extra padding between the signals.            mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,                    0, 0, 0);            mMobile.setPaddingRelative(                    mIsMobileTypeIconWide ? mWideTypeIconStartPadding : mMobileDataIconStartPadding,                    0, 0, 0);            mMobileDark.setPaddingRelative(                    mIsMobileTypeIconWide ? mWideTypeIconStartPadding : mMobileDataIconStartPadding,                    0, 0, 0);            if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",                        (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));            mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);            mMobileRoaming.setVisibility((mRoaming && !mReadIconsFromXML)? View.VISIBLE : View.GONE);            mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE);            mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE);            mDataActivity.setVisibility(mDataActivityId != 0 ? View.VISIBLE : View.GONE);            return mMobileVisible;        }

~
mReadIconsFromXML:是定义在config.xml中config_read_icons_from_xml的值,顾名思义。
setIconForView:设置图片,mIconScaleFactor是定义在dimen.xml中的值:status_bar_icon_scale_factor;

下面是setIconForView

private void setIconForView(ImageView imageView, @DrawableRes int iconId) {        // Using the imageView's context to retrieve the Drawable so that theme is preserved.        Drawable icon = imageView.getContext().getDrawable(iconId);        if (mIconScaleFactor == 1.f) {            imageView.setImageDrawable(icon);        } else {            imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor));        }    }
~
~
下面主要来说说: setIconForView(mMobile, mMobileStrengthId);中的 mMobileStrengthId,这个特别重要。在 MobileSignalController.java中的方法notifyListeners()中调用 SignalClusterView.javasetMobileDataIndicators()方法传进来的。

下面是setMobileDataIndicators()

 @Override    public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,            int qsType, boolean activityIn, boolean activityOut, int dataActivityId,            int stackedDataId, int stackedVoiceId,String typeContentDescription,            String description, boolean isWide, int subId, boolean roaming) {        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.mRoaming = roaming;        state.mActivityIn = activityIn && mActivityEnabled && !mWifiVisible;        state.mActivityOut = activityOut && mActivityEnabled && !mWifiVisible;        state.mDataActivityId = dataActivityId;        state.mStackedDataId = stackedDataId;        state.mStackedVoiceId = stackedVoiceId;        apply();    }

~
~
MobileSignalController.java中的方法notifyListeners()

 @Override    public void notifyListeners(SignalCallback callback) {        if (mConfig.readIconsFromXml) {            generateIconGroup();        }        MobileIconGroup icons = getIcons();        String contentDescription = getStringIfExists(getContentDescription());        String dataContentDescription = getStringIfExists(icons.mDataContentDescription);        final boolean dataDisabled = mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED                && mCurrentState.userSetup;        // Show icon in QS when we are connected or data is disabled.        boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;        IconState 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 || dataDisabled;        if (SystemProperties.getBoolean("persist.vendor.radio.L_L_4G", false)                && (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA                       || mDataNetType == TelephonyManager.NETWORK_TYPE_LTE)) showDataIcon = true;        int typeIcon = (showDataIcon && mStyle == STATUS_BAR_STYLE_ANDROID_DEFAULT) ? icons.mDataType : 0;        int dataActivityId = showDataIcon && !showMobileActivity() ? icons.mActivityId : 0;        callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,                activityIn, activityOut, dataActivityId,                icons.mStackedDataIcon, icons.mStackedVoiceIcon,                dataContentDescription, description, icons.mIsWide,                mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming);    }

~

~

statusIcon.icon对应下面代码getCurrentIconId()。不明白可以看下面NetworkController.java。

 IconState statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,                getCurrentIconId(), contentDescription);

NetworkController.java中的内部类IconState
路径:\frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\policy\NetworkController.java

 public static class IconState {        public final boolean visible;        public final int icon;        public final String contentDescription;        public IconState(boolean visible, int icon, String contentDescription) {            this.visible = visible;            this.icon = icon;            this.contentDescription = contentDescription;        }        public IconState(boolean visible, int icon, int contentDescription,                Context context) {            this(visible, icon, context.getString(contentDescription));        }    }

android8.0和以前版本的区别就在这里。getCurrentIconId():

下面是8.0的代码,在8.0中是直接通过信号级别然后通过SignalDrawable代码绘制图片完成的。先对来说比往常版本要简单,但是如果要使用自己的信号样式,就是说定制信号样式。那么可以根据往常的版本来做。

 @Override    public int getCurrentIconId() {        if (mCurrentState.iconGroup == TelephonyIcons.CARRIER_NETWORK_CHANGE) {            return SignalDrawable.getCarrierChangeState(getNumLevels());        } else if (mCurrentState.connected) {            int level = mCurrentState.level;            if (mConfig.inflateSignalStrengths) {                level++;            }            if (mConfig.readIconsFromXml) {                return getIcons().mSingleSignalIcon;            } else {                return SignalDrawable.getState(level, getNumLevels(),                    false);            }        } else if (mCurrentState.enabled) {            if (mConfig.readIconsFromXml) {                return getIcons().mSbDiscState;            } else {                return SignalDrawable.getEmptyState(getNumLevels());            }        } else {            return 0;        }    }

~
~
~
以下是8.0之前的处理情况,直接获取信号级别图片,而这个图片呢是在你的res资源文件中。下面细看。
如果你要定制信号,请将readIconsFromXml 设置为true或改写代码。
readIconsFromXml 之前介绍过,在config中有定义。

 @Override    public int getCurrentIconId() {         if (mConfig.readIconsFromXml && mCurrentState.connected) {            return getIcons().mSingleSignalIcon;        } else {            return super.getCurrentIconId();        }    }

情况一、没有改写readIconsFromXml 。
return super.getCurrentIconId();
父类\frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\policySignalController.java中的getCurrentIconId()和getIcons();

  public int getCurrentIconId() {        if (mCurrentState.connected) {            return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];        } else if (mCurrentState.enabled) {            return getIcons().mSbDiscState;        } else {            return getIcons().mSbNullState;        }    }
 protected I getIcons() {        return (I) mCurrentState.iconGroup;    }

这里有点小复杂,MobileSignalController.java继承了SignalController.java。
并在generateIconGroup()对mCurrentState.iconGroup进行了赋值。
sbIcons 后面那个null是qsIcons,源生代码上状态栏下拉数据开关是显示信号的,一般看到手机上的都是开发者修改后的。
这里sbIcons使用了TelephonyIcons中的一个数组,数组中对应res中的信号图片文件。

       int[][] sbIcons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH; mCurrentState.iconGroup = new MobileIconGroup(                TelephonyManager.getNetworkTypeName(dataType),                sbIcons , null, contentDesc, 0, 0, sbDiscState, 0, discContentDesc,                dataContentDesc, dataTypeIcon, false, qsDataTypeIcon,                singleSignalIcon, stackedDataIcon, stackedVoiceIcon, dataActivityId);

~~以上这种情况是简单的定制。但是比较复杂一点的定制这样就无法完成,比如三星的部分手机,还有个品牌手机电信卡信号显示双层信号。
~~
~~

下面说说第二种情况。

 @Override    public int getCurrentIconId() {         if (mConfig.readIconsFromXml && mCurrentState.connected) {            return getIcons().mSingleSignalIcon;        } else {            return super.getCurrentIconId();        }    }

当修改过mConfig.readIconsFromXml的值。
return getIcons().mSingleSignalIcon;
mSingleSignalIcon:对应上方代码
mCurrentState.iconGroup = new MobileIconGroup(.........){........}

中的参数singleSignalIcon。
而singleSignalIcon是通过generateIconGroup()方法从TelephonyIcons中获取的。(generateIconGroup详情,请看源码MobileSignalController.java)

singleSignalIcon = TelephonyIcons.getSignalStrengthIcon(slotId, inet, level, roaming);

~
~

下面介绍一下TelephonyIcons.java

getSignalStrengthIcon():返回值就是根据传进参数获取的信号级别图片。

mSignalStrengthArray:

获取的是res中array.xml中的三维数组,数组中存放着图片名称。R.array.multi_signal_strength

  readIconsFromXml()方法中:   mSignalStrengthArray = mRes.getStringArray(R.array.multi_signal_strength);
 static int getSignalStrengthIcon(int slot, int inet, int level, boolean roaming) {        log(TAG, "getSignalStrengthIcon: " + String.format(                "slot=%d, inetCondition=%d, level=%d, roaming=%b", slot, inet, level, roaming));        String[] signalStrengthArray, selectedTypeArray;        signalStrengthArray = mRes.getStringArray(mRes.getIdentifier(!roaming ?                mSignalStrengthArray[slot] : mSignalStrengthRoamingArray[slot], null, NS));        log(TAG, String.format("signalStrengthArray.length=%d", signalStrengthArray.length));        selectedTypeArray = mRes.getStringArray(mRes.getIdentifier(                signalStrengthArray[mSelectedSignalStreagthIndex[slot]], null, NS));        log(TAG, String.format("selectedTypeArray.length=%d", selectedTypeArray.length));        String[] inetArray = mRes.getStringArray(                mRes.getIdentifier(selectedTypeArray[inet], null, NS));        log(TAG, String.format("inetArray.length=%d", inetArray.length));        return mRes.getIdentifier(inetArray[level], null, NS);    }

~
这里在说一下,如果定制自己的信号样式的时候。发现代码修改都没有问题,R.array.multi_signal_strength的修改也没有问题。咦,怎么显示的和我在R.array.multi_signal_strength写的图片不一样,这个时候,检查一下android\vendor\qcom\proprietary\qrdplus\China下面。有三个文件夹ChinaMobile、ChinaTelecom、ChinaUnicom。一般是默认使用ChinaTelecom。为什么看这里呢,因为如果这里的array.xml的R.array.multi_signal_strength中和SystemUI中的R.array.multi_signal_strength中有相同的数组名字,它会使用ChinaTelecom中array.xml中的值。有点绕,希望能看明白。
~
~
~

信号定制、信号显示流程就到这里,能看懂的其实数据流量图标显示样式也能这样修改。下次有时间再写写信号是如何上报的,WIFI开启流程等等。欢迎读者交流,相互成长。







更多相关文章

  1. Android上传图片到七牛云看这篇就够了
  2. Android studio制作计算器源代码
  3. Android Studio 单刷《第一行代码》系列 03 —— Activity 基础
  4. [置顶] [小代码]连接MySQL数据库(android,php,MySQL)
  5. Android——获取手机当前信号强度(dbm/asu值)

随机推荐

  1. Android 下压缩图片—微弱失真
  2. Android(安卓)GSM驱动模块(rild)详细分析(二
  3. Android中的表格布局TableLayout
  4. Android IMF学习笔记二
  5. Android中TextView富文本适配问题
  6. Android(安卓)AOP(三):在Android中Javassi
  7. 移动端判断是android手机还是ios手机
  8. Android ArrayAdapter的使用
  9. Android:EventBus-3 基本使用
  10. 【Android】Fragment将废弃onActivityCre