Android 里的 Blurring 模糊效果并不只是加一层半透明遮罩那么简单。真正的高斯模糊需要对周围像素做加权采样,计算量随半径增大呈指数级上升。过去很多项目为了做毛玻璃背景或头像虚化,会在后台线程里生成一张缩小后的 Bitmap,再用 RenderScript 或自研的 StackBlur 算法进行处理。这些方案在低版本设备上能跑通,但往往伴随着明显的内存抖动和掉帧。如果想在列表滚动或动画过程中保持流畅,就必须理解不同模糊方案背后的渲染管线差异。

RenderScript 的遗留问题与 RenderEffect 的崛起
RenderScript 曾经是 Android 官方推荐的高性能计算方案,尤其是在图像处理领域,很多模糊效果都是基于 ScriptIntrinsicBlur 实现的。它的好处是语法简单,几行代码就能对 Bitmap 做高斯模糊,而且系统会自动调度 CPU 或 GPU 资源。但从 Android 12 开始,RenderScript 被正式标记为 deprecated,官方不再建议在新项目中使用。原因并不只是 API 过时,而是 RenderScript 的上下文创建和内存分配开销较高,频繁调用时会触发大量 GC,拖慢 UI 线程。再加上不同厂商对 RenderScript 的驱动实现差异很大,同一份代码在 Pixel 和小米设备上的模糊半径可能都会出现不一致。
相比之下,API 31 引入的 RenderEffect 走的是硬件加速渲染管线,直接在 RenderNode 上应用效果,不会产生额外的 Bitmap 分配。对于一个普通的 ImageView,只需要设置一个 BlurEffect 就能完成实时模糊,而且滚动和动画时也能保持较好的帧率。下面是一段简单的使用示例:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
RenderEffect blurEffect = RenderEffect.createBlurEffect(
20f, 20f, Shader.TileMode.CLAMP);
imageView.setRenderEffect(blurEffect);
} else {
// 低版本降级到 RenderScript 或自定义 Bitmap 模糊
applyLegacyBlur(imageView);
}
这里有两个参数需要特别注意。第一个是 Radius,它控制模糊的强度,单位是像素。第二个参数 Shader.TileMode 决定了边缘像素的采样策略,CLAMP 会直接复制边缘颜色,REPEAT 则会让图像内容重复出现。对于一般的毛玻璃背景,使用 CLAMP 就能避免边缘出现奇怪的条纹。不过 RenderEffect 也有明显的短板,它只能作用于 API 31 以上的设备,而且 Radius 最大支持 25f,如果需要更强的模糊效果,就得叠加多个 BlurEffect 或者采用自定义方案。
还有一个容易忽视的问题:RenderEffect 作用于 View 时,是对 View 的整个绘制内容做模糊,包括 View 自身的背景和子 View。如果你只想模糊背景而不影响前景文字,就需要把背景单独抽成一个 View,或者使用 RenderNode 的 setRenderEffect 方法进行更细粒度的控制。这也是很多开源毛玻璃组件在 API 31 上的常见做法。
自定义 Shader 与 RenderNode 的进阶实现
如果 minSdk 低于 31,又不想引入过重的第三方库,那么自定义 Bitmap 模糊算法就是一个可靠的选择。StackBlur 是一种基于栈的模糊算法,它的时间复杂度接近 O(n),比朴素的高斯卷积快很多。实现思路是先对图像做水平方向的模糊,再对结果做垂直方向的模糊,两次一维卷积叠加起来就能近似二维高斯模糊。下面是一个简化版的核心函数,它接受原始 Bitmap 和模糊半径,返回处理后的新 Bitmap:
public static Bitmap stackBlur(Bitmap source, int radius) {
if (radius < 1) {
return source;
}
int width = source.getWidth();
int height = source.getHeight();
int[] pixels = new int[width * height];
source.getPixels(pixels, 0, width, 0, 0, width, height);
int[] horizontalResult = new int[width * height];
blurHorizontal(pixels, horizontalResult, width, height, radius);
blurVertical(horizontalResult, pixels, width, height, radius);
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
result.setPixels(pixels, 0, width, 0, 0, width, height);
return result;
}
上面的代码只展示了调用结构,实际的 StackBlur 需要维护一个动态栈来累加邻近像素的颜色值。由于篇幅有限,这里不展开完整实现,但核心思想是每一行从左到右滑动时,去掉最左侧的像素贡献,加入最右侧的新像素贡献,避免重复计算。这个算法在半径小于 30 时表现很好,再大的话性能会明显下降。为了进一步优化,很多项目会先把原图缩小到原来的 1/4 或 1/8,再对缩小图做 StackBlur,最后放大回来。这样虽然会损失一点细节,但视觉上完全够用,而且计算量能减少一个数量级。
另一种进阶方案是使用 RenderNode 配合 HardwareRenderer 做离屏模糊。RenderNode 可以记录一段 View 的绘制指令,然后由硬件加速渲染器执行。我们可以在 RenderNode 上设置 RenderEffect,再将其绘制到一个 HardwareRenderer 的输出 Surface 上,最后把结果转换为 Bitmap。这种方式在 API 29 以上就能使用,比 RenderEffect 的 API 31 门槛更低,而且同样利用了 GPU 加速。它的缺点是 API 较为底层,需要手动管理 RenderNode 的生命周期和 HardwareRenderer 的销毁,代码复杂度明显上升。
如何对 Blurring 效果进行有效测试
模糊效果很难用一句话判断好坏,因为“看起来模糊”是主观感受,不同设备的屏幕密度和色彩配置也会影响结果。但是从工程角度,我们仍然可以拆解出几个可量化的指标:模糊半径是否准确、边缘是否出现异常、帧率是否稳定、内存是否在合理范围内。第一个指标可以通过像素对比来验证。准备一张有清晰边缘的测试图,比如黑白棋盘格,模糊后边缘像素的颜色应该向中间灰值靠拢。读取 Bitmap 的像素值,计算模糊前后每个通道的平均绝对差,如果整体差值远小于预期,说明模糊没有生效;如果某些区域差值异常大,可能是 TileMode 设置错误导致边缘颜色溢出。
int totalDiff = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int before = beforeBitmap.getPixel(x, y);
int after = afterBitmap.getPixel(x, y);
totalDiff += Math.abs(Color.red(before) - Color.red(after));
totalDiff += Math.abs(Color.green(before) - Color.green(after));
totalDiff += Math.abs(Color.blue(before) - Color.blue(after));
}
}
double avgDiff = totalDiff / (width * height * 3.0);
第二类测试是性能监控。模糊操作如果发生在主线程,很容易造成丢帧。利用 Android Studio 的 Profiler 观察 CPU 和内存曲线,可以快速定位是不是 blur 函数耗时过长。更精确的做法是使用 FrameMetrics 或 dumpsys gfxinfo 命令,统计模糊 View 所在页面的帧渲染时间。一般来说,模糊半径在 15 到 20 之间,1080p 分辨率下,RenderEffect 的 GPU 模糊应该能把单帧渲染控制在 8 毫秒以内。如果超过 16 毫秒,说明设备负载已经很高,需要考虑降低模糊质量或者缓存模糊结果。
另外,自动化截图对比也是一个很实用的测试手段。在仪器化测试中通过 ActivityScenario 启动目标页面,用 UiAutomator 或 View 的 draw 方法生成 Bitmap 截图,然后与预先保存的基准截图做像素级比较。需要注意的是,不同设备的分辨率和字体渲染会有差异,所以这种测试更适合在固定模拟器配置上运行,或者只比较模糊区域的统计特征,而不是逐像素完全相等。对于低版本降级方案,还可以编写单元测试验证 StackBlur 函数的正确性,比如输入一张 3x3 的纯色图加一个不同颜色的中心像素,模糊后中心像素的 RGB 值应该向周围颜色靠拢,而不是保持不变。
综合来看,Blurring 模糊效果的实现和测试需要同时关注渲染管线的选择和量化验证的方法。RenderEffect 是未来趋势,但兼容性要求较高;自定义 StackBlur 灵活可控,适合低版本兜底;RenderNode 则提供了介于两者之间的一个平衡点。无论选择哪种方案,都应该把模糊质量、帧率和内存占用纳入测试清单,而不是只在视觉上简单确认一下。
Android 模糊效果高斯模糊RenderEffect修改时间:2026-09-28 12:33:49