MoreRSS

site icon技术小黑屋修改

89年出生的保定人在北京,Android 工程师, Infoq译者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

技术小黑屋的 RSS 预览

Android 升级 targetSDK 35 解决 namespace 问题

2025-10-04 10:00:00

升级 Android targetSDK 至 35 并使用 Gradle 8.0+ 后,遇到了第三方库 namespace 配置问题。

错误信息

1
2
3
4
Execution failed for task ':react-native-inappbrowser:processDebugManifest'.
> A failure occurred while executing com.android.build.gradle.tasks.ProcessLibraryManifest$ProcessLibWorkAction
> Setting the namespace via the package attribute in the source AndroidManifest.xml is no longer supported.
  Recommendation: remove package="com.proyecto26.inappbrowser" from the source AndroidManifest.xml.

或者类似错误:

1
2
3
4
5
Namespace not specified. Please specify a namespace in the module's build.gradle file like so:

android {
    namespace 'com.example.namespace'
}

原因分析

Android Gradle Plugin 8.0+ 不再支持在 AndroidManifest.xml 中通过 package 属性设置 namespace,要求在 build.gradle 中显式声明。升级 targetSDK 至 35 需要使用 Gradle 8.0+,但很多第三方库(如 react-native-inappbrowserappcenter-analytics 等)尚未更新配置,导致构建失败。


解决方案

在项目根目录的 android/build.gradle 文件中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
allprojects {
    repositories {
        google()
        mavenCentral()
        
        // 如果使用 Detox 测试框架,添加此配置
        maven {
            url("$rootDir/../node_modules/detox/Detox-android")
        }
    }
    
    subprojects {
        afterEvaluate { project ->
            if (project.hasProperty('android')) {
                project.android {
                    // 自动设置 namespace
                    if (namespace == null || namespace.isEmpty()) {
                        def defaultNamespace = project.group.toString().replace('.', '_')
                        namespace = defaultNamespace
                    }

                    // 启用 buildConfig
                    buildFeatures {
                        buildConfig = true
                    }
                }

                // 自动修复 namespace 和清理 AndroidManifest.xml
                project.tasks.register("fixManifestsAndNamespace") {
                    doLast {
                        // 1. 从 AndroidManifest.xml 提取 package 并添加到 build.gradle
                        def buildGradleFile = file("${project.projectDir}/build.gradle")
                        if (buildGradleFile.exists()) {
                            def buildGradleContent = buildGradleFile.getText('UTF-8')
                            def manifestFile = file("${project.projectDir}/src/main/AndroidManifest.xml")
                            if (manifestFile.exists()) {
                                def manifestContent = manifestFile.getText('UTF-8')
                                def packageName = manifestContent.find(/package="([^"]+)"/) { match, p -> p }
                                if (packageName && !buildGradleContent.contains("namespace")) {
                                    println "Setting namespace in ${buildGradleFile}"
                                    buildGradleContent = buildGradleContent.replaceFirst(
                                        /android\s*\{/, "android {\n    namespace '${packageName}'"
                                    )
                                    buildGradleFile.write(buildGradleContent, 'UTF-8')
                                }
                            }
                        }

                        // 2. 移除 AndroidManifest.xml 中的 package 属性
                        def manifests = fileTree(dir: project.projectDir, includes: ['**/AndroidManifest.xml'])
                        manifests.each { File manifestFile ->
                            def manifestContent = manifestFile.getText('UTF-8')
                            if (manifestContent.contains('package=')) {
                                println "Removing package attribute from ${manifestFile}"
                                manifestContent = manifestContent.replaceAll(/package="[^"]*"/, '')
                                manifestFile.write(manifestContent, 'UTF-8')
                            }
                        }
                    }
                }

                // 在构建前自动执行修复
                project.tasks.matching { it.name.startsWith("preBuild") }.all {
                    dependsOn project.tasks.named("fixManifestsAndNamespace")
                }
            }
        }
    }
}

说明

工作原理

此方案包含三个层次的处理:

  1. 自动设置 namespace:如果子项目未配置 namespace,自动使用 project.group 并将点号替换为下划线作为 namespace
  2. 启用 buildConfig:自动为所有子项目启用 buildConfig 特性
  3. 自动迁移配置
    • AndroidManifest.xml 中提取 package 属性
    • 将其写入对应的 build.gradle 作为 namespace
    • 移除 AndroidManifest.xml 中的 package 属性

这个 task 在每次构建前(preBuild)自动执行,确保所有第三方库都符合 Gradle 8.0+ 的要求。

适用场景

  • React Native 项目升级 targetSDK 35
  • Flutter 项目升级 targetSDK 35
  • 使用 Detox 测试框架的项目
  • 原生 Android 项目使用旧版第三方库
  • 任何遇到 “namespace not specified” 或 “package attribute not supported” 错误的场景

注意事项

  • 此方案会自动修改第三方库的 build.gradleAndroidManifest.xml 文件
  • 修改仅在 node_modules 中生效,不影响源码仓库
  • 建议在 CI/CD 中首次构建后检查修改是否正确
  • 如果某些库已经声明了 namespace,不会被覆盖

验证

执行以下命令重新构建项目:

1
2
3
cd android
./gradlew clean
./gradlew assembleDebug

或在 React Native 项目中:

1
npx react-native run-android

构建过程中会看到类似输出:

1
2
Setting namespace in /path/to/project/android/react-native-inappbrowser/build.gradle
Removing package attribute from /path/to/project/android/react-native-inappbrowser/src/main/AndroidManifest.xml

与简化方案对比

如果只需要为缺少 namespace 的库自动设置默认值,可以使用简化版:

1
2
3
4
5
6
7
8
9
10
11
subprojects {
    afterEvaluate { project ->
        if (project.hasProperty('android')) {
            project.android {
                if (namespace == null || namespace.isEmpty()) {
                    namespace project.group.toString().replace('.', '_')
                }
            }
        }
    }
}

简化方案不会修改任何文件,仅在内存中设置 namespace,但可能无法解决所有第三方库的问题。


参考



解决 Android Studio 关闭后终端 flutter run 进程自动结束的问题

2025-09-28 08:56:00

在 Flutter 开发过程中,很多开发者遇到一个困扰的问题:当使用终端运行 flutter run 命令进行开发时,一旦关闭 Android Studio 或 IntelliJ IDEA,终端中的 flutter run 进程就会自动结束,导致应用停止运行。本文将详细分析这个问题的原因并提供解决方案。

问题现象

典型场景

  1. 在终端中执行 flutter run 启动 Flutter 应用
  2. 同时打开 Android Studio 进行代码编辑
  3. 关闭 Android Studio 或 IntelliJ IDEA
  4. 终端中的 flutter run 进程自动结束,应用停止运行

影响范围

  • 通过终端启动的 flutter run 进程
  • 相关的热重载功能失效
  • 调试连接中断
  • 需要重新启动应用才能继续开发

问题原因分析

根本原因

当 Android Studio 启动时,它会自动管理 ADB(Android Debug Bridge)服务器的生命周期。默认情况下,IDE 会:

  1. 启动自己的 ADB 服务器实例
  2. 接管现有的调试连接
  3. 在退出时终止所有相关的调试进程

这种设计导致即使是通过终端独立启动的 flutter run 进程,也会因为 ADB 服务器的关闭而被迫结束。

进程依赖关系

1
终端 flutter run → ADB 连接 → Android Studio 管理的 ADB 服务器

当 Android Studio 关闭时,它管理的 ADB 服务器也会关闭,进而导致所有依赖该 ADB 连接的进程(包括终端的 flutter run)都被终止。

解决方案

配置外部 ADB 服务器管理

最有效的解决方案是让 Android Studio 使用外部手动管理的 ADB 服务器,而不是自己管理一个实例:

配置步骤

  1. 打开 Android Studio 设置(Preferences/Settings)
  2. 导航到 Build, Execution, DeploymentDebugger
  3. 找到 Android Debug Bridge (adb) 部分
  4. Adb Server Lifecycle Management 中选择 Use existing manually managed server
  5. 设置 Existing ADB server port5037(默认端口)

android studio adb config

关键配置说明

  • Use existing manually managed server: 告诉 Android Studio 不要自己管理 ADB 服务器,而是使用外部已存在的服务器
  • Existing ADB server port: 指定外部 ADB 服务器的端口(通常为 5037)

这样配置后,Android Studio 不会在启动时接管 ADB 服务器,也不会在关闭时终止它,从而保证终端运行的进程不受影响。

验证配置是否生效

配置完成后,可以通过以下步骤验证:

  1. 在终端启动 flutter run
  2. 打开 Android Studio
  3. 关闭 Android Studio
  4. 检查终端中的 flutter run 是否依然运行

如果 flutter run 进程没有被终止,说明配置成功。

总结

通过配置 Android Studio 使用外部手动管理的 ADB 服务器,可以有效解决 IDE 关闭后终端 flutter run 进程自动结束的问题。这种方法的优势在于:

  1. 进程独立性:终端和 IDE 的调试进程相互独立
  2. 开发效率:无需频繁重启应用
  3. 资源优化:避免不必要的进程重启
  4. 稳定性:减少因 IDE 操作导致的调试中断

推荐所有 Flutter 开发者采用这种配置方式,特别是那些习惯在终端中运行 flutter run 的开发者。



使用 grep 查找关键字并显示上下文行

2025-06-24 10:30:00

背景

排查日志时,常需要定位关键字并带上一两行上下文确认语义。grep 内建的上下文选项可以直接满足需求,不必再手动 sed -n '19,21p'

快速示例

假设想在 app.log 中找出包含 Fatal error 的行,并且同时看到上一行与下一行:

1
grep -n -C 1 "Fatal error" app.log
  • -n 会显示行号,便于定位。
  • -C 1 等价于 --context=1,表示向前向后各多带 1 行。想多看几行时调整数字即可。

输出中,命中的行以冒号分隔行号与内容,上下文行则以短横线 - 连接,快速区分重点。

控制上下文范围

grep 提供三个粒度化参数:

  • -C <N>:两侧各 N 行,是最常用的形式。
  • -B <N>:只带前 N 行(Before)。
  • -A <N>:只带后 N 行(After)。

例如只关心关键字后面的调用栈,可使用:

1
grep -n -A 4 "NullPointerException" stacktrace.txt

再配合 -m 1(匹配一次后退出)可以缩短复杂日志的搜索时间。

与常见参数组合

  • -i:忽略大小写,处理大小写不一致的告警信息很方便。
  • -E:启用扩展正则,可直接写 grep -E "(WARN|ERROR)"
  • --color=auto:高亮命中关键字,在终端阅读更直观。

将这些参数组合成 Shell 函数,后续排查直接调用。例如在 ~/.bashrc 中定义:

1
2
3
4
gctx() {
  local keyword="$1" file="$2" lines="${3:-1}"
  grep -n --color=always -C "$lines" "$keyword" "$file"
}

执行 gctx "timeout" service.log 2,即可得到行号、关键字高亮、上下文行的结果。

小结

  • -C/-A/-B 是获取上下文的核心选项,记住数字表示行数即可。
  • 搭配 -n--color-m 等参数可以提升排查效率。
  • 如果命中结果过多,将命令与 less -Rfzf 管道组合,能够在终端中进行二次筛选,让排查体验更顺滑。


Android 开发中的三个常见构建错误及解决方案

2025-06-23 08:33:00

最近在 Android 项目开发中遇到了几个构建错误,以下是解决方案,供遇到同样问题的开发者参考。

1. META-INF 文件冲突

错误信息

1
2
3
4
5
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:mergeDebugJavaResource'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction
   > 2 files found with path 'META-INF/versions/9/OSGI-INF/MANIFEST.MF' from inputs:

解决方案

app/build.gradle 中添加以下配置:

1
2
3
4
5
6
7
android {
    packagingOptions {
        resources {
            excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
        }
    }
}

说明

此错误通常由多个依赖包含相同的 META-INF 文件引起,通过 excludes 排除重复文件即可解决。


2. TensorFlow Lite 库冲突

错误信息

1
2
3
Caused by: java.lang.RuntimeException: Duplicate class org.tensorflow.lite.DataType found in
modules jetified-litert-api-1.0.1-runtime (com.google.ai.edge.litert:litert-api:1.0.1) and
jetified-tensorflow-lite-api-2.12.0-runtime (org.tensorflow:tensorflow-lite-api:2.12.0)

解决方案

app/build.gradle 中添加依赖替换规则:

1
2
3
4
5
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute module("org.tensorflow:tensorflow-lite") with module("com.google.ai.edge.litert:litert:1.0.1")
    }
}

说明

Google 将 TensorFlow Lite 迁移到新包名 com.google.ai.edge.litert,若项目同时包含新旧包名,会导致类冲突。通过依赖替换强制使用新包解决。


3. Jetifier 与 BouncyCastle 兼容性问题

错误信息

1
2
3
4
Caused by: java.lang.RuntimeException: Failed to transform
'/Users/xxxxx/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bcprov-jdk18on/1.78/619aafb92dc0b4c6c
c4cf86c487ca48ee2d67a8e/bcprov-jdk18on-1.78.jar' using Jetifier. 
Reason: IllegalArgumentException, message: Unsupported class file major version 65.

解决方案

在项目根目录的 android/gradle.properties 文件中添加:

1
android.jetifier.ignorelist=bcprov-jdk18on-1.78.jar,bcutil-jdk18on-1.78.jar

说明

BouncyCastle 1.78 版本使用 Java 21 编译(class file major version 65),而 Jetifier 不支持此版本字节码。将相关 jar 包加入 Jetifier 忽略列表可避免转换错误。


总结

以上三个问题是 Android 构建中常见的依赖冲突问题,解决思路包括:

  • 排除重复文件
  • 替换冲突依赖
  • 跳过不兼容的处理

遇到类似问题时,仔细分析错误信息,通常能找到相应解决方案。



使用 flock 解决 Git `unable to read tree` 问题

2025-06-15 08:49:00

背景

在 CI/CD 环境下,团队常遇到以下错误:

1
fatal: unable to read tree <SHA>

这通常是多个进程或脚本并发操作同一个 Git 仓库,导致元数据损坏或锁冲突。Git 并非为高并发本地操作设计,因此需要解决并发问题。

问题复现

在自动化脚本中,例如:

1
2
git fetch origin
git checkout some-branch

如果多个任务同时执行,可能导致锁冲突或元数据损坏。

解决思路

通过加锁机制,让所有 Git 操作串行执行。flock 是一个简单高效的工具,专为这种场景设计。

flock 安装

Linux

大多数 Linux 发行版自带 flock(属于 util-linux 套件)。如果没有,可按以下方式安装:

  • Debian/Ubuntu:
1
2
sudo apt-get update
sudo apt-get install util-linux
  • CentOS/RHEL:
1
sudo yum install util-linux
  • Arch:
1
sudo pacman -S util-linux

安装后即可使用 flock 命令。

macOS

macOS 默认不包含 flock,但可通过 Homebrew 安装兼容版本:

1
brew install flock

安装的是 Ben Noordhuis 的 flock,语法与 Linux 版本基本一致。

提示:在 CI 服务(如 GitHub Actions)中,可在步骤中提前安装 flock

flock 用法

flock 用于在 shell 脚本中对文件加锁:

1
flock <lockfile> <command>

建议将锁文件放在 .git 目录下,避免污染业务代码目录。

实战例子

假设有一个 deploy.sh 脚本:

1
2
3
4
#!/bin/bash
git fetch origin
git checkout some-branch
# ...more commands...

加锁后修改为:

1
2
3
4
5
6
7
8
#!/bin/bash
LOCK_FILE="/path/to/your/repo/.git/deploy.lock"

flock -n "$LOCK_FILE" bash <<'EOF'
git fetch origin
git checkout some-branch
# ...more commands...
EOF

或者直接锁定整个脚本:

1
flock -n /path/to/your/repo/.git/deploy.lock ./deploy.sh
  • -n:表示拿不到锁时立即退出(可选)。
  • 建议将锁文件放在 .git 目录下。

总结

  • 避免并发操作同一个 Git 仓库!
  • 使用 flock 使 Git 操作串行,防止元数据损坏。
  • Linux 下直接使用,macOS 通过 Homebrew 安装 flock
  • 锁粒度可适当放宽,确保安全优先。
  • 本地自动化操作 Git 时,flock 是必备工具,简单高效!

如有问题,请在评论区讨论。



Could not create task ':generateDebugRFile' 问题小记

2024-11-03 00:49:00

前段时间,处理一个比较旧的 flutter plugin,涉及到 Android 的部分,一顿修改后,发现无法 gradle sync 成功。 报错如下,

1
2
3
4
5
6
7
8
9
10
Could not create task ':generateDebugRFile'.
Cannot use @TaskAction annotation on method IncrementalTask.taskAction$gradle_core() because interface org.gradle.api.tasks.incremental.IncrementalTaskInputs is not a valid parameter to an action method.

* Try:
> Run with --debug option to get more log output.
> Run with --scan to get full insights.

* Exception is:
com.intellij.openapi.externalSystem.model.ExternalSystemException: Could not create task ':generateDebugRFile'.
Cannot use @TaskAction annotation on method IncrementalTask.taskAction$gradle_core() because interface org.gradle.api.tasks.incremental.IncrementalTaskInputs is not a valid parameter to an action method.

根据分析上面的错误信息,判定与 gradle 有关,和修改的 kotlin 代码无关。

经过一些简短尝试,最终确定是 gradle 版本不匹配的问题(主要由这一句推断 because interface org.gradle.api.tasks.incremental.IncrementalTaskInputs is not a valid parameter to an action method.)。

原因与解法

  • 原因为 Android Gradle Plugin 与 gradle 不匹配。
  • 可以修改 gradle plugin 版本,也可以修改 gradle 版本。

修改 AGP 版本

classpath ‘com.android.tools.build:gradle:7.1.2’ // The Android Gradle plugin.

修改 gradle 版本

修改gradle/wrapper/gradle-wrapper.properties

distributionUrl=https://services.gradle.org/distributions/gradle-8.0-all.zip

修改成(或者对应的gradle 版本) distributionUrl=https://services.gradle.org/distributions/gradle-7.4-all.zip

如何确定 AGP 与 gradle 对应关系

查询,请访问 这里 https://developer.android.com/build/releases/gradle-plugin?#updating-gradle