来自:http://www.eoeandroid.com/thread-246825-1-1.html
在上一篇 Android 4.0 平板全屏实现(一) 中,实现了android 4.0 上平板的导航栏的“不可见”,即所有的导航键都不显示,但导航栏所占的区域大小还在,只是显示成一个黑条,这显然不是我们想要的效果。本着打破沙锅问到底的精神,笔者继续翻源码,查阅相关资料,发现4.0 以后的导航栏都是被 SystemUI.apk 的应用控制的,它的源码所在的位置:
frameworks/base/packages/SystemUI
在SystemUI 启动流程一文中对 SystemUI 有详细的介绍,在这里就不再赘述,各位看官可移步了解之。废话不多说,开始回到正题。我们最终想要得到的效果,就是完全隐藏平板上的导航栏,没有占用任何显示空间。
SystemUI 的启动过程(平板部分)
在Android 4.0 ICS SystemUI浅析——SystemUI启动流程这篇文章中做了详细的介绍,挑几个重要的步骤来讲:
1.
01
02
03
04
05
06
07
|
static
final
void
startSystemUi(Context context) {
Intent intent =
new
Intent();
intent.setComponent(
new
ComponentName(
"com.android.systemui"
,
"com.android.systemui.SystemUIService"
));
Slog.d(TAG,
"Starting service: "
+ intent);
context.startService(intent);
}
|
2. SystemUIService 启动两个继承自 SystemUI 的 "service", 其中 导航栏的类是根据屏幕分辨率来判断的
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// Pick status bar or system bar.
IWindowManager wm = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
try
{
Log.d(TAG,
"*** canStatusBarHide = "
+ wm.canStatusBarHide() +
" ***"
);
SERVICES[
0
] = wm.canStatusBarHide()
? R.string.config_statusBarComponent
: R.string.config_systemBarComponent;
}
catch
(RemoteException e) {
Slog.w(TAG,
"Failing checking whether status bar can hide"
, e);
}
final
int
N = SERVICES.length;
mServices =
new
SystemUI[N];
for
(
int
i=
0
; i<N; i++) {
Class cl = chooseClass(SERVICES<i>);
Slog.d(TAG,
"loading: "
+ cl);
try
{
mServices<i> = (SystemUI)cl.newInstance();
}
catch
(IllegalAccessException ex) {
throw
new
RuntimeException(ex);
}
catch
(InstantiationException ex) {
throw
new
RuntimeException(ex);
}
mServices<i>.mContext =
this
;
Slog.d(TAG,
"running: "
+ mServices<i>);
mServices<i>.start();
}
</i></i></i></i></i>
|
如果是平板(wm.canStatusBarHide() = false)则启用 com.android.systemui.statusbar.tablet.TabletStatusBar,这也是我们接下来研究的重点。因为手机已经可以做到完全全屏。
继续跟踪,发现 PhoneStatusBar 和 TabletStatusBar 都继承自 StatusBar, 在 StatusBar 中有一个非常重要的抽象函数 makeStatusBarView(),没错,看名字就可以知道,就是它初始化了SystemUI的主要部分(手机的是 NavigationBar 和 StatusBar, 平板的是 Combination bar). 不管是手机还是平板,都是通过这个函数来生成自己的导航栏,被 StatusBar 调用,具体代码如下:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
public
void
start() {
// First set up our views and stuff.
View sb = makeStatusBarView();
// Connect in to the status bar manager service
StatusBarIconList iconList =
new
StatusBarIconList();
ArrayList<IBinder> notificationKeys =
new
ArrayList<IBinder>();
ArrayList<StatusBarNotification> notifications =
new
ArrayList<StatusBarNotification>();
mCommandQueue =
new
CommandQueue(
this
, iconList);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
int
[] switches =
new
int
[
7
];
ArrayList<IBinder> binders =
new
ArrayList<IBinder>();
try
{
mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
switches, binders);
}
catch
(RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
disable(switches[
0
]);
setSystemUiVisibility(switches[
1
]);
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
]);
setHardKeyboardStatus(switches[
5
] !=
0
, switches[
6
] !=
0
);
// Set up the initial icon state
int
N = iconList.size();
int
viewIndex =
0
;
for
(
int
i=
0
; i<N; i++) {
StatusBarIcon icon = iconList.getIcon(i);
if
(icon !=
null
) {
addIcon(iconList.getSlot(i), i, viewIndex, icon);
viewIndex++;
}
}
// Set up the initial notification state
N = notificationKeys.size();
if
(N == notifications.size()) {
for
(
int
i=
0
; i<N; i++) {
addNotification(notificationKeys.get(i), notifications.get(i));
}
}
else
{
Log.wtf(TAG,
"Notification list length mismatch: keys="
+ N
+
" notifications="
+ notifications.size());
}
// Put up the view
final
int
height = getStatusBarHeight();
mStatusBarHeight = height;
final
WindowManager.LayoutParams lp =
new
WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
height,
WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.OPAQUE);
// the status bar should be in an overlay if possible
final
Display defaultDisplay
= ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
if
(ActivityManager.isHighEndGfx(defaultDisplay)) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
lp.gravity = getStatusBarGravity();
lp.setTitle(
"StatusBar"
);
lp.packageName = mContext.getPackageName();
lp.windowAnimations = R.style.Animation_StatusBar;
mStatusBarLayoutParams = lp;
mStatusBarView = sb;
WindowManagerImpl.getDefault().addView(sb, lp);
if
(SPEW) {
Slog.d(TAG,
"Added status bar view: gravity=0x"
+ Integer.toHexString(lp.gravity)
+
" icons="
+ iconList.size()
+
" disabled=0x"
+ Integer.toHexString(switches[
0
])
+
" lights="
+ switches[
1
]
+
" menu="
+ switches[
2
]
+
" imeButton="
+ switches[
3
]
);
}
mDoNotDisturb =
new
DoNotDisturb(mContext);
}
|
通过上面这段代码,可以看到,在调用makeStatusBarView生成View sb 之后,通过 WindowManagerImpl.getDefault().addView(sb, lp); 将其显示到屏幕之上。追踪到这里,我想大多数同学应该知道怎么去做导航栏的隐藏了。。
没错,很简单,调用 WindowManagerImpl.getDefault().updateView(sb, lp) 就可以搞定。如果是要导航栏始终不可见,将其高度设置为0就可以,但是我们还要将其还原。还得update回来。。
接下来的问题,就是要找个合适的位子,添加上我们的这段代码,在什么地方调用updateView才是最合适的呢?
全屏实现(4.0平板)
由于 StatusBar 是被 SystemUIService 调用的,而且仅仅执行 start() 函数,而 StatusBar 是个普通的Java类,没有Context上下文环境,不具备接收事件的能力(这里可能描述得不够清楚,主要就是说应用层发送的广播啊,startService之类的方法都不被StatusBar接收到。因为我们需要在应用层调用相关cmd来控制导航栏的显示/隐藏)。
我们会发现,SystemUIService 是个普通的 service 能够使用到组件之间的通信之中,并且StatusBar也是在这里被调起来的。开始动工吧,能想到的最简单的调用顺序就是 :
1. 扩展 SystemUI, 添加多一个抽象函数 onReceive(String action)
2. 在 SystemUIService 中注册一个 BroadcastReceiver 监听我们定制的两个 action (隐藏/显示)
3. 在 BroadcastReceive 函数中,调用 SystemUI 的 onReceive函数
4. 在 StatusBar 的 onReceive 函数中处理导航栏的显示/隐藏。
在 SystemUI 中添加抽象函数(frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUI.java)
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public
abstract
class
SystemUI {
public
Context mContext;
public
abstract
void
start();
<font color=
"#00f0f0"
>
public
abstract
void
onReceive(String action);</font>
protected
void
onConfigurationChanged(Configuration newConfig) {
}
public
void
dump(FileDescriptor fd, PrintWriter pw, String[] args) {
}
}
|
在 SystemUIService 中注册 BroadcastReceiver (frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
@Override
public
void
onCreate() {
// Pick status bar or system bar.
IWindowManager wm = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
try
{
Log.d(TAG,
"*** canStatusBarHide = "
+ wm.canStatusBarHide() +
" ***"
);
SERVICES[
0
] = wm.canStatusBarHide()
? R.string.config_statusBarComponent
: R.string.config_systemBarComponent;
}
catch
(RemoteException e) {
Slog.w(TAG,
"Failing checking whether status bar can hide"
, e);
}
final
int
N = SERVICES.length;
mServices =
new
SystemUI[N];
for
(
int
i=
0
; i<N; i++) {
Class cl = chooseClass(SERVICES<i>);
Slog.d(TAG,
"loading: "
+ cl);
try
{
mServices<i> = (SystemUI)cl.newInstance();
}
catch
(IllegalAccessException ex) {
throw
new
RuntimeException(ex);
}
catch
(InstantiationException ex) {
throw
new
RuntimeException(ex);
}
mServices<i>.mContext =
this
;
Slog.d(TAG,
"running: "
+ mServices<i>);
mServices<i>.start();
}
<font color=
"#00f0f0"
>
// register broadcast receiver
IntentFilter intentFilter =
new
IntentFilter();
intentFilter.addAction(ACTION_DISPLAY_STATUS_BAR);
intentFilter.addAction(ACTION_HIDE_STATUS_BAR);
registerReceiver(mStatusBarReceiver, intentFilter);</font>
}</i></i></i></i></i>
|
在 onReceive 中分发事件(frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
BroadcastReceiver mStatusBarReceiver =
new
BroadcastReceiver() {
@Override
public
void
onReceive(Context context, Intent intent) {
if
(intent !=
null
&& !TextUtils.isEmpty(intent.getAction())) {
String action = intent.getAction();
final
int
N = SERVICES.length;
for
(
int
i =
0
; i < N; i++) {
Slog.d(TAG,
"invoke: "
+ mServices<i> +
"'s onReceive()"
);
<font color=
"#00f0f0"
> mServices<i>.onReceive(action);</i></font><i>
}
}
}
};</i></i>
|
在StatusBar中处理消息(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
public
abstract
class
StatusBar
extends
SystemUI
implements
CommandQueue.Callbacks {
static
final
String TAG =
"StatusBar"
;
private
static
final
boolean
SPEW =
false
;
protected
CommandQueue mCommandQueue;
protected
IStatusBarService mBarService;
// Up-call methods
protected
abstract
View makeStatusBarView();
protected
abstract
int
getStatusBarGravity();
public
abstract
int
getStatusBarHeight();
public
abstract
void
animateCollapse();
private
DoNotDisturb mDoNotDisturb;
<font color=
"#00f0f0"
>
private
View mStatusBarView;
private
int
mStatusBarHeight;
private
WindowManager.LayoutParams mStatusBarLayoutParams;</font>
public
void
start() {
// First set up our views and stuff.
View sb = makeStatusBarView();
// Connect in to the status bar manager service
StatusBarIconList iconList =
new
StatusBarIconList();
ArrayList<IBinder> notificationKeys =
new
ArrayList<IBinder>();
ArrayList<StatusBarNotification> notifications =
new
ArrayList<StatusBarNotification>();
mCommandQueue =
new
CommandQueue(
this
, iconList);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
int
[] switches =
new
int
[
7
];
ArrayList<IBinder> binders =
new
ArrayList<IBinder>();
try
{
mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
switches, binders);
}
catch
(RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
disable(switches[
0
]);
setSystemUiVisibility(switches[
1
]);
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
]);
setHardKeyboardStatus(switches[
5
] !=
0
, switches[
6
] !=
0
);
// Set up the initial icon state
int
N = iconList.size();
int
viewIndex =
0
;
for
(
int
i=
0
; i<N; i++) {
StatusBarIcon icon = iconList.getIcon(i);
if
(icon !=
null
) {
addIcon(iconList.getSlot(i), i, viewIndex, icon);
viewIndex++;
}
}
// Set up the initial notification state
N = notificationKeys.size();
if
(N == notifications.size()) {
for
(
int
i=
0
; i<N; i++) {
addNotification(notificationKeys.get(i), notifications.get(i));
}
}
else
{
Log.wtf(TAG,
"Notification list length mismatch: keys="
+ N
+
" notifications="
+ notifications.size());
}
// Put up the view
final
int
height = getStatusBarHeight();
<font color=
"#00f0f0"
> mStatusBarHeight = height;</font>
final
WindowManager.LayoutParams lp =
new
WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
height,
WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.OPAQUE);
// the status bar should be in an overlay if possible
final
Display defaultDisplay
= ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
if
(ActivityManager.isHighEndGfx(defaultDisplay)) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
lp.gravity = getStatusBarGravity();
lp.setTitle(
"StatusBar"
);
lp.packageName = mContext.getPackageName();
lp.windowAnimations = R.style.Animation_StatusBar;
<font color=
"#00f0f0"
> mStatusBarLayoutParams = lp;
mStatusBarView = sb;</font>
WindowManagerImpl.getDefault().addView(sb, lp);
if
(SPEW) {
Slog.d(TAG,
"Added status bar view: gravity=0x"
+ Integer.toHexString(lp.gravity)
+
" icons="
+ iconList.size()
+
" disabled=0x"
+ Integer.toHexString(switches[
0
])
+
" lights="
+ switches[
1
]
+
" menu="
+ switches[
2
]
+
" imeButton="
+ switches[
3
]
);
}
mDoNotDisturb =
new
DoNotDisturb(mContext);
}
protected
View updateNotificationVetoButton(View row, StatusBarNotification n) {
View vetoButton = row.findViewById(R.id.veto);
if
(n.isClearable()) {
final
String _pkg = n.pkg;
final
String _tag = n.tag;
final
int
_id = n.id;
vetoButton.setOnClickListener(
new
View.OnClickListener() {
public
void
onClick(View v) {
try
{
mBarService.onNotificationClear(_pkg, _tag, _id);
}
catch
(RemoteException ex) {
// system process is dead if we're here.
}
}
});
vetoButton.setVisibility(View.VISIBLE);
}
else
{
vetoButton.setVisibility(View.GONE);
}
return
vetoButton;
}
<font color=
"#00f0f0"
>
@Override
public
void
onReceive(String action) {
Log.d(TAG,
"*** onReceive(), action = "
+ action +
" ***"
);
if
(SystemUIService.ACTION_DISPLAY_STATUS_BAR.equals(action)) {
mStatusBarLayoutParams.height = mStatusBarHeight;
WindowManagerImpl.getDefault().updateViewLayout(mStatusBarView,
mStatusBarLayoutParams);
}
else
if
(SystemUIService.ACTION_HIDE_STATUS_BAR.equals(action)) {
mStatusBarLayoutParams.height =
0
;
WindowManagerImpl.getDefault().updateViewLayout(mStatusBarView,
mStatusBarLayoutParams);
}</font>
}
}
|
至此,在SystemUI.apk 中的改动全部完成。
还没说完……在应用端 通过发送 broadcast 就可以控制导航栏的显示/隐藏了
具体调用如下:
全屏
01
|
sendBroadcast(
new
Intent(
"com.android.action.HIDE_BAR"
));
|
非全屏
01
|
sendBroadcast(
new
Intent(
"com.android.action_DISPLAY_BAR"
));
|