分类 安卓 下的文章

本文首发地址 https://blog.csdn.net/CSqingchen/article/details/134656140
最新更新地址 https://gitee.com/chenjim/chenjimblog

通过 Android Bitmap 使用 ScriptIntrinsicBlur、Toolkit 实现模糊,我们已经知道两种实现模糊方法。
本文主要讲解另外几种高效实现Bitmap模糊的方法。

使用 RenderEffect 模糊

RenderEffect 是 Android 中一种用于实现图像特效的类,最低 API 要求 31
它允许开发者在不修改原始图像数据的情况下,对图像进行各种处理,例如模糊、光晕、阴影等
对 Bitmap 模糊及注释代码如下

fun blur(bitmap:Bitmap, radius: Float, outputIndex: Int): Bitmap {

     // 配置跟 bitmap 同样大小的 ImageReader
    val imageReader = ImageReader.newInstance(
        bitmap.width, bitmap.height,
        PixelFormat.RGBA_8888, numberOfOutputImages,
        HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
    )
    val renderNode = RenderNode("RenderEffect")
    val hardwareRenderer = HardwareRenderer()

    // 将 ImageReader 的surface 设置到 HardwareRenderer 中
    hardwareRenderer.setSurface(imageReader.surface)
    hardwareRenderer.setContentRoot(renderNode)
    renderNode.setPosition(0, 0, imageReader.width, imageReader.height)

    // 使用 RenderEffect 配置模糊效果,并设置到 RenderNode 中。  
    val blurRenderEffect = RenderEffect.createBlurEffect(
        radius, radius,
        Shader.TileMode.MIRROR
    )
    renderNode.setRenderEffect(renderEffect)

    // 通过 RenderNode 的 RenderCanvas 绘制 Bitmap。   
    val renderCanvas = renderNode.beginRecording()
    renderCanvas.drawBitmap(bitmap, 0f, 0f, null)
    renderNode.endRecording()

    // 通过 HardwareRenderer 创建 Render 异步请求。   
    hardwareRenderer.createRenderRequest()
        .setWaitForPresent(true)
        .syncAndDraw()

    // 通过 ImageReader 获取模糊后的 Image 。
    val image = imageReader.acquireNextImage() ?: throw RuntimeException("No Image")

    // 将 Image 的 HardwareBuffer 包装为 Bitmap , 也就是模糊后的。   
    val hardwareBuffer = image.hardwareBuffer ?: throw RuntimeException("No HardwareBuffer")
    val bitmap = Bitmap.wrapHardwareBuffer(hardwareBuffer, null)
        ?: throw RuntimeException("Create Bitmap Failed")
    hardwareBuffer.close()
    image.close()
    return bitmap
}

完整实例参考 RenderEffectImageProcessor.kt
还可以通过设置 RenderEffect 的其他属性,如 setColorFilter( )方法,为模糊后的 Bitmap 添加颜色滤镜。

使用 Vukan 模糊

Vulkan 是一种低开销、跨平台的 API,用于高性能 3D 图形。
Android平台包含 Khronos Group 的 Vulkan API规范的特定实现。
使用 Vukan 模糊的核心代码如下,可参考 ImageProcessor.cpp

bool ImageProcessor::blur(float radius, int outputIndex) {
    RET_CHECK(1.0f <= radius && radius <= 25.0f);

    //高斯模糊配置,在后文 GLSL 同样 适用
    constexpr float e = 2.718281828459045f;
    constexpr float pi = 3.1415926535897932f;
    float sigma = 0.4f * radius + 0.6f;
    float coeff1 = 1.0f / (std::sqrtf(2.0f * pi) * sigma);
    float coeff2 = -1.0f / (2.0f * sigma * sigma);
    int32_t iRadius = static_cast<int>(std::ceilf(radius));
    float normalizeFactor = 0.0f;
    for (int r = -iRadius; r <= iRadius; r++) {
        const float value = coeff1 * std::powf(e, coeff2 * static_cast<float>(r * r));
        mBlurData.kernel[r + iRadius] = value;
        normalizeFactor += value;
    }
    normalizeFactor = 1.0f / normalizeFactor;
    for (int r = -iRadius; r <= iRadius; r++) {
        mBlurData.kernel[r + iRadius] *= normalizeFactor;
    }
    RET_CHECK(mBlurUniformBuffer->copyFrom(&mBlurData));

    // 应用两阶段模糊算法:一个水平模糊核,然后是一个垂直模糊核。
    // 比单遍应用一个2D模糊滤镜更高效。
    // 两遍模糊算法有两个核,每个核的时间复杂度为O(半径),
    // 而单遍模糊算法只有一个核,但时间复杂度为O(半径^2)。
    auto cmd = mCommandBuffer->handle();
    RET_CHECK(beginOneTimeCommandBuffer(cmd));

    // 临时映像在第一遍中用作输出存储映像
    mTempImage->recordLayoutTransitionBarrier(cmd, VK_IMAGE_LAYOUT_GENERAL, /*preserveData=*/false);

    // 水平方向高斯模糊
    mBlurHorizontalPipeline->recordComputeCommands(cmd, &iRadius, *mInputImage, *mTempImage,
                                                   mBlurUniformBuffer.get());

    // 临时图像在第二遍中用作输入采样图像,
    // 过渡图像用作输出存储映像。
    mTempImage->recordLayoutTransitionBarrier(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
    mStagingOutputImage->recordLayoutTransitionBarrier(cmd, VK_IMAGE_LAYOUT_GENERAL,
                                                       /*preserveData=*/false);

    // 数值方向高斯模糊
    mBlurVerticalPipeline->recordComputeCommands(cmd, &iRadius, *mTempImage, *mStagingOutputImage,
                                                 mBlurUniformBuffer.get());

    // 准备将图像从过渡图像复制到输出图像。
    mStagingOutputImage->recordLayoutTransitionBarrier(cmd, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);

    // 复制暂存图像到输出图像。
    recordImageCopyingCommand(cmd, *mStagingOutputImage, *mOutputImages[outputIndex]);

    // 提交到队列。
    RET_CHECK(endAndSubmitCommandBuffer(cmd, mContext->queue()));
    return true;
}

VulkanContext.cpp 主要是一些初始化

VulkanResources.cpp 主要是 Buffer 和 Image 的一些封装方法

上层接口参见 VulkanImageProcessor.kt

使用 GLSL 模糊

主要流程:

  • 将输入 Bitmap 转为纹理
    GLES31.glTexStorage2D(GLES31.GL_TEXTURE_2D, 1, GLES31.GL_RGBA8, mInputImage.width, mInputImage.height )
    GLUtils.texImage2D(GLES31.GL_TEXTURE_2D, 0, mInputImage, 0)
  • 通过 OpenGL 处理纹理,同样有水平、竖直模糊,即 mBlurHorizontalProgram 和 mBlurVerticalProgram
  • 将纹理转换为 Bitmap ,即 copyPixelsToHardwareBuffer ,这里也是耗时最多的

完整实例参考 GLSLImageProcessor.kt

libVkLayer_khronos_validation.so 主要是调试用,
ImageProcessor::create(/*enableDebug=*/false, assetManager) 传入 false 可以不需要
可以在以下地址下载新版本
https://github.com/KhronosGroup/Vulkan-ValidationLayers

RS、Vukan、RenderEffect、GLSL 效率对比

上文完整源码及对比示例地址
https://gitee.com/chenjim/android-blur/blob/blur/RenderScriptMigrationSample

他们之间效率对比结果如下

虽然 GLSL 看起来会差一些,主要是因为 openGL 纹理转 Bitmap 耗时较大。
如果纯GL场景使用,跟 Vukan 和 RenderEffect 相差无几。


以上就是Android 使用Vukan、RenderEffect、GLSL实现模糊的介绍,希望对你有所帮助。
如果你在使用过程遇到问题,可以留言讨论。
如果你觉得本文写的还不错,欢迎点赞收藏。


相关文章
Android Bitmap 使用ScriptIntrinsicBlur、Toolkit 实现模糊
Android Bitmap 使用Vukan、RenderEffect、GLSL实现模糊)

Android性能优化--Perfetto用SQL性能分析

[toc]

本文首发地址 https://blog.csdn.net/CSqingchen/article/details/134167741
最新更新地址 https://gitee.com/chenjim/chenjimblog
Perfetto 抓取 trace 可参考 https://blog.csdn.net/CSqingchen/article/details/128900541

介绍

Perfetto 是一个由 Google 开发的高性能、可扩展的事件追踪系统,用于在实时和离线场景下监控系统的性能。
它通过一种简单且强大的查询语言(称为 SQL)来分析和查询事件数据。
在本博客中,我们将深入探讨如何使用 SQL 在 Perfetto 中进行性能分析。

Perfetto SQL 基础

Perfetto SQL 是一种用于查询事件数据的语言,它支持大多数标准的 SQL 操作,
如 SELECT、FROM、WHERE、GROUP BY、ORDER BY 等。
在 Perfetto 中,数据以表格的形式存储,因此你可以使用 SQL 来检索和操作这些数据。
下面是一个简单的 Perfetto SQL 查询示例:
SELECT ts, dur, name FROM slice WHERE ts > 85545835986081 AND ts < 85546017415330
ts, dur, name 是挑选的字段,slice 是挑选的表名

示例 trace 文件可以在 data/perfetto下载 xiaomi13.camera.trace.7z
解压后,在 https://ui.perfetto.dev/ 打开

可以使用如下命令查看表中有哪些字段
SELECT * FROM slice LIMIT 10

如果 trace 中包含 android log,还可以用如下命令过滤日志
SELECT * FROM android_logs WHERE msg LIKE "%ProcessRequest%" LIMIT 30

trace 中有哪些表可用,以及各个字段是什么关系呢,可以参考
https://perfetto.dev/docs/analysis/sql-tables
其中 Event 关系图如下

使用 Perfetto SQL 进行性能分析

使用 Perfetto SQL 进行性能分析的关键在于理解如何构造查询以获取你需要的信息。
以下是一些常见的性能分析任务和相应的 SQL 查询示例:

  1. 分析特定事件的发生频率:
    SELECT COUNT(*) FROM slice WHERE name = 'waitForNextFrame'
    waitForNextFrame 一共有多少次

  2. 分析事件的性能数据:
    SELECT (dur/1e6) FROM slice WHERE name = 'waitForNextFrame'
    每次 waitForNextFrame 耗时多少毫秒。dur单位是纳秒

  3. 分析一段时间内的事件数据:
    SELECT MIN(dur/1e6) as min_duration, MAX(dur/1e6) as max_duration, AVG(dur/1e6) as avg_duration FROM slice WHERE name = 'waitForNextFrame' and dur > 0
    显示 waitForNextFrame 最小、最大、平均值

  4. 对事件进行排序:
    SELECT (dur/1e6),ts,name FROM slice WHERE name LIKE '%wait%' and dur > 0 ORDER by dur DESC

  5. 统计 CPU 时间

     DROP VIEW IF EXISTS slice_with_utid;
     CREATE VIEW slice_with_utid AS
     SELECT
     ts,
     dur,
     slice.name as slice_name,
     slice.id as slice_id, utid,
     thread.name as thread_name
     FROM slice
     JOIN thread_track ON thread_track.id = slice.track_id
     JOIN thread USING (utid);
    
     DROP TABLE IF EXISTS slice_thread_state_breakdown;
     CREATE VIRTUAL TABLE slice_thread_state_breakdown
     USING SPAN_LEFT_JOIN(
     slice_with_utid PARTITIONED utid,
     thread_state PARTITIONED utid
     );
    
     SELECT slice_id, slice_name, SUM(dur) AS cpu_time
     FROM slice_thread_state_breakdown
     WHERE state = 'Running'
     GROUP BY slice_id;

基本都是 SQL 语句,SQL关键字含义可以参考 https://www.w3schools.cn/sql/

总结

使用 Perfetto 和 SQL 进行性能分析是一种强大而灵活的方法。
通过理解如何构造 SQL 查询,你可以轻松地获取你需要的信息,从而更好地理解系统的性能。
在 Perfetto 中使用 SQL 进行性能分析可以帮助你更好地理解系统的性能,并找出潜在的性能问题。

相关文章
Android性能优化--Perfetto抓取trace
Android性能优化--perfetto分析native内存泄露
Android性能优化--Perfetto用SQL性能分析

参考文章
https://perfetto.dev/docs/quickstart/trace-analysis
https://perfetto.dev/docs/analysis/common-queries
https://zhuanlan.zhihu.com/p/641412977
https://yiyan.baidu.com/share/gdFw3P5ucI

Android 性能优化--Gradle 编译速度优化

本文首发地址 https://blog.csdn.net/CSqingchen/article/details/132308808
本文最新根系地址 https://gitee.com/chenjim/chenjimblog

1. 保持工具最新

Android 工具几乎每次更新都会获得构建优化和新功能,保持最新版本可以加快构建速度
Android Gradle 插件
Android Studio 和 SDK 工具

2. 使用 KSP 代替 kapt

Kapt(Kotlin 注释处理工具)允许您将 Java 注释处理器与 Kotlin 代码结合使用,即使这些处理器没有对 Kotlin 的特定支持。这是通过从 Kotlin 文件生成 Java 存根来完成的,然后处理器可以读取这些存根。这种存根生成是一项昂贵的操作,并且对构建速度有重大影响。
KSP(Kotlin 符号处理)是 kapt 的 Kotlin 优先替代品。KSP 直接分析 Kotlin 代码,速度提高了 2 倍。它还可以更好地理解 Kotlin 的语言结构。
建议尽可能从 kapt 迁移到 KSP。在大多数情况下,此迁移只需要更改项目的构建配置。
如果你的项目使用了数据绑定(dataBinding)暂无无法迁移到KSP,尚未计划对 KSP 支持 , 可以通过将dataBinding的使用隔离到单独的模块来减轻 kapt 对构建的影响。
许多流行的库(比如 GlideRoom 等)都已经具有 KSP 支持,还有一些正在添加支持,比如 Dagger
可参考 迁移KSP文档

3. 避免编译不必要的资源

避免编译和打包未测试的资源,例如其他语言本地化和屏幕密度资源。

android {
    productFlavors {
        create("dev") {
            // 如下配置显示 dev 渠道包,只打包 en 语言和 xxhdpi 屏幕密度的资源  
            resourceConfigurations("en", "xxhdpi")
}   }   }

或者如下修改

android {
    applicationVariants.configureEach { variant ->
        if (variant.buildType.name == "debug") {
            variant.mergedFlavor.resourceConfigurations.clear()
            variant.mergedFlavor.resourceConfigurations.add("zh-rCN")
            variant.mergedFlavor.resourceConfigurations.add("xxhdpi")
}     }    }

4. 优化 repositories maven 排序

如下图,可以查看到需要的依赖库下载结果

可以将多数能找到放最前面,比如在国内可以使用如下示例

repositories {
    maven { url 'https://maven.aliyun.com/repository/google' }
    maven { url 'https://maven.aliyun.com/repository/public' }
    maven { url 'https://your.private.io/repository' }
    mavenCentral()
    google()
}

5. 在调试构建中使用静态构建配置值

使用动态版本代码、版本名称、资源或任何其他更改清单文件的构建逻辑,
每次您想要运行更改时,都需要完整的应用程序构建,即使实际的更改可能只需要热交换。
如果 Release 版本确实需要,我们可以只跟改配置,使 debug 版本不生效。
比如在 BuildConfig 中版本号使用动态时间戳(精确到时分秒),导致小的修改,大编译,严重影响Rebuild速度。
在 Gradle 8.0 默认不生成 BuildConfig.java,进而无法直接引用 BuildConfig 中配置。

6. 使用静态依赖版本

在build.gradle文件中声明依赖项时,避免使用动态版本号
(末尾带有加号的版本号,如'androidx.appcompat:appcompat:1.+')
可能会导致意外的版本更新、难以解决版本差异以及 Gradle 检查更新导致的构建速度变慢。

7. 创建库模块

查找可以转换为 Android 库模块的代码。
以这种方式模块化您的代码,允许构建系统仅编译您修改的模块,并缓存这些输出以供将来构建。
模块化还可以使 并行项目执行更加有效

8. 为自定义构建逻辑创建任务

编译项目(Make Project)完成后,如下图,点击 Build Analyzer

会跳转到编译分析栏,会显示,Config 及 Task 执行时间,如下图

切换 OverviewTasks,可以显示各个 Task 执行耗时

创建构建配置文件后,如果构建配置文件显示相对较长的构建时间花费在 Configuration 阶段,
请检查您的build.gradle脚本,并查找要包含在自定义 Gradle 任务中的代码,
通过将一些构建逻辑移至任务中,您可以帮助确保任务仅在需要时运行,可以缓存结果以供后续构建使用

9. 将图像转换为 WebP

WebP 可以提供比 JPEG 或 PNG 更好的压缩。
减少图像文件大小而不必执行构建时压缩可以加快构建速度,特别是当您的应用程序使用大量图像资源时。
使用 Android Studio 可以 轻松 将图像转换为 WebP

10. 禁用 PNG 处理

如果您不将 PNG 图像转换为 WebP,您仍然可以通过在每次构建应用程序时禁用自动图像压缩来加快构建速度。
如果您使用的是 Android Gradle 插件 3.0.0 或更高版本,则默认情况下,“debug”构建类型会禁用 PNG 处理。
如果 Release 也需要禁用 PNG 处理,可以参见如下修改

android {
    buildTypes {
        release {
            crunchPngs false
            //  isCrunchPngs = false //for kotlin
}   }   }

11. 挑选 JVM 垃圾收集器

通过配置 Gradle 使用的最佳 JVM 垃圾收集器可以提高构建性能。
虽然 JDK 8 默认配置为使用并行垃圾收集器,但 JDK 9 及更高版本配置为使用 G1 垃圾收集器
建议 使用并行垃圾收集器测试您的 Gradle 构建,需要在 gradle.properties 添加如下修改
org.gradle.jvmargs=-XX:+UseParallelGC 或者 org.gradle.jvmargs=-Xmx4608M -XX:+UseParallelGC

12. 增加 JVM 堆大小

在 编译分析 栏,我们可以看到编译过程中,GC 花费的构建时间,
如果 超过 超过 15% ,我们可以增加 Java 虚拟机 (JVM) 堆大小
需要在 gradle.properties 中修改限制 4、6 或 8 GB ,参考如下
org.gradle.jvmargs=-Xmx6g

13. 使用非传递 R 类

使用非传递R类可以更快地构建具有多个模块的应用程序。
这样做可以确保每个模块的R类仅包含对其自身资源的引用,而不从其依赖项中提取引用,从而有助于防止资源重复。
这会带来更快的构建速度以及避免编译的相应好处。
这是 Android Gradle 插件 8.0.0 及更高版本中的默认行为。

14. 使用非常量 R 类

在APP模块和自动化测试时,使用非常量的 R.class 可以提高 Java 编译的增量并允许更精确的资源收缩。
在Library模块,R.class 一直不是常量,只有在打包APP或者自动化测试时,资源才被编号。
这是 Android Gradle Plugin 8.0.0 及更高版本中的默认行为,也就是如下代码会编译出错.

switch (viewId) {
    case R.id.bt_back:
        dosth1;
        break;
    case R.id.bt_enter:
        dosth2();
        break;
}

// 可以替换为  
if (viewId == R.id.bt_back) dosth1;
else if (viewId == R.id.bt_enter) dosth2();

15. 禁用 Jetifier 标志

由于大多数项目直接使用 AndroidX 库,因此您可以删除 Jetifier标志以获得更好的构建性能。
gradle.properties 修改如下即可
android.enableJetifier=false

16. 使用配置缓存

配置缓存 允许 Gradle 记录有关构建任务图的信息并在后续构建中重用它,不必再次重新配置整个构建。
可以在 gradle.properties 修改如下

org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn

注意可能会存在冲突的情况,参见 Gradle#13490
启用配置缓存后,第一次运行项目时,构建输出会显示
Calculating task graph as no configuration cache is available for tasks
在后续运行期间,构建输出显示
Reusing configuration cache


参考文档
https://developer.android.com/build/optimize-your-build
https://developer.android.com/build/migrate-to-ksp

Android Studio Bot 下载使用

本文最新更新地址 https://gitee.com/chenjim/chenjimblog

  • 下载 Android Studio Hedgehog
    当前的新版本是 2023.1.1 Canary 10
  • 找到Studio Bot: View->Tool Windows->Studio Bot,或者下图
  • 登录 Google 账号,注意当前限制只能US的账户使用 !!

    查看自己 Google 账户服务地区 https://policies.google.com/terms
    如果没有 US 账户,可以尝试开启全局 Proxy重新注册账户
  • 注意配置 Android Studio 的 Proxy,参考如下
  • 完成以上步骤后,我们就可以开始我们的 Studio Bot 体验之旅了

参考文章
https://blog.csdn.net/dai_jiawei/article/details/130702636
https://developer.android.com/studio/preview/studio-bot

Android Compose Button defaultButtonColors

本文最新更新地址 <https://gitee.com/chenjim/chenjimblog

发现问题

最近看 Android Compose 相关资料发现如下代码

    colors = defaultButtonColors(
        backgroundColor = if (count > 5) Color.Green else Color.White
    )

原文地址 https://developer.android.com/jetpack/compose/preview?hl=zh-cn

编译会出现异常 Unresolved reference: defaultButtonColors


解决问题

  1. 以上是中文页面,对应的 英文页面 ,当前(20230701) 已经没有相应的说明
    新版 compose preview 介绍参考 https://developer.android.com/jetpack/compose/tooling/previews

  2. 在新版本中,本文使用的是 implementation 'androidx.compose.material3:material3:1.1.1'
    已经没有 ButtonConstants.defaultButtonColorsbackgroundColor
    可以使用如下代码替换

         colors = ButtonDefaults.buttonColors(
             containerColor = if (count > 5) Color.Green else Color.Gray
         )
  3. android-compose-codelabs 示例中,也均使用的是 ButtonDefaults.buttonColors


参考自 https://stackoverflow.com/questions/64376333