Android 禁止状态栏下拉,纯应用层方法,不修改framework

禁止 Android 的状态栏(Status bar)下拉,是一个常见的定制需求,通常是通过修改系统源码实现。
如果不具备修改系统源码的条件,有没有纯应用层的替代方案呢?
有!但有局限。

这个方案参考了 Stack Overflow 上的一个问答:How to disable status bar click and pull down in Android?
思路就是:通过 WindowManager 在状态栏上添加一个相同宽高尺寸的透明遮盖层,并拦截掉相关触摸事件。

代码实现

private fun preventStatusBarExpansion(context: Context) {
        val manager = context.applicationContext
            .getSystemService(Context.WINDOW_SERVICE) as WindowManager

        val activity = context as Activity
        val localLayoutParams = WindowManager.LayoutParams()
        // 在Android 8.0及以上的系统中已经无效了~
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            localLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        } else {
            localLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
        }
        localLayoutParams.gravity = Gravity.TOP
        localLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                // this is to enable the notification to recieve touch events
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                // Draws over status bar
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN

        localLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
        val resId = activity.resources.getIdentifier("status_bar_height", "dimen", "android")
        var result = 0
        if (resId > 0) {
            result = activity.resources.getDimensionPixelSize(resId)
        }
        localLayoutParams.height = result
        localLayoutParams.format = PixelFormat.TRANSPARENT
        val view = CustomViewGroup(context)
        manager.addView(view, localLayoutParams)
    }

    class CustomViewGroup(context: Context) : ViewGroup(context) {

        override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}

        override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
            // 拦截掉触摸事件
            return true
        }
    }

注意 window 权限处理

使用时需要注意对添加 window 权限的判断。

  1. 在 Manifest 中声明 SYSTEM_ALERT_WINDOW 权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
	
manifest>
  1. 判断应用是否有在其他应用之上绘制内容的权限,如果没有,打开系统设置页面引导用户授权:
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
            	// 打开系统设置页面引导用户授权
                val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
                startActivity(intent)
            } else {
                preventStatusBarExpansion(this)
            }
        } else {
            preventStatusBarExpansion(this)
        }
    }

如果应用没有在其他应用之上绘制内容的权限,直接调用了 preventStatusBarExpansion() ,运行将抛出 permission denied for window type 的异常

Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@c70056a -- permission denied for window type 20xx
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:703)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)

全屏应用补充

如果是全屏的应用,类似视频应用的全屏观看页面那种,可以配合设置全屏的相关Flag SYSTEM_UI_FLAG_XX
来实现:

override fun setContentView(view: View?) {
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or
            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    super.setContentView(view)
}

一个小缺陷就是:仍然可以从屏幕边缘划出状态栏。不过划出后的状态栏是不能操作的,过一会儿也会自动隐藏,无伤大雅。

注意!!!适用版本:< Android 8.0

Android 8.0 Behavior Changes - Common window types for alert windows
在Android 8.0的变更描述中已经提到,使用了 SYSTEM_ALERT_WINDOW 权限的应用不能再使用下面这些type:
Android 禁止状态栏下拉,纯应用层方法,不修改framework_第1张图片
而是必须用 TYPE_APPLICATION_OVERLAY 这个新的type来代替。如果在 8.0 以上系统中继续使用上面的类型,还是会抛出 permission denied for window type 的异常。
但经过测试,将 type 设置成 TYPE_APPLICATION_OVERLAY 之后,就没有效果了,状态栏任然可以下拉~

其他方法

在定制系统中,如果有完整的系统源码,可以通过修改系统源码中的 framework 部分,然后重新编译、刷入系统镜像来实现,具体目录在:

android-aosp/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/

网上有很多指导修改的博客了,感兴趣的小伙伴可以自行研究。

你可能感兴趣的:(Android开发)