Android开发踩坑记录:签名管理、Gradle配置与NDK开发

Android开发踩坑记录:签名管理、Gradle配置与NDK开发

这篇文章是我在2021年进行Android应用开发时遇到的一些问题和解决方案的整理。

APK签名管理

获取APK的SHA1值

SHA1值用于应用的身份验证和第三方SDK集成(如微信登录、支付宝等)。

方法一:从APK提取

1
2
3
4
# 1. 用WinRAR打开APK,提取META-INF目录下的CERT.RSA文件

# 2. 执行命令获取SHA1
keytool -printcert -file META-INF/CERT.RSA

方法二:从签名证书获取

1
2
3
4
# 获取.keystore或.jks文件的SHA1
keytool -list -v -keystore D:\my.keystore -storepass 111111

# 111111为证书密码

方法三:在线工具

可以使用在线工具上传.keystore或APK文件获取SHA1值。

查看APK签名信息

1
2
3
4
5
6
7
8
9
10
11
12
13
# 解压APK的META-INF目录
# 使用keytool查看签名详情
keytool -printcert -file META-INF/CERT.RSA

# 输出示例:
# Owner: CN=Android Debug, O=Android, C=US
# Issuer: CN=Android Debug, O=Android, C=US
# Serial number: 1
# Valid from: Thu Jan 01 00:00:00 CST 2009 until: ...
# Certificate fingerprints:
# MD5: ...
# SHA1: ...
# SHA256: ...

Gradle高级配置

动态修改包名

通过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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// build.gradle

ext {
// 修改前的applicationId
applicationIds = 'com.aaa.xxx'
// 修改后的applicationId
toApplicationIds = 'com.bbb.xxx'
// 修改前的package
packageName = 'package ' + applicationIds
// 修改后的package
toPackageName = 'package ' + toApplicationIds
// 复制代码的目录
fromPath = 'app/src/' + replaceText(applicationIds, "\\.", "\\/")
// 复制要放到的新目录
intoPath = 'app/src/' + replaceText(toApplicationIds, "\\.", "\\/")
// 删除以前的目录
deletePath = 'app/src/' + replaceText(applicationIds, "\\.", "\\/") + '/../'
// 修改前渠道名
fromChannel = '"AAA"'
// 修改后渠道名
toChannel = '"' + toApplicationIds + '"'
// 是否执行替换
ifReplace = false
}

// 替换每一个java文件中的引用路径
task replaceImportPath {
if (!ifReplace) return

FileTree tree = fileTree(dir: 'app')
tree.include '**/*.java'
tree.include '**/*.xml'

tree.each { File mfile ->
fileReader(mfile.path, applicationIds, toApplicationIds)
fileReader(mfile.path, fromChannel, toChannel)
}

fileReader("app/proguard-rules.pro", applicationIds, toApplicationIds)
fileReader("app/build.gradle", applicationIds, toApplicationIds)
}

task copyFile(type: Copy) {
if (!ifReplace) return

println("replaceImportPath:" + replaceImportPath)
dependsOn replaceImportPath

copy {
from fromPath
into intoPath
filter { String line ->
if (line.find(packageName)) {
line = line.replace(packageName, toPackageName)
}
"$line"
}
}

println("fromPath:" + fromPath)
println("intoPath:" + intoPath)

File file1 = new File(deletePath)
file1.deleteDir()
}

def fileReader(path, fromString, toString) {
def readerString = ""
def hasReplace = false

file(path).withReader('UTF-8') { reader ->
reader.eachLine {
if (it.find(fromString)) {
it = it.replace(fromString, toString)
hasReplace = true
}
readerString <<= it
readerString << '\n'
}

if (hasReplace) {
println(path + " has replace package.")
file(path).withWriter('UTF-8') { within ->
within.append(readerString)
}
}
}
}

def replaceText(String fileText, String key, String value) {
def regex = key
fileText = (fileText =~ /${regex}/).replaceAll(value)
return fileText
}

库冲突排除

当项目中引入多个库时,可能会出现依赖冲突。

1
2
3
4
5
6
7
8
9
10
11
configurations {
all*.exclude group:'com.qq.e.union', module: 'union'
all*.exclude group:'com.android.support', module: 'support-v4'
}

// 或在dependencies中排除
dependencies {
implementation('com.example:library:1.0.0') {
exclude group: 'com.example', module: 'conflict-module'
}
}

Gradle内存配置

解决Expiring Daemon because JVM heap space is exhausted错误:

1
2
3
4
5
# gradle.properties
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=2048m
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true

离线模式切换

问题: Disable offline mode and rerun the build

解决方案:

  • 在Android Studio中,选择右侧边栏Gradle
  • 点击Toggle Offline Mode切换

或修改gradle.properties:

1
org.gradle.offline=false

NDK开发环境配置

NDK版本下载

版本 Windows Mac Linux
r21d 下载 下载 下载
r22 下载 下载 下载

历史版本下载链接格式:

1
https://dl.google.com/android/repository/android-ndk-r{版本号}-{平台}.zip

CMake版本问题

错误: CMake报Invalid revision 3.18.1-g262b901-dirty错误

原因: CMake版本过高

解决方案:

1
2
# 在SDK Manager中卸载高版本
# 下载安装3.6或3.10版本

解决路径过长问题

错误: cocos2dx_static/scripting/js-bindings/jswrapper/v8/debugger/inspector_socket_server.o.d No such file or directory

解决方案:

  1. 将项目放在盘符根目录下(如C:\或D:\)
  2. 缩短项目名称
  3. 使用subst命令创建虚拟驱动器
1
2
3
# Windows
subst X: "C:\Very\Long\Path\To\Project"
cd X:\

环境变量配置

1
2
3
# Windows系统环境变量
ANDROID_NDK_HOME=C:\Users\Administrator\AppData\Local\Android\Sdk\ndk\20.0.5594570
NDK_ROOT=D:\android-ndk-r20b

NDK编译配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// build.gradle (Module: app)
android {
ndkVersion "21.0.6113669"

defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
arguments "-DANDROID_STL=c++_shared"
}
}

ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}

externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}

Cocos2d-x与Android Studio集成

Cocos2d-x-Lua Windows环境配置

环境依赖:

  • python-2.7.6
  • android-ndk-r9d-windows-x86_64
  • apache-ant-1.9.16-bin
  • cocos2d-x-3.2
  • adt-bundle-windows-x86_64-20140702
  • jdk-8u301-windows-x64

环境变量:

1
2
3
4
5
6
ANDROID_SDK_ROOT=E:\adt-bundle-windows-x86_64-20140702\sdk
ANT_ROOT=E:\apache-ant-1.9.16\bin
COCOS_CONSOLE_ROOT=E:\cocos2d-x-3.2\tools\cocos2d-console\bin
NDK_ROOT=E:\android-ndk-r9d
JAVA_HOME=C:\Program Files\Java\jdk1.8.0_301
NDK_MODULE_PATH=E:\adt-bundle-windows-x86_64-20140702\sdk

创建工程:

1
2
3
4
5
# 进入cocos2d-x目录
cd E:\cocos2d-x-3.9\tools\cocos2d-console\bin

# 创建Lua工程
cocos.py new MyGame -p com.game.simple -l lua -d E:\project

编译运行:

1
2
3
4
5
# 打包Android
cocos compile -p android --ap 20

# 或在Android Studio中打开
# ...\MyGame\frameworks\runtime-src\proj.android-studio

Visual Studio Lua调试配置

安装BabeLua插件:

配置Lua工程:

  1. 工具栏 -> Lua -> New Lua Project
  2. Lua scripts folder: 项目的src目录
  3. Lua exe path: simulator\win32\exe
  4. Working path: 项目目录

注意事项:

  • 生成的Lua项目设置为启动项
  • 可能需要修改working path
  • 在AppDelegate.cpp添加搜索路径:
1
2
LuaStack* stack = engine->getLuaStack();
stack->addSearchPath("src/");

常见编译错误与解决方案

Program type already present

错误:

1
Program type already present: android.support.v4.os.ResultReceiver

解决方案:

1
2
3
# gradle.properties
android.useAndroidX=true
android.enableJetifier=true

android.support.v4.content.FileProvider

修改AndroidManifest.xml:

1
2
3
4
5
// 修改前
android.support.v4.content.FileProvider

// 修改后
androidx.core.content.FileProvider

LIBCMTD库冲突

错误: 默认库"LIBCMTD"与其他库的使用冲突

解决方案:

  1. 项目属性 -> 配置属性 -> 链接器 -> 输入
  2. 忽略特定库:libcmtd.lib

Ninja构建错误

错误: CMake was unable to find a build program corresponding to "Ninja"

解决方案:

1
2
3
4
5
// 升级Gradle插件到3.3.2
classpath 'com.android.tools.build:gradle:3.3.2'

// buildToolsVersion使用28.0.3
buildToolsVersion "28.0.3"

缓存文件只读错误

错误: Could not read entry xxx from cache XXXX.bin

解决方案:

1
2
3
4
5
# 方法1:取消.bin文件的只读属性

# 方法2:删除.gradle缓存
rm -rf .gradle/
./gradlew clean

变体输出文件名冲突

错误: Several variant outputs are configured to use the same file name

解决方案:

1
2
3
4
5
# 搜索并删除冲突的xxxxx文件
find . -name "xxxxx" -delete

# 重新打包
./gradlew clean assembleRelease

cpu-features.c编译错误

错误: android/cpufeatures/cpu-features.c needed by obj/local/

原因: 使用不同版本的NDK编译过同一工程产生冲突

解决方案:

1
2
3
4
5
# 删除输出目录
rm -rf obj/

# 重新编译
./gradlew assembleRelease

Text must not be null or empty

解决方案:

  1. 重启Android Studio
  2. 切换Build Variants(如从debug切换为release)

SocketException Connection reset

错误: java.net.SocketException: Connection reset

解决方案:

  1. 设置NDK环境变量
  2. 移除build.gradle中硬编码的ndkVersion
  3. 打开Android Studio重新同步项目

SDK依赖更新

友盟SDK迁移

原依赖地址:

1
maven { url 'https://dl.bintray.com/umsdk/release' }

修改为:

1
maven { url 'https://repo1.maven.org/maven2/' }

性能优化

Gradle编译加速

1
2
3
4
5
6
# gradle.properties
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.daemon=true
org.gradle.caching=true

构建变体优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android {
buildTypes {
debug {
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

以上是我在2021年Android开发中遇到的一些问题和解决方案。