针对大屏设备优化 Android 应用的方式及相关注意事项

针对大屏设备优化 Android 应用的方式及相关注意事项

作者 / Android 团队

近年来,包括大型可折叠设备、平板电脑以及 Chromebook 等大屏 Android 设备的数量与日俱增。确保应用可以在大屏设备上为用户提供无缝体验比以往任何时候都更加重要。例如,用户希望应用能够更充分利用这些设备的更大屏幕空间。我们发现,如果应用能够支持大屏设备,便可以在这些设备上实现更出色的业务指标。

这些设备还可以在不同地方以及不同的方式来使用,而不是通常预想的手机形式。例如,可折叠设备可在桌面模式下使用,用户可能离桌面显示器有一定距离,并且可以搭配鼠标和键盘来使用大屏设备。

但伴随着这些新变化,也出现了一些需要我们注意的新问题。例如:

  • 当用户在平板电脑上使用应用时,是否可以通过双手来便捷的触到最重要的控件?

  • 用户能否通过键盘和鼠标使用应用的各项功能?

  • 无论设备如何放置,应用相机预览的方向能否与设备屏幕方向一致?

大屏应用设计和应用质量

定义什么是出色的大屏应用设计和应用质量可能很难,因为不同的应用可能会在不同的方面拥有卓越表现。您最了解自己的产品,因此请在大屏设备上亲自使用您的应用并思考如何提供最佳体验。如果您无法使用大屏设备,可以尝试在可折叠设备、桌面设备或平板电脑的虚拟设备中选择其一。

Google 在您的整个开发过程中提供资源,有助于您优化应用。如果您正在寻找设计指南,可以参阅我们精心编制的《大屏设备 Material Design 指南》等设计资源,以及可直接使用的组合布局 (如规范化布局等)。您还可以从大屏设备图库中汲取灵感,该图库提供许多不同应用的优秀示例。如果您希望通过结构化方式提高大屏应用的质量,可以参阅《大屏应用质量指南》。该指南提供直观的核对清单和一系列测试,可帮助您的应用在大屏设备上获得精彩表现。

针对大屏设备进行优化的注意事项

无论您是否已经开发出了精美的大屏应用,我们都希望重点介绍一些实用建议,以及在针对大屏设备优化应用时要避免的常见错误。

🙅 错误做法:假设应用享有对资源的专属访问权限

  • 不要假设您的应用享有对相机等硬件资源的专属访问权限。通常情况下,大屏设备上会有多款应用同时处于活跃状态,其他应用也可能会尝试访问相同的资源。

  • 因此,您应该测试您的应用与其他应用一起工作时的表现。永远不要假设某一资源在任意给定时间均可使用。

  • 对相机等硬件资源的专属访问权限
    https://developer.android.google.cn/training/camera2/camera-preview#exclusive_resources

🙆 正确做法: 妥善处理硬件访问权限

  • 在尝试使用相机等硬件资源之前,您需要先检查此类资源。请记住,硬件外设可以随时通过 USB 添加和移除

  • 如果应用在运行时无法访问给定资源,可以妥善处理失败的情况

try {
      // Attempt to use the camera
        ...
    } catch (e: CameraAccessException) {
      e.message?.let { Log.e(TAG, it) }
      // Fail gracefully if the camera isn't currently available
    }
}

🙆 正确做法: 对生命周期事件做出恰当的响应

  • 在 onPause() 期间,应用可能仍然对用户可见,特别是当屏幕上有多款应用更是如此。因此,在调用 onStop() 之前,您需要保持媒体播放和界面处于活跃状态。

🙅错误做法:在 onPause() 期间停止应用界面

override fun onPause() {
        //DON'T clean up resources here. 
        //Your app can still be visible.
        super.onPause()
    }

🙅 错误做法: 依靠 "isTablet" 等设备类型布尔值。

  • 在过去,应用的常用模式是利用屏幕宽度以创建类似 "isTablet" 这样的布尔值,从而根据运行应用的设备类型做出调整,但这种方法很不可靠。应用需要使用代理来确定设备类型是这种方法的核心问题,而此类代理容易出错。例如,应用在启动时检测到大显示屏,因此确定该设备是平板电脑。但是,如果应用为了不占据全屏而调整其窗口大小,则可能会出现异常。即使您使用的设备类型布尔值可以响应配置更改,在展开可折叠设备的情况下也会影响用户体验,因为在其他配置发生更改 (如重新折叠设备) 之前,该布尔值无法返回。

🙆 正确做法:尽量采用合适的方法取代当前使用设备类型布尔值的方法

查询与要完成的任务所需设备相关的信息。例如:

  • 如果您使用设备类型布尔值来调整布局,可以改为使用 WindowSizeClass。该内容库同时支持视图和 Jetpack Compose,以便您能够简单明确地根据预定义断点调整界面。
// androidx.compose.material3.windowsizeclass.WindowSizeClass
class MainActivity : ComponentActivity() {
  … 
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
WindowSizeClassDisplay(windowSizeClass)
  }

@Composable
fun WindowSizeClassDisplay(windowSizeClass : WindowSizeClass) {
  when (windowSizeClass.widthSizeClass)
  {
WindowWidthSizeClass.Compact   -> {  compactLayout()   }
WindowWidthSizeClass.Medium   -> {  mediumLayout()     }
WindowWidthSizeClass.Expanded   -> {  expandedLayout()   }    
  }
}
  • 视图
    https://developer.android.google.cn/reference/kotlin/androidx/compose/material3/windowsizeclass/WindowSizeClass

  • 如果您使用 isTablet 来更改面向用户的字符串 (如 "您的平板电脑"),那么您可能不需要更多信息便可以轻松解决此问题。您只需使用诸如 "您的 Android 设备" 等更通用的措辞即可。

  • 如果您使用设备类型布尔值来预测电话、蓝牙等硬件功能或资源是否存在,在尝试使用所需功能之前,您可以在运行时直接检查其是否可用,如果功能不可用,则需要妥善处理失败的情况。这种基于功能的方法可确保应用能够恰当地响应可连接或移除的外设。此外,这种方法还可以避免某种功能即使受设备支持,也依然缺失的情况。

val packageManager: PackageManager = context.packageManager
val hasTelephony = 
packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)

🙆 正确做法:尽可能使用 Jetpack CameraX

  • 显示相机预览可能极为复杂,这涉及到屏幕方向、宽高比等。在您使用 Jetpack CameraX 时,该库将为您处理尽可能多的细节:

https://developer.android.google.cn/training/camerax

🙅 错误做法: 假设相机预览的方向会与设备屏幕方向保持一致

🙅 错误做法: 假设宽高比是静态的

🙆 正确做法 : 正确声明硬件功能要求

  • 在声明应用的功能要求时,请参阅《针对大屏设备的实战宝典》中的指南。为了确保您没有不必要地限制应用的受众群体,您需要使用适用于您应用的最详尽的清单条目。
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />

🙅错误做法: 假设窗口边衬区是静态的

  • 大屏设备的屏幕经常变化,包括其 WindowInsets。因此,请勿只在应用启动时才检查边衬区,而且从不进行更改。

🙆 正确做法: 使用面向视图的 WindowInsetsListener API

  • 当边衬区发生变化时,WindowInsetsListener API 会通知应用
ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
  val insets = windowInsets.getInsets(
      WindowInsetsCompat.Type.systemBars())
  view.updateLayoutParams<MarginLayoutParams>(
      leftMargin = insets.left,
      bottomMargin = insets.bottom,
      rightMargin = insets.right,
  )
  WindowInsetsCompat.CONSUMED
}

🙆 正确做法: 在 Jetpack Compose 中使用 windowInsetsPadding Modifier

  • windowInsetsPadding Modifier 会根据给定的窗口边衬区类型动态填充。此外,该 Modifier 的多个实例可以相互通信,以避免添加重复的填充,而且这些实例会自动进行动画处理。

  • windowInsetsPadding Modifier
    https://youtu.be/mlL6H-s0nF0

🙅 错误做法: 假设设备配备触摸屏**

  • 越来越多的用户开始借助鼠标和键盘来使用 Android 应用,但有时您的应用可能无法提供触摸支持,例如当该应用在连接的显示器上显示时。您需要确保应用的所有功能都支持基本的鼠标或触控板互动:

https://www.youtube.com/watch?v=ucaSqyfpPas

🙆 正确做法: 在大屏设备上测试应用**

🙆 正确做法: 充分利用 Android Studio 中的大屏设备工具

Google I/O 大会精彩回顾

希望这些建议可作为您针对大屏设备优化应用时的良好起点。在今年的 Google I/O 大会上,我们发布了 Google 的最新资讯和创新。

版权声明

禁止一切形式的转载-禁止商用-禁止衍生 申请授权

脉脉不得语
脉脉不得语
Zhengzhou Website
Android Developer | https://androiddevtools.cn and https://androidweekly.io Funder | GDG Zhengzhou Funder & Ex Organizer | http://Toast.show(∞) Podcast Host

你已经成功订阅到 Android 开发技术周报
太棒了!接下来,完成检验以获得全部访问权限 Android 开发技术周报
欢迎回来!你已经成功登录了。
Unable to sign you in. Please try again.
成功!您的帐户已完全激活,您现在可以访问所有内容。
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.
🍗