Technical review · 2026-10-13. The 2019 screenshots and logs remain historical evidence, not a current-device benchmark. Source checks use AOSP
android-9.0.0_r1andandroid-16.0.0_r1. Cache behavior still matters, but the original claims about reallocating every frame, fixed performance rankings, and CPU load inferred from gfxinfo have been corrected. Case 6 repeats Case 1’s entire log; its measurement provenance is unresolved.
Hardware Acceleration vs. Software Acceleration
Many people confuse hardware acceleration with Hardware Layer in Android. Enabling hardware acceleration does not automatically give every View an explicit hardware layer. Before discussing layers, let’s look at hardware acceleration.
The original background articles remain historical reading: Android Hardware Acceleration Principles and Implementation and Understanding Android Hardware Acceleration for Beginners. Check the official Android guide for API support.
For View drawing, the software path rasterizes primarily on the CPU; hardware acceleration lets HWUI use the GPU for drawing. The CPU still handles layout, records drawing commands, and submits work. Skia also has GPU backends, so its presence does not establish software rendering.
View hardware acceleration arrived in Android 3.0 (API 11) and is enabled by default for apps targeting API 14 or later. Application, Activity, and Window configuration, as well as software layers on individual Views, affect the actual path. The articles above cover the code and principles. Here I want to take another angle and walk through those differences in the traces I used at the time.
Hardware-Accelerated App Performance
Take home-screen scrolling as an example. In the typical HWUI path on Android 5.0 (API 21) and later, the main thread and RenderThread cooperate to prepare a frame.

Zooming in shows input, animation, and conditional traversal work on the main thread, followed by rendering work on RenderThread. The old annotations group some measure/layout work under “Draw”; distinguish these using the actual slices and call paths.

These are CPU-thread slices. RenderThread duration is not GPU execution time, and command submission or eglSwapBuffers returning does not prove that a frame has reached the screen. When the main thread is released from a wait also depends on the rendering path.
Software-Accelerated App Performance
For the software-drawing comparison, I used Cloud Flash Pay. Scrolling in that case was noticeably jerky and unpleasant. The screenshot shows drawing and buildDrawingCache on the main thread without visible RenderThread work. This does not prove that the entire process never has a RenderThread, or describe every version of this app. Download historical Trace

The selected interval in the enlarged image is about 20.878 ms. It shows a long interval, not that every frame misses its deadline. Even if the app draws window contents on the CPU, subsequent system composition may use the GPU or a hardware composer.

Summary
- Hardware acceleration changes the View drawing path; it does not remove CPU work. RenderThread visibility alone is not an app-wide acceleration test.
- Software Views or windows can coexist with hardware-accelerated windows. App drawing and system composition are separate stages.
- Hardware acceleration usually suits modern View UIs, but its benefit depends on workload, resources, and device. Two screenshots cannot establish that it is always faster.
- Check unsupported drawing operations against the Android API version. Where needed, use a software layer on the affected View instead of disabling acceleration for the whole app.
Software Layer vs. Hardware Layer
Software Layer and Hardware Layer refer to offscreen pixel caches for a View. They are distinct from SurfaceFlinger composition layers and from the hardware-acceleration setting for an entire window.
A View has three layer types:
LAYER_TYPE_NONE: no extra explicit View pixel layer.LAYER_TYPE_SOFTWARE: draw View content through the software path into a Bitmap, including inside a hardware-accelerated window.LAYER_TYPE_HARDWARE: use a GPU offscreen layer in a hardware-accelerated View hierarchy; otherwise behave as a software layer.
LAYER_TYPE_NONE
This is the default. It does not disable hardware acceleration or require all work to start from scratch: the hardware path can still reuse drawing commands recorded in RenderNodes/display lists.
LAYER_TYPE_SOFTWARE
How is a software layer implemented? In simple terms, it caches the View content in a Bitmap. This allocation fragment comes from Android 9 View.java; its variables belong to the surrounding framework code:
1 | bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), |
Invalid content requires a redraw. An existing Bitmap of the same dimensions can be reused; redraw does not necessarily mean destruction and reallocation.
The role of Software layer:
- In software rendering, a layer can apply opacity, color filtering, or blending to a View and its children as a group.
- In hardware rendering, it can handle local operations unsupported by that version’s hardware path; caching stable content may also reduce repeated drawing.
- Frequent updates incur software redraws and texture-upload work in the hardware path. A software layer is not a universal animation optimization.
LAYER_TYPE_HARDWARE
A hardware layer draws View content into an offscreen GPU resource for later window drawing. FBO is a useful description for an OpenGL backend, not a universal implementation across Android versions and Skia GL/Vulkan backends. The View hierarchy must be hardware accelerated; otherwise the layer behaves as a software layer.
The role of Hardware layer:
Hardware layers can apply blending effects to a group and reduce repeated drawing of stable, complex content during translation, alpha, scale, or rotation animations. Caching also affects clipping and sampling in rotation or 3D transforms; it does not guarantee sharper or faster scaling.
Use the layer temporarily and restore its previous type. This example assumes UI-thread execution and exclusive control of the View’s layerType:
1 | ObjectAnimator anim = ObjectAnimator.ofFloat(view, "rotationY", 180f); |
ObjectAnimator cancellation also invokes the end callback. Do not let overlapping animations manage the same View’s layerType. Since API 16, withLayer() lets ViewPropertyAnimator restore the type captured at the call when the animation ends or is canceled:
1 | view.animate().rotationY(180f).withLayer().start(); |
Transform-only changes may reuse cached content. Text changes, child additions/removals, or other content invalidation require redraws. Reallocation depends on dimensions, resource state, and backend; “the FBO is destroyed” is not a valid general description.
Summary
- Software and hardware layers use different pixel-caching paths. Choose based on supported drawing operations and measured cost.
- Hardware layers may benefit property animations of stable, complex content; compare against the default
NONEpath. - Restore the previous type afterward instead of retaining an unnecessary layer. Graphics resources may be released later or reused internally; process memory need not drop immediately.
Performance Problem Cases Caused by Incorrect LayerType Usage
Performance Problems Caused by Incorrect Software Layer Usage
This historical WeChat Moments trace shows repeated buildDrawingCache/SW work on the main thread. Investigate content updates and invalidation that cause repeated software drawing; the slice alone does not prove allocation of a new Bitmap each time. Download historical Trace

Scrolling that large Moments image felt very jerky. Zooming into the trace shows long drawing work. Check whether repeated software drawing can be avoided, and inspect scheduling and other work before assigning the cause of jank.

Code Flow
Distinguish the two branches of buildLayer(). These excerpts from Android 9 View.java show that only LAYER_TYPE_SOFTWARE follows buildDrawingCache → buildDrawingCacheImpl; the hardware branch delegates to ThreadedRenderer. These framework fragments depend on class fields.
1 | public void buildLayer() { |
1 | public void buildDrawingCache(boolean autoScale) { |
The trace covers cache drawing, not a dedicated allocation event. buildDrawingCacheImpl allocates when the Bitmap is absent or its dimensions differ; otherwise it can clear and redraw the existing Bitmap. The old drawing-cache API was deprecated in API 28 and is not a recommended modern optimization entry point for app code.
Performance Problems Caused by Incorrect Hardware Layer Usage
This historical folder-opening case has several icons using hardware layers and repeated layer drawing on RenderThread. Frequently changing content can offset caching benefits through extra offscreen work. These slices alone do not prove that a buffer is destroyed and reallocated every frame. Download historical Trace

The selected interval is about 21.68 ms, with one ShortcutIcon layer-drawing slice lasting about 4.783 ms. Keep a single slice, a whole frame, and GPU execution time separate.

For example, Android 9 SkiaPipeline::createOrUpdateLayer checks layer existence and dimensions and can reuse existing resources. Content redraw and resource allocation are different operations.
Debugging Tools
Some historical systems expose “Show hardware layers updates” in developer options. Its location and availability vary by version and vendor; it is not necessarily under Accessibility.
It highlights updated layer regions, not necessarily the entire interface. For a transform animation of stable content, reuse after initialization is desirable. Continuous highlighting is a reason to inspect update sources, but dynamic content legitimately needs updates. Absence of highlighting alone proves neither caching behavior nor performance. Inspect layer drawing, dimensions, and frame timing together.
Summary
- Check content stability and drawing complexity before adding a hardware layer to a property animation.
- Do not add layers indiscriminately. Cache creation, memory use, offscreen drawing, and subsequent window drawing all have costs.
- Additional offscreen work for alpha depends on overlapping content and rendering paths. Not every setAlpha call or opacity animation creates the same kind of buffer.
- Invalidation may require redraw, but not necessarily reallocation of an entire resource. Compare
NONEwith layer paths when content changes frequently instead of assuming a fixed ranking. - Restore the prior layerType after temporary changes. Use software layers locally where compatibility or measured benefit justifies them.
Examples of LayerType’s Impact on Animation Performance
Example
Code
To compare the situations discussed above, I wrote a small example with just two TextViews: one starts the animation and one animates TRANSLATION_X, ALPHA, TRANSLATION_Y, SCALE_X, and SCALE_Y. Start/end listeners select a layer; the update listener controls text changes.
The example project at this source snapshot (0060782a6c32337641acc14af3f9b4e8818402a3) sets HARDWARE and then SOFTWARE consecutively in its start callback. SOFTWARE wins, and text updates are enabled. Running that snapshot unchanged does not produce six independent comparisons. It is a historical project with compile/target SDK 29 and min SDK 26.
The code retains the original 500 ms duration, 150 px translations, alpha target 0, and 150× scaleX/scaleY targets. These are not ordinary UI values. Dynamic cases append text every frame, adding string allocation, text measurement, and layout changes; differences cannot be attributed solely to layers.
These are syntax-corrected illustrative fragments, not newly measured code underlying the old logs. Install only one case’s listeners, replacing the default listeners below; do not accumulate all six. Use a fresh AnimatorSet and reset the View to layer NONE, original text, counter 0, alpha 1, scaleX/scaleY 1, and translationX/Y 0 before each run. Wait for layout and prevent overlapping runs. Resetting to NONE at the end is valid only under this explicit initial condition. Fields, View initialization, and imports still require the complete project.
1 | // Set up animation |
Statistics Tool: GFXInfo
Alongside Systrace, I used adb shell dumpsys gfxinfo <package> to record HWUI frame statistics. Read the old annotations in this screenshot with the following corrections:

- Janky frames follow the system’s frame-interval/deadline rules, not a universal 16 ms threshold. Histograms and percentiles are bucketed; the 99th percentile is not the exact maximum.
- High input latency measures neither CPU load nor input-callback execution time. Android 9 JankTracker has a triple-buffering/deferred-deadline branch that can record high input latency without a missed deadline. Reason counters also cannot simply be added to obtain the janky-frame total.
- Android 16 JankTracker retains legacy and newer deadline accounting. For current comparisons, record the OS version, refresh rate, measurement interval, and original output. gfxinfo does not cover complete input-to-display latency.
- These historical samples contain only 29–31 frames each, with insufficient device, OS, and reset-condition records. Cases 1 and 6 have identical logs, including the histogram; they are not confirmed independent measurements. No device rerun or replacement data was produced for this review.
Case 1: Normal Layer + No Dynamic View Content Update
Code
1 | // Leave layerType and text unchanged. |
Systrace Phenomenon
The screenshot contains yellow frame markers and a long flush commands slice on RenderThread. This is CPU-side work or waiting, not a direct GPU-duration measurement. Download historical Trace

gfxInfo Data
The preserved log reports 14/30 janky frames (46.67%), 99th percentile 32 ms, and High input latency 30. These are historical sample statistics, not a main-thread CPU-load measurement.
1 | Total frames rendered: 30 |
Case 2: Software Layer + No Dynamic View Content Update
Code
1 | objectAnimator1.addListener(new android.animation.AnimatorListenerAdapter() { |
Systrace Phenomenon
The screenshot shows buildDrawingCache/SW initially, without the same cache-drawing work in the displayed later transform frames. This is consistent with reusing stable content. Green markers do not replace complete frame timing and repeated measurements. Download historical Trace

gfxInfo Data
The preserved log reports 1/31 janky frames (3.23%), 99th percentile 16 ms, and High input latency 0.
1 | Total frames rendered: 31 |
Case 3: Hardware Layer + No Dynamic View Content Update
Code
1 | objectAnimator1.addListener(new android.animation.AnimatorListenerAdapter() { |
Systrace Phenomenon
The screenshot shows initial drawLayer work and green markers in the displayed frames. It supports investigating cache reuse, not a universal performance or input-latency claim. Download historical Trace

gfxInfo Data
The preserved log reports 0/31 janky frames (0.00%), 99th percentile 14 ms, and High input latency 0.
1 | Total frames rendered: 31 |
Case 4: Normal Layer + Dynamic View Content Update
Code
1 | objectAnimator1.addListener(new android.animation.AnimatorListenerAdapter() { |
Systrace Phenomenon
Appending text introduces long Animation, measure, layout, and draw slices. Separate text updates, layout work, and scheduling waits. Download historical Trace

gfxInfo Data
The preserved log reports 12/31 janky frames (38.71%), 99th percentile 29 ms, and High input latency 31.
1 | Total frames rendered: 31 |
Case 5: Software Layer + Dynamic View Content Update
Code
1 | objectAnimator1.addListener(new android.animation.AnimatorListenerAdapter() { |
Systrace Phenomenon
Repeated buildDrawingCache work shows software-cache redraws. It does not prove destruction of a Bitmap every frame or establish a single bottleneck for all elapsed time. Download historical Trace

gfxInfo Data
The preserved log reports 12/29 janky frames (41.38%), 99th percentile 32 ms, and High input latency 18.
1 | Total frames rendered: 29 |
Case 6: Hardware Layer + Dynamic View Content Update
Code
1 | objectAnimator1.addListener(new android.animation.AnimatorListenerAdapter() { |
Systrace Phenomenon
Repeated drawLayer work appears while growing text changes content dimensions; the layer dimensions differ from the static case. Resizing can require allocation, but these slices do not establish buffer reallocation every frame. Download historical Trace

gfxInfo Data
The preserved log reports 14/30 janky frames (46.67%), 99th percentile 32 ms, and High input latency 30. These are historical sample statistics, not a main-thread CPU-load measurement. This entire block duplicates Case 1, including its histogram. Its independent provenance is unverified, so exclude it from quantitative comparisons.
1 | Total frames rendered: 30 |
Summary
The cases preserve a useful observation: caching may help stable content, while changing content needs redraws. They do not establish the original fixed performance rankings. Samples are small, Cases 1 and 6 duplicate data, and growing text changes the workload.
- For stable, expensive-to-draw Views, compare temporary hardware layers against NONE using frame timing, memory, and GPU cost.
- For dynamic content, inspect invalidation and size changes before assessing extra offscreen work. Do not assume that NONE or any layer type always wins.
- Repeated buildDrawingCache/drawLayer slices are investigation leads, not evidence of reallocation every frame or sufficient grounds to blame a system/vendor issue.
- Fix the version, refresh rate, initial state, and repetition count for current-device comparisons. On Android 12 (API 31) and later, Perfetto FrameTimeline can add expected/actual-frame evidence when that data is captured; missing data does not mean no jank.
For modern implementation details, compare Android 16 View, RenderNode, SkiaPipeline, and ViewPropertyAnimator. These source tags identify implementation boundaries; they do not imply that the historical experiment was reproduced on an Android 16 device.
If you have thoughts after reading this, leave a comment or use the WeChat QR code below to discuss them with me. Please also tell me about any errors or omissions so I can correct them before they mislead anyone else. And if you found it useful, please share it.
Zhihu Article Address
Since blog message communication is inconvenient, for likes or communication, you can move to this article’s Zhihu page.
Zhihu - A Deep Dive into Hardware Layers in Android
About Me && Blog
Below is a personal introduction and related links. I look forward to communicating with colleagues in the same field. When three walk together, there must be my teacher!
- Blogger Personal Introduction: Inside are personal WeChat and WeChat group links.
- Blog Content Navigation: A navigation of personal blog content.
- Personally Organized and Collected Excellent Blog Articles - Android Performance Optimization Must Know: Everyone is welcome to recommend and self-recommend (private WeChat chat is fine).
- Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.
“If you want to go fast, go alone. If you want to go far, go together.”
