Android 4.0 平板全屏实现(二)

来自: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.

SystemServer 在开机之后启动SystemUIService


   

?
代码片段,双击复制
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" ));


 

你可能感兴趣的:(Android 4.0 平板全屏实现(二))