WearOS Offload模式下的表盘开发

  WearOS手表offload模式下的表盘渲染,是通过BG绘制的。

一.表盘进入offload的条件是手表处于微光模式且表盘是Decomposable的。

WearOS Offload模式下的表盘开发_第1张图片

为了满足上述条件,需要表盘开发中做如下配置:

  a.manifest中为DecompositionWatchFaceService增加mera-data

        

b.updateDecomposition。

     必须调用updateDecomposition来生成一个有效的WatchFaceDecomposition,否则系统被认为普通表盘。常用方法是

    1.直接继承DecompositionWatchFaceService实现buildDecomposition方法。

   2.也可自定义CanvasWatchFaceService,但必须实现一个可显示的WatchFaceDecomposition且通过updateDecomposition通知系统。

c.资源要求。

    offload模式下表盘显示的字体、字符串都必须用"图片抠图"的形式呈现,例如想显示数字1,必须从字体资源(FontComponent)中扣出数字1的图片,字符串显示也是先转成drawable再通过BG绘制,因此涉及到表盘翻译的字符串,建议都用复杂数据显示,因为复杂数据本身包含drawable,home可将该drawable转成ImageComponent方便表盘展示。资源中图片格式必须是点阵图,且必须是RGB332,整个表盘最多能显示16种颜色,不支持扛锯齿,防烧屏机制在该模式下继续生效。

二.如何确认手表是否成功进入offload模式。

除了看AmbientService、SidekickService的log外,还可以直接看系统power wake状态,如:

# dumpsys power | grep -i wake
    no_cached_wake_locks=true
  mWakefulness=Dozing
  mWakefulnessChanging=false
  mWakeLockSummary=0x40
  mLastWakeTime=45074118 (11793 ms ago)
  mHoldingWakeLockSuspendBlocker=false
  mWakeUpWhenPluggedOrUnpluggedConfig=true
  mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=false
  mDrawWakeLockOverrideFromSidekick=true
  mDoubleTapWakeEnabled=false
Wake Locks: size=1
  DOZE_WAKE_LOCK                 'DreamManagerService' ACQ=-1s396ms (uid=1000 pid=1752)
  PowerManagerService.WakeLocks: ref count=0
  mGravitySensor={Sensor name="gravity  Non-wakeup", vendor="qualcomm", version=1, type=9, maxRange=1.0, resolution=0.1, power=0.515, minDelay=20000}

当SidekickService.mShouldControlDisplay为true时,该值也为true,保持屏幕处于状态STATE_DOZE_SUSPEND:The display is dozing in a suspended low power state; it is still on but the CPU is not updating it。

SidekickService会计算出buildDecomposition()返回的WatchFaceDecomposition中所用的Components,并解析每个Components
的显示区域,最后调用replaceWatchFaceComponents交给home更新。

03-09 17:18:45.329  1752  1911 I SidekickService: endDisplayLocked()
03-09 17:18:45.359  1752  1911 D SidekickService:    ... endDisplay returns void
03-09 17:18:51.765  2390  4681 I SidekickManager: clearWatchFace called:
03-09 17:18:51.765  1752  1775 D SidekickService: resetLocked()
03-09 17:18:51.989  2390  4681 I SidekickManager: sendWatchFaceImpl(): watchFace = com.google.android.clockwork.decomposablewatchface.WatchFaceDecomposition@6673f0c forTWM = false
03-09 17:18:52.099  1752  1775 D SidekickService: sendWatchFaceComponents called: watchFace = com.google.android.clockwork.decomposablewatchface.WatchFaceDecomposition@468521shouldReplace = true
03-09 17:18:52.100  1752  1775 D SidekickService: sendWatchFaceLocked(): shouldReplace = true, mSidekickIsControlling = false, 8 images, 0 fonts, 0 numbers, 0 proportionalFonts, 0 strings
03-09 17:18:52.101  1752  1775 D SidekickService: ISidekickGraphics#beginResources()...
03-09 17:18:52.122  1752  1775 D SidekickServiceUtils: bitmapFromDrawable: Returning bitmap directly
03-09 17:18:52.208  1752  1775 D SidekickService: sendBitmapsLocked(): bounds = RectF(0.0, 0.0, 1.0, 1.0), drawableInfo = {.id = 2, .width = 338, .height = 412, .display = true, .offsetX = 0.0, .offsetY = 0.0, .rotationInfo = {.hasRotation = false, .pivotX = 0.0, .pivotY = 0.0, .degreesPerDay = 0.0, .degreesPerStep = 0.0, .zeroDegreesTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .transformInfo = {.transformed = false, .flipX = false, .flipY = false, .flip45 = false, .scaleX = 0.0, .scaleY = 0.0}, .type = GENERIC, .blink = {.blinking = false, .periodOnMs = 0.0, .periodOffMs = 0.0, .startTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .zOrder = 2, .displayInTwm = true}, image.size() = 1462
03-09 17:18:52.270  1752  1775 D SidekickService: sendBitmapsLocked(): status = 0, bytesUsed = 840
03-09 17:18:52.274  1752  1775 D SidekickServiceUtils: bitmapFromDrawable: Returning bitmap directly
03-09 17:18:52.275  1752  1775 D SidekickService: sendBitmapsLocked(): bounds = RectF(0.0, 0.0, 0.088757396, 0.07281554), drawableInfo = {.id = 10, .width = 30, .height = 30, .display = true, .offsetX = 0.0, .offsetY = 0.0, .rotationInfo = {.hasRotation = false, .pivotX = 0.0, .pivotY = 0.0, .degreesPerDay = 0.0, .degreesPerStep = 0.0, .zeroDegreesTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .transformInfo = {.transformed = false, .flipX = false, .flipY = false, .flip45 = false, .scaleX = 0.0, .scaleY = 0.0}, .type = GENERIC, .blink = {.blinking = false, .periodOnMs = 0.0, .periodOffMs = 0.0, .startTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .zOrder = 3, .displayInTwm = true}, image.size() = 131
03-09 17:18:52.279  1752  1775 D SidekickService: sendBitmapsLocked(): status = 0, bytesUsed = 75
03-09 17:18:52.279  1752  1775 D SidekickServiceUtils: bitmapFromDrawable: Returning bitmap directly
03-09 17:18:52.281  1752  1775 D SidekickService: sendBitmapsLocked(): bounds = RectF(0.9112426, 0.0, 1.0, 0.07281554), drawableInfo = {.id = 11, .width = 30, .height = 30, .display = true, .offsetX = 308.0, .offsetY = 0.0, .rotationInfo = {.hasRotation = false, .pivotX = 0.0, .pivotY = 0.0, .degreesPerDay = 0.0, .degreesPerStep = 0.0, .zeroDegreesTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .transformInfo = {.transformed = false, .flipX = false, .flipY = false, .flip45 = false, .scaleX = 0.0, .scaleY = 0.0}, .type = GENERIC, .blink = {.blinking = false, .periodOnMs = 0.0, .periodOffMs = 0.0, .startTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .zOrder = 3, .displayInTwm = true}, image.size() = 131
03-09 17:18:52.282  1752  1775 D SidekickService: sendBitmapsLocked(): status = 0, bytesUsed = 75
03-09 17:18:52.283  1752  1775 D SidekickServiceUtils: bitmapFromDrawable: Returning bitmap directly
03-09 17:18:52.286  1752  1775 D SidekickService: sendBitmapsLocked(): bounds = RectF(0.0, 0.92718446, 0.088757396, 1.0), drawableInfo = {.id = 12, .width = 30, .height = 30, .display = true, .offsetX = 0.0, .offsetY = 382.0, .rotationInfo = {.hasRotation = false, .pivotX = 0.0, .pivotY = 0.0, .degreesPerDay = 0.0, .degreesPerStep = 0.0, .zeroDegreesTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .transformInfo = {.transformed = false, .flipX = false, .flipY = false, .flip45 = false, .scaleX = 0.0, .scaleY = 0.0}, .type = GENERIC, .blink = {.blinking = false, .periodOnMs = 0.0, .periodOffMs = 0.0, .startTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .zOrder = 3, .displayInTwm = true}, image.size() = 131
03-09 17:18:52.289  1752  1775 D SidekickService: sendBitmapsLocked(): status = 0, bytesUsed = 75
03-09 17:18:52.290  1752  1775 D SidekickServiceUtils: bitmapFromDrawable: Returning bitmap directly
03-09 17:18:52.291  1752  1775 D SidekickService: sendBitmapsLocked(): bounds = RectF(0.9112426, 0.92718446, 1.0, 1.0), drawableInfo = {.id = 13, .width = 30, .height = 30, .display = true, .offsetX = 308.0, .offsetY = 382.0, .rotationInfo = {.hasRotation = false, .pivotX = 0.0, .pivotY = 0.0, .degreesPerDay = 0.0, .degreesPerStep = 0.0, .zeroDegreesTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .transformInfo = {.transformed = false, .flipX = false, .flipY = false, .flip45 = false, .scaleX = 0.0, .scaleY = 0.0}, .type = GENERIC, .blink = {.blinking = false, .periodOnMs = 0.0, .periodOffMs = 0.0, .startTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .zOrder = 3, .displayInTwm = true}, image.size() = 131
03-09 17:18:52.293  1752  1775 D SidekickService: sendBitmapsLocked(): status = 0, bytesUsed = 75
03-09 17:18:52.295  1752  1775 D SidekickServiceUtils: bitmapFromDrawable: Returning bitmap directly
03-09 17:18:52.296  1752  1775 D SidekickService: sendBitmapsLocked(): bounds = RectF(0.4704142, 0.47572815, 0.5295858, 0.52427185), drawableInfo = {.id = 14, .width = 20, .height = 20, .display = true, .offsetX = 159.0, .offsetY = 196.0, .rotationInfo = {.hasRotation = false, .pivotX = 0.0, .pivotY = 0.0, .degreesPerDay = 0.0, .degreesPerStep = 0.0, .zeroDegreesTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .transformInfo = {.transformed = false, .flipX = false, .flipY = false, .flip45 = false, .scaleX = 0.0, .scaleY = 0.0}, .type = GENERIC, .blink = {.blinking = false, .periodOnMs = 0.0, .periodOffMs = 0.0, .startTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .zOrder = 3, .displayInTwm = true}, image.size() = 143
03-09 17:18:52.298  1752  1775 D SidekickService: sendBitmapsLocked(): status = 0, bytesUsed = 60
03-09 17:18:52.299  1752  1775 D SidekickServiceUtils: bitmapFromDrawable: Returning bitmap directly
03-09 17:18:52.303  1752  1775 D SidekickService: sendBitmapsLocked(): bounds = RectF(0.46745563, 0.2669903, 0.5325444, 0.526699), drawableInfo = {.id = 15, .width = 22, .height = 107, .display = true, .offsetX = 158.0, .offsetY = 110.0, .rotationInfo = {.hasRotation = true, .pivotX = 11.0, .pivotY = 96.0, .degreesPerDay = 518400.0, .degreesPerStep = 6.0, .zeroDegreesTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 60000}}, .transformInfo = {.transformed = false, .flipX = false, .flipY = false, .flip45 = false, .scaleX = 0.0, .scaleY = 0.0}, .type = ROTATING, .blink = {.blinking = false, .periodOnMs = 0.0, .periodOffMs = 0.0, .startTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .zOrder = 4, .displayInTwm = true}, image.size() = 241
03-09 17:18:52.306  1752  1775 D SidekickService: sendBitmapsLocked(): status = 0, bytesUsed = 2897
03-09 17:18:52.307  1752  1775 D SidekickServiceUtils: bitmapFromDrawable: Returning bitmap directly
03-09 17:18:52.309  1752  1775 D SidekickService: sendBitmapsLocked(): bounds = RectF(0.0, 0.0, 0.0027173914, 0.002232143), drawableInfo = {.id = 100000, .width = 1, .height = 1, .display = true, .offsetX = 0.0, .offsetY = 0.0, .rotationInfo = {.hasRotation = false, .pivotX = 0.0, .pivotY = 0.0, .degreesPerDay = 0.0, .degreesPerStep = 0.0, .zeroDegreesTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .transformInfo = {.transformed = false, .flipX = false, .flipY = false, .flip45 = false, .scaleX = 0.0, .scaleY = 0.0}, .type = GENERIC, .blink = {.blinking = false, .periodOnMs = 0.0, .periodOffMs = 0.0, .startTime = {.daysSinceLocalEpoch = 0, .msSinceMidnight = 0}}, .zOrder = 5, .displayInTwm = true}, image.size() = 84
03-09 17:18:52.310  1752  1775 D SidekickService: sendBitmapsLocked(): status = 0, bytesUsed = 18
03-09 17:18:52.310  1752  1775 D SidekickService: ISidekickGraphics#endResources()...
03-09 17:21:32.580  1752  2548 D SidekickService: sendBitmapsLocked(): status = 0, bytesUsed = 155
03-09 17:21:32.580  1752  2548 D SidekickService: ISidekickGraphics#endResources()...
03-09 17:21:32.812  2390  2390 D SidekickManagerAsync: replaceWatchFaceComponents(): Callback#onResult(): 0
03-09 17:21:32.819  1752  2548 D SidekickService: setShouldControlDisplay called: true
03-09 17:21:32.875  1752  1911 I SidekickService: beginDisplayLocked(): DOZE_SUSPEND
03-09 17:21:32.965  1752  1911 D SidekickService:    ... beginDisplay result: 0 for halPower: 1
(standard input):17378:03-09 17:21:26.048  1752 19744 I AmbientService: [6e93f94] Showing watch face in offload mode, canceling alarm.

replaceWatchFaceComponents会对资源进行检查,如果资源格式不对,会有相关错误信息提示,如

03-09 17:32:01.810  2390  2390 D SidekickManagerAsync: sendWatchFace(): Callback#onResult(): 2
03-09 17:32:09.808  1752  1752 D SidekickService: setShouldControlDisplay called: false
03-09 17:32:09.861  2390  7074 I SidekickManager: replaceWatchFaceComponentsImpl(): watchFace = com.google.android.clockwork.decomposablewatchface.WatchFaceDecomposition@f4d00e1
03-09 17:32:09.862  1752 26357 D SidekickService: sendWatchFaceComponents called: watchFace = com.google.android.clockwork.decomposablewatchface.WatchFaceDecomposition@dfbff84shouldReplace = false
03-09 17:32:09.864  1752 26357 W SidekickService: Ignoring partial update to an invalid watchface
03-09 17:32:09.923  2390  2390 D SidekickManagerAsync: replaceWatchFaceComponents(): Callback#onResult(): 2

replaceWatchFaceComponents(): Callback#onResult(): 返回2,表示表盘无效。

三.Decomposable指针表盘的显示问题 。

   进入offload模式下,具有以下显示特点。

1.表盘上只能显示原图(如果设置指针ImageComponent的矩形区域与原图不一致,显示也不会被放大/缩小),基于这个规律,当给imageComponent设置的RECF区域与原图不一致时,只有左上角的坐标有效。这在谷歌Wear Partner Doc_ Decomposable Watch Face API文档有介绍。

2.指针旋转轴永远是BG绘制区域的中心位置(BG绘制参考本文 五)。基于这个规律,Decomposable Watchface表盘开发时,必须将指针的"旋转点儿"固定到BG中心位置。

3.即便满足以上2点时,实测发现,指针在旋转过程中,"旋转点儿"的位置还是会发生肉眼可见的偏移。为降低这个问题,修改指针形状,在表盘中心位置固定一个圆形的ImageComponent circle,把circle放到时针和分针的上面,秒针放到circle上方。

4.因为offload模式不支持扛锯齿,不能显示矢量图,只能显示点阵图,不可避免会出现粗糙边缘,所以UI出图时,含有圆形、椭圆形的,尽可能把圆弧弧度渐变做的细腻一些。且尽量不要使用彩色,实测发现彩色可能会因为屏幕较暗时,颜色越鲜艳屏幕越容易闪。

5.offload模式下显示指针表盘时,无法扛锯齿,显示效果确实不好,谷歌说在未来版本或许能优化,但目前只能通过调整指针图片来改善显示效果。

WearOS Offload模式下的表盘开发_第2张图片

四、手表Decomposable表盘产品需求。

因为Decomposable表盘能节约功耗,所以项目中希望能使用Decomposable表盘,但指针表盘显示效果不好,经与产品、设计沟通,更倾向于数字版的Decomposable表盘。数字版Decomposable表盘要考虑的问题。

 1.需要显示的基础信息,基本确定要显示时 分、日期、星期天。

 2.由于offload模式表盘只能显示ImageComponent、ComplicationComponent、FontComponent、NumberComponent。这些component均以drawable形式存在,考虑到手表国际化字符串翻译问题,只能通过ComplicationComponent形式来展示上述基础信息。

3.系统自带的时钟复杂数据很难满足我们UI要求。如果要满足产品要求,需要我们自定义复杂数据,分别显示时分,日期,星期天。手表要开发世界时钟,可以在这个app里增加复杂数据。

4.验证过我们自定义的复杂数据是能按预期刷新的(显示时间/日期/星期等时间TEXT的复杂数据,要

使用ComplicationText.TimeFormatBuilder(),生成一个TimeDependentText)。

五、BG绘制区域。

  手表进入ambient时,为了防止屏幕中某区域常亮导致屏幕出现坏块,高通提供了"防烧屏"机制,该机制的原理是在原显示屏中显示一块较小的区域,在1~2分钟的时间内移动该区域。显示区域大小可调,但具体调整规则目前不清楚,只知道

2*(10+2-DISPLAY_BURNIN_PIXEL_WIDTH*2+2*DISPLAY_BURNIN_PIXEL_HEIGHT不能大于255,其中DISPLAY_BURNIN_PIXEL_WIDTH和DISPLAY_BURNIN_PIXEL_HEIGHT分别表示防烧屏时减去的宽度、高度。

例如原屏幕分辨率368*448,DISPLAY_BURNIN_PIXEL_WIDTH=30,DISPLAY_BURNIN_PIXEL_HEIGHT=36.那么在

ambient模式下(使能防烧屏)实际显示的分辨率是338*412。

logcat.01:1044:03-09 19:10:10.950033  1691  1732 D SidekickService: SidekickService#getCapabilities()
logcat.01:1067:03-09 19:10:11.002619   413  1733 D SKGHAL  : getCapabilities: Capabilities returned
logcat.01:1068:03-09 19:10:11.002643   413  1733 D SKGHAL  : getCapabilities: RGB bits        = [3,3,2]
logcat.01:1069:03-09 19:10:11.002663   413  1733 D SKGHAL  : getCapabilities: paletteSize      = 16
logcat.01:1070:03-09 19:10:11.002680   413  1733 D SKGHAL  : getCapabilities: operations       = 1102000b
logcat.01:1071:03-09 19:10:11.002698   413  1733 D SKGHAL  : getCapabilities: availMemory      = 49800
logcat.01:1072:03-09 19:10:11.002718   413  1733 D SKGHAL  : getCapabilities: (x, y)           = (338, 412)
logcat.01:1074:03-09 19:10:11.004113  1691  1732 D SidekickService: SidekickService#getCapabilities(): {.capabilities = 285343755, .displaySizeX = 338, .displaySizeY = 412}

六,功耗测试

在只保留基本运动传感器功能,未开启运动模式/GPS/WIFI/心率灯等模块时,bg绘制表盘相对ap绘制,功耗大约能节约10%。

 

 

 

 

你可能感兴趣的:(WearOS Offload模式下的表盘开发)