你知道屏幕上存在多少个 Window 吗

本系列为小说《逆袭西二旗》的技术讲解,用于详细说明剧情里涉及的开发细节。

什么是 Window

Window 是屏幕上显示的 Activity 或其他 UI 组件的所有视图的容器。它是视图层级中的顶层元素,也是连接应用 UI 和屏幕的桥梁。

每个 ActivityDialogToast 都与一个 Window 对象绑定,该对象为其包含的视图提供布局参数、动画和过渡效果。

面试问题

当显示一个带有简单布局的 Activity 时,屏幕上存在多少个 Window

它们是什么?

核心特性

Window 类提供了以下关键功能:

  1. DecorView :每个 Window 包含一个 DecorView,作为视图层级的根视图。它通常包含状态栏、导航栏和应用的内容区域。
  2. 布局参数Window 通过布局参数定义视图的排列和显示方式,如大小、位置和可见性,这些参数可以通过编程方式自定义。
  3. 输入事件处理Window 处理触摸手势、按键按下等输入事件,并将其分发给相应的视图。
  4. 动画与过渡Window 支持打开、关闭或在屏幕间切换时的动画效果。
  5. 系统装饰Window 可以显示或隐藏状态栏、导航栏等系统 UI 元素。

Window 管理

WindowWindowManager(一种系统服务)管理,负责添加、移除或更新窗口。这确保了不同窗口(如应用窗口、系统对话框和通知)能够在设备上正确共存和交互。

使用场景

  1. 自定义 Activity 窗口 :可以使用 getWindow() 方法修改 Activity 窗口的行为,例如隐藏状态栏或更改背景:

    kotlin 复制代码
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
    window.setBackgroundDrawable(ColorDrawable(Color.BLACK))
  2. 创建 DialogDialog 基于独立的 Window 实现,因此可以悬浮在其他 UI 元素之上。

  3. 使用悬浮窗 :通过 Window 可以创建悬浮窗,例如系统级功能或抬头通知,需要使用 TYPE_APPLICATION_OVERLAY 类型。

  4. 处理多窗口模式:Android 支持多窗口模式,可实现分屏或画中画等功能。

总结

Window 类是 Android 中的核心组件,为应用的视图和 UI 元素提供顶层容器。它允许自定义应用与屏幕、系统 UI 和用户输入的交互方式,而 WindowManager 对它的管理则确保了与 Android 环境的无缝集成。

进阶:什么是 WindowManager

WindowManager 是 Android 系统提供的服务,负责管理屏幕上所有窗口的位置、大小和显示方式。它是应用与系统之间的交互接口,允许应用创建、修改或移除窗口。在 Android 中,窗口可以是全屏 Activity,也可以是悬浮窗。

其核心职责是:

  • 管理系统中窗口的层级关系,确保窗口按照 Z 轴顺序正确显示。
  • 处理焦点变化、触摸事件和窗口动画等交互逻辑。

常见使用场景有:

  • 添加自定义 View :在应用的标准 Activity 之外显示自定义视图,如悬浮小部件或系统悬浮窗。
  • 修改现有窗口:更新已有窗口的属性,如调整大小、位置或透明度。
  • 移除窗口 :通过 removeView() 方法以编程方式移除窗口。

通过 Context.getSystemService(Context.WINDOW_SERVICE) 获取 WindowManager 服务。举个例子,以下是使用 WindowManager 向屏幕添加悬浮视图的示例:

kotlin 复制代码
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val floatingView = LayoutInflater.from(context).inflate(R.layout.floating_view, null)

val params = WindowManager.LayoutParams(
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // 悬浮窗类型
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // 窗口标记
    PixelFormat.TRANSLUCENT // 像素格式
)

windowManager.addView(floatingView, params)

说明:

  • TYPE_APPLICATION_OVERLAY 允许视图显示在其他应用的上层。
  • FLAG_NOT_FOCUSABLE 使窗口不接收用户输入,除非额外指定交互属性。

对于系统悬浮窗等特殊窗口,需要申请 SYSTEM_ALERT_WINDOW 权限。从 Android 8.0(API 26)开始,系统对悬浮窗的限制更为严格,以保障安全。

总而言之,WindowManager 是管理 Android 窗口的基础 API。它允许开发者在标准 Activity 生命周期之外,以编程方式添加、更新和移除视图。合理使用 WindowManager 可以实现悬浮小部件或悬浮窗等高级功能,但需要仔细考虑权限和用户体验。

进阶:PopupWindow

PopupWindow 是用于在现有布局上方显示悬浮弹窗的 UI 组件。

Dialog 不同,它不需要遮挡整个屏幕,也不强制要求用户交互来关闭,因此更灵活,常用于实现菜单、提示框等临时或上下文相关的 UI。

PopupWindow 具有以下特性:

  • 显示可自定义的悬浮视图。
  • 不需要使屏幕变暗或被遮挡,允许用户与弹窗后的其他 UI 组件交互。
  • 支持自定义布局、动画和关闭行为。
  • 支持点击外部区域关闭和焦点控制,以提供流畅的用户体验。

以下是创建并显示 PopupWindow 的示例代码:

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 填充弹窗布局
        val popupView = layoutInflater.inflate(R.layout.popup_layout, null)

        // 创建 PopupWindow
        val popupWindow = PopupWindow(
            popupView,
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT,
            true // 可聚焦
        )

        // 绑定按钮点击事件
        val button = findViewById<Button>(R.id.button)
        button.setOnClickListener {
            // 显示锚定到按钮的弹窗
            popupWindow.showAsDropDown(button)
        }
    }
}

PopupWindow 有个实现起来比较麻烦的功能,点击区域外消失,下面给出一种解决方案:

Kotlin 复制代码
val popupWindow = PopupWindow(contentView, width, height).apply {
    // 1. 设置可获取焦点
    isFocusable = true
    
    // 2. 设置点击外部可触发
    isOutsideTouchable = true
    
    // 3. 设置背景(关键!)
    setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}

虽然 PopupWindow 看起来像是继承自 Window,但它实际上是一个独立的类,内部通过 WindowManager 在屏幕上添加和移除窗口。

总而言之,PopupWindow 是在 Android 中显示悬浮 UI 元素的多功能工具。它支持自定义布局和灵活定位,非常适合创建上下文菜单、提示框和临时弹窗,能在不打断主应用流程的情况下提升用户交互体验。

进阶:如何回答上面那个面试题

在标准的、现代化的 Android 设备上(带有屏幕内虚拟按键/手势条,且未做全屏隐藏处理),显示一个带简单布局的 Activity 时,屏幕上通常存在 3 个 Window

这 3 个 Window 分别是

  1. 状态栏 Window (Status Bar):

    • 属于 SystemUI 进程管理的系统级 Window
    • 位于屏幕顶部,负责显示时间、电量、通知图标等。
  2. 导航栏 Window (Navigation Bar):

    • 同样属于 SystemUI 管理的系统级 Window
    • 位于屏幕底部,负责显示返回、Home、最近任务三大按键(或者是全面屏底部的手势小白条)。如果是早期的物理按键手机,可能没有这个 Window。
  3. ActivityWindow (Application Window):

    • 属于我们自己应用进程的 Window
    • 它是 PhoneWindow 的实例,内部包裹着 DecorView。我们在 Activity 中写的那个"简单布局"(通过 setContentView 传入的),就是挂载在这个 Window 的 DecorView 之中的。"

如何渲染网页

WebView 是 Android 中用于直接显示和交互网页内容的多功能组件,相当于嵌入在应用中的迷你浏览器。它支持渲染网页、加载 HTML 内容甚至运行 JavaScript。

为了在设备上安全地使用最新的 WebView 能力,建议使用 AndroidX Webkit 库,它提供了向后兼容的 API,确保无论设备的 Android 版本如何,都能访问现代 Web 特性。

面试问题

如何有效处理 WebView 导航,防止用户点击外部链接时离开应用?

初始化 WebView

你可以在布局文件中添加 WebView,或者通过代码动态创建。

例如,在 XML 布局中添加 WebView

xml 复制代码
<!-- activity_main.xml -->
<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

也可以通过代码创建:

kotlin 复制代码
val webView = WebView(this)
setContentView(webView)

加载网页

使用 loadUrl() 方法加载网页,并确保在 AndroidManifest.xml 中添加网络权限:

kotlin 复制代码
val webView: WebView = findViewById(R.id.webview)
webView.loadUrl("https://www.example.com")
xml 复制代码
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />

启用 JavaScript

如果网页需要运行 JavaScript,可以通过修改 WebSettings 来启用:

kotlin 复制代码
val webSettings = webView.settings
webSettings.javaScriptEnabled = true

自定义 WebView 行为

WebView 提供了多种方法来处理事件和自定义行为:

  • 拦截页面导航 :使用 WebViewClient 处理页面内导航,避免跳转到外部浏览器。
kotlin 复制代码
webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
        view.loadUrl(url)
        return true
    }
}
  • 处理下载 :使用 DownloadListener 管理 WebView 发起的文件下载。
kotlin 复制代码
webView.setDownloadListener { url, userAgent, contentDisposition, mimeType, contentLength ->
    // 在此处处理文件下载
}
  • 运行 JavaScript :通过 evaluateJavascriptloadUrl("javascript:...") 注入 JavaScript 代码。
kotlin 复制代码
webView.evaluateJavascript("document.body.style.backgroundColor = 'red';") { result ->
    Log.d("WebView", "JavaScript 执行结果: $result")
}

JavaScript 与 Android 代码绑定

集成 JavaScript 和 Android 代码可以增强混合式 Web 应用,允许客户端脚本与 Android 原生特性无缝交互。这对于在 WebView 中运行的 Web 应用尤其有用,让 JavaScript 可以利用 Android 专属功能。

例如,你可以触发原生 Android 弹窗或 Toast 消息,而不是依赖 JavaScript 的 alert() 函数。

要实现这种交互,使用 addJavascriptInterface() 方法。该方法将一个 Java 对象绑定到 WebView 中的 JavaScript 上下文,通过指定接口名称,使其方法可被 JavaScript 访问。

绑定 JavaScript 到 Android 的示例

kotlin 复制代码
// 定义接口并绑定到 WebView
class WebAppInterface(private val context: Context) {
    // 暴露给 JavaScript 的方法
    @JavascriptInterface
    fun showToast(message: String) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
    }
}

// 初始化 WebView 并绑定接口
val webView: WebView = findViewById(R.id.webview)
webView.addJavascriptInterface(WebAppInterface(this), "Android")

在本示例中,WebAppInterface 类暴露了一个带有 @JavascriptInterface 注解的 showToast 方法。addJavascriptInterface() 方法将此接口以名称 "Android" 绑定到 WebView。现在,WebView 中运行的 JavaScript 可以调用此方法。

在 HTML 端,以下脚本演示了如何从 JavaScript 调用 showToast 方法:

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>WebView Example</title>
</head>
<body>
    <button onclick="callAndroidFunction()">点击我</button>
    <script type="text/javascript">
        function callAndroidFunction() {
            Android.showToast("来自 JavaScript 的问候!");
        }
    </script>
</body>
</html>

当按钮被点击时,JavaScript 函数 callAndroidFunction() 调用 Android 接口中的 showToast 方法,显示原生 Toast 消息。

虽然 addJavascriptInterface() 是一个实用功能,但它也带来了显著的安全风险。如果你的 WebView 加载不受信任或动态的 HTML 内容,攻击者可能注入恶意 JavaScript 来利用暴露的接口,并潜在地执行未授权的 Android 代码。

安全问题

  • 除非必要,否则避免启用 JavaScript,以降低安全风险(当然,这个其实是不可能,有一条比较通用的做法,本 App 只运行本 App 相关的网页)。
  • 谨慎使用 setAllowFileAccess()setAllowFileAccessFromFileURLs(),防止未授权的文件访问。
  • 始终验证用户输入并清理 URL,防止跨站脚本攻击(XSS)或 URL 伪造。
  • 确保通过 @JavascriptInterface 暴露的方法不会引入安全漏洞。

总结

WebView 是在 Android 应用中渲染网页内容的基础组件。通过使用 WebViewClient 自定义其行为,并在需要时启用 JavaScript 等功能,你可以为用户创造流畅的体验。然而,在将网页内容集成到应用中时,始终要考虑安全和性能影响。

AppCompat 到底有什么用

AppCompat 库是 Android Jetpack 套件的一部分,旨在帮助开发者在旧版 Android 系统上保持兼容性。它允许在应用中实现现代特性和设计模式,同时确保与早期 Android 版本的向后兼容。这个库对面向多种设备(运行不同 Android 版本)的开发者尤其有益。

Jetpack

这里插播一条------那么什么是 Jetpack 呢?

Jetpack 是 Google 提供的一系列库和工具的集合,旨在帮助 Android 开发者更高效、更可维护地构建应用。这些库解决了生命周期管理、UI 导航、后台任务和数据存储等常见开发问题,并且与现代 Android 开发实践无缝集成,简化了应用创建的许多环节,同时遵循最佳实践。

Jetpack 中的库是模块化的,开发者可以只选择项目所需的特定组件,例如用于状态管理的 ViewModel、处理屏幕过渡的 Navigation ,以及管理本地数据库的 Room,而无需绑定到单一的庞大框架。

通过解决常见和复杂的挑战,Jetpack 提供了一套补充 Android 核心能力的工具。

开发者并非必须使用 Jetpack 库,在某些场景下,替代方案或自定义实现可能更合适。理解 Jetpack 的组件及其在 Android 生态系统中的定位,能帮助开发者就项目采用哪些工具做出明智的决策。

最新的 NavigationRoom 已经完全能够跨平台了,这两个 Jetpack 库以后将不再局限于 Android 了

面试问题

AppCompat 库如何在旧版 Android 上启用 Material Design 支持?

有哪些关键的 UI 组件从中受益?

AppCompat 的特性

AppCompat 库提供了一系列向后兼容的组件和工具,以增强应用功能和设计一致性。以下是其主要特性:

  • UI 组件的向后兼容性 :该库引入了现代 UI 组件,如 AppCompatActivity(它扩展了 FragmentActivity),确保与旧版 Android 的兼容性。这让开发者可以在运行旧版 Android 的设备上使用操作栏等特性。
  • Material Design 支持AppCompat 允许在运行旧版 Android 的设备上采用 Material Design 原则。这包括 AppCompatButtonAppCompatTextView 等控件,它们会根据设备的 API 级别自动调整外观和行为。
  • 主题与样式支持 :借助 AppCompat ,你可以使用 Theme.AppCompat 等主题,确保在所有 API 级别上外观保持一致。这些主题带来了现代样式能力,例如对矢量图的支持,这在旧版 Android 上原本是没有的。
  • 动态特性支持:该库提供了动态资源加载和矢量图支持,让开发者能更高效地实现现代设计元素,同时保持向后兼容性。

为什么要用 AppCompat

使用 AppCompat 库的主要原因是确保现代 Android 特性和 UI 组件在所有支持的 API 级别上都能一致运行。它降低了维护兼容性的复杂度,让开发者能够专注于构建现代且功能丰富的应用,同时仍能支持运行旧版 Android 的设备。

我们用 AppCompatActivity 举个例子:

kotlin 复制代码
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

在这个示例中,AppCompatActivity 确保该 Activity 可以在运行旧版 Android 的设备上使用操作栏等特性。

总结

AppCompat 库对于构建能兼容多种设备和 API 级别的 Android 应用非常有用。通过提供向后兼容的组件、Material Design 支持和一致的主题化,它简化了开发流程,同时提升了旧设备上的用户体验。

什么是 Material Design Components (MDC)

Material Design Components (MDC) 是一套基于 Google Material Design 指南的可定制 UI 组件和工具。这些组件旨在提供一致、友好的用户界面,同时允许开发者根据应用的品牌和设计需求自定义其外观和行为。

MDC 是 Material Components for Android (MDC-Android) 库的一部分,它能无缝集成到 Android 项目中,确保现代设计原则得到有效实施。

面试问题

MDC 中的 Material Theming 如何帮助在整个应用中维持设计一致性?

Material Design Components 的核心特性

  • Material 主题化MDC 通过 Material Theming 支持主题定制,允许开发者在全局或组件级别自定义排版、形状和颜色。这能轻松让 UI 与品牌标识保持一致,同时在整个应用中维持设计的统一性。
  • 预制 UI 组件MDC 提供了一系列即用型 UI 组件,如按钮、卡片、应用栏、导航抽屉、芯片等。这些组件针对可访问性、性能和响应性进行了优化。
  • 动画支持 :Material Design 强调运动与过渡效果。MDC 内置了对动画的支持,包括共享元素过渡、涟漪效果和视觉反馈,提升了用户交互体验。
  • 深色模式支持:该库包含了轻松实现深色模式的工具,允许开发者定义亮色和暗色主题,同时确保视觉一致性。
  • 可访问性MDC 遵循可访问性标准,提供了更大的触摸目标、语义标签和适当的焦点管理等特性,确保 UI 对所有用户都具有包容性。

以下是如何使用 MDC 库中的 MaterialButton 的示例:

xml 复制代码
<com.google.android.material.button.MaterialButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Click Me"
    app:cornerRadius="8dp"
    app:icon="@drawable/ic_example"
    app:iconPadding="8dp" />

在这里,MaterialButton 控件通过圆角、图标和内边距进行了自定义,以符合 Material Design 原则。

总结

Material Design Components 让开发者能够创建符合 Google Material Design 指南的现代、一致且视觉吸引力强的用户界面。借助主题化、预制控件、动画支持和可访问性工具等特性,MDC 简化了实现高质量 UI 的过程,同时确保在不同设备和屏幕尺寸上的适应性和响应性。

爱与恨

国内开发者对 MDC 可谓又爱又恨。

在于其开箱即用、设计规范统一,配合 Jetpack Compose 或传统 View 体系能快速搭建高保真原型,极大提升个人或小团队的开发效率。

则源于现实落差:国内大厂普遍推行自研 UI 规范(如阿里 Ant Design Mobile、腾讯 QMUI),产品设计高度定制化,几乎不用原生 MDC 风格。开发者即便掌握 MDC ,在实际工作中也难有用武之地,反而需额外学习各厂私有组件库,徒增学习成本与技术栈负担。久而久之,MDC 成了"练手神器",却难登企业级应用的"大雅之堂"。

相关推荐
Android技术之家2 小时前
Android Studio 专属AI智能体+Skills完整版提示词(可直接复制使用)
android·ide·人工智能·android studio
xiegwei2 小时前
Android 原生项目添加 Flutter Activity 示例及常见报错解决方案
android·flutter
于慨2 小时前
Flutter Android gradle 8.14 file lock, incompatibility issue
android·flutter·issue
千百元2 小时前
HBuildX 打包成apk完整过程
android
流星白龙11 小时前
【MySQL】7.MySQL基本查询(2)
android·mysql·adb
mldlds12 小时前
MySQL加减间隔时间函数DATE_ADD和DATE_SUB的详解
android·数据库·mysql
智算菩萨14 小时前
MP3音频编码原理深度解析与Python全参数调优实战:从心理声学模型到LAME编码器精细控制
android·python·音视频
studyForMokey15 小时前
【Android面试】Activity生命周期专题
android·面试·职场和发展
chehaoman16 小时前
MySQL的索引
android·数据库·mysql