Adnroid 生成正式签名的 APK 文件

通过 Android Studio 将程序安装到手机上,实际是 Android Studio 会将程序代码打包成一个 APK 文件,然后将这个文件传输到手机上,最后再执行安装操作。

Android 系统会将所有的 APK 文件识别为应用程序的安装包,类似于 Windows 系统上的 EXE 文件。并且,只有签名后的 APK 文件才可以安装到手机上。而直接通过 Android Studio 运行程序会使用一个默认的 keystore 文件帮我们自动进行了签名。(点击 Android Studio 右侧工具栏的 Gradle -> 项目名 -> app -> Tasks -> android -> 双击 “signingReport”,查看这个 debug.keystore 文件具体内容)

正式发布的应用程序,还是需要一个正式的 keystore 文件来进行签名才行。


混淆

Proguard:Java 类

  • 文件压缩器
    • 压缩(Sharinking):默认开启,优化 Apk 的体积,移除无用的类和成员。
  • 代码优化
    • 优化(Optimizaion):主要体现在字节码上,让应用运行更快。
  • 代码混淆器
    • 混淆:类和成员随机命名,增加了反编译和阅读的难度,也可以使用 keep 保持不混淆。
  • 预校验器

混淆的规则

  • 关闭压缩:-dontshrink
  • 关闭优化:-dontoptimize
  • 关闭混淆:-dontobfuscate
    • -keep class com.example.xxx.* :表示保持该包下的类,但是子类依然会被混淆。
    • -keep class com.example.xxx.** :表示保持该包下的类,子类不会被混淆。
    • -keep class class com.example.xxx.*{*;} :想避免类中的成员不会混淆。

项目混淆

build.gradle(:app)
1
2
3
4
5
6
7
8
//打包配置
buildTypes {
release {
//是否混淆
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
proguard-rules.pro
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
95
96
97
98
99
100
101
102
#指定压缩级别(1-7
-optimizationpasses 5

#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers

#混淆时采用的算法(默认的)
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#把混淆类中的方法名也混淆了
#-useuniqueclassmembernames

#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification

#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
#保留行号
-keepattributes SourceFile,LineNumberTable
#保持泛型
-keepattributes Signature

#------------------------------------------------------------

#保持所有实现 Serializable 接口的类成员
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}

-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

-keepclassmembers class * implements android.os.Parcelable {
public <fields>;
private <fields>;
}

# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**

#Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment
-keep public class * extends androidx.fragment.app.Fragment

#Android SDK
#-keep class android.app.**{*;}
-keep public class * extends androidx.appcompat.app.AppCompatActivity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.preference.Preference
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.annotation.**
-keep public class * extends android.support.v7.**
-keep public class * extends androidx.**
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
-keep class android.support.** {*;}
-keep class androidx.** {*;}
-keep class android.**{*;}

# 自定义View
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}

# 自己的View
-keep class com.liuguilin.framework.view.**{*;}

#枚举
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

#保持native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}


# 第三方SDK
#---------------------------- ------------------------------


生成正式签名的 APK 文件

使用 AS 的可视化工具生成

  • Android App Bundle:创建 Android App Bundle 文件,用于上架 Google Play 商店的。(最后生成 .aab 后缀的签名文件)

    详细信息

    使用这种类型的文件,Google Play 可以根据用户的手机,只下发它需要的那部分程序资源。比如说一个高分辨率的手机,是没有必要下载低分辨率目录下的图片资源的,一个 arm 架构的手机,也没有必要下载 x86 架构下的 so 文件(so 文件是使用 C/C++ 代码开发的库文件)。因此,使用此类型文件可以显著地减少 App 的下载体积,但缺点是它不能直接安装到手机上,也不能用于上架除 Google Play 之外的其它应用程序。

  • APK:创建 APK 文件。(最后生成 .apk 后缀的签名文件)

创建 APK 文件举例:(不管创建哪种类型的文件,后面的流程基本上是一样的。)

点击 AS 导航栏上的 Build -> Generate Signed Bundle/APK ,选择 APK ,点击 “Next” 按钮,然后开始填入 keystore 文件的路径和密码。第一次使用则点击 “Create new” 按钮,然后填写创建 keystore 文件所必要的信息,根据自己的实际情况进行填写即可。

  • Key store path:存储签名文件的的路径地址
  • Password/Confirm:密码/确认密码
  • Key
    • Alias:别名
    • Password/Confirm:签名密码/确认签名密码
    • Validity(years):有效时长(单位:年。建议设置的长一些)
    • Certificate
      • First and Last Name:名字
      • Organizational Unit:组织或公司
      • Organization:组织
      • City or Locality:城市或地区
      • State or Province:州或省
      • Country Code(XX):国家代码(中国:cn)

图例:

填写完成后,点击 “OK”,然后刚才填写的信息会自动填充到创建签名 APK 的对话框中。如果希望以后都不用再输 keystore 的密码了,可以将 “Remember passwords” 选项勾上,然后点击 “Next” 来选择 APK 文件的输出地址。

  • Destination Folder:默认将 APK 文件生成到项目的 app 目录下
  • Build Veriants:选择构建类型
  • Signature Versions:这里将签名版本中的 V1 和 V2 选项同时勾上,表示会使用同时兼容新老版本系统的签名方式。(只使用 V1 则在 7.0 上不会使用更安全的安装方式。而只使用 V2 则 7.0 以下会显示未安装,因为 7.0 都开始使用 V2。V2 签名后无法更改。)

最后点击 “Finish” 按钮,然后稍等一段时间,APK 文件就会生成好了,并且会在右下角弹出提示(或在 Event Log 中查看提示)。点击提示上的 “locate” 可以立刻查看生成的 APK 文件,“analyze” 可以对 APK 文件进行分析 。

使用 Gradle 生成

Gradle 是一个非常先进的项目构建工具,在 Android Studio 中开发的所有项目都是使用它来构建的。

首先配置 app/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
android {

...

// gradle 是从上向下的,所以 signingConfigs 要在 buildTypes 的上面。因为在 buildTypes 中引用了 signingConfig signingConfigs.config。
// 在 android 闭包中添加了一个 signingConfigs 闭包
signingConfigs{
// 添加 config 闭包,在其中配置 keystore 文件的各种信息
config{
// 将敏感数据配置在一个独立的文件里面,然后在这里去读取这些数据
// 指定 keystore 文件的位置(需要一个已存在的文件,若果没有则去 create)
storeFile file(KEY_PATH)
// 指定密码(create 时填写的密码)
storePassword KEY_PASS
// 指定别名(create 时填写的别名)
keyAlias ALIAS_NAME
// 指定别名密码(create 时填写的密码)
keyPassword ALIAS_PASS
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

// 这样,当生成正式 APK 文件时,就会自动使用这个配置的签名信息来进行签名了。
signingConfig signingConfigs.config
}
}
}

为了避免将 keystore 的信息以明文的形式直接配置在 build.gradle 中,更推荐的做法是将这些敏感数据配置在一个独立的文件里面,这里则配置在 Android Studio 项目的根目录下的 gradle.properties 文件中,这个文件它是专门用来配置全局键值对数据的。(然后,如果要开源代码时,只需要将这个文件从 Git 版本控制中排除即可。)

1
2
3
4
5
# 配置 keystore 信息
KEY_PATH=/Users/jianghouren/jhr.jks
KEY_PASS=1234567
ALIAS_NAME=jhrdev
ALIAS_PASS=1234567

然后通过 Android Studio 中内置的 Gradle Tasks 来生成 APK 文件,点击右侧工具栏的 Gradle -> 项目名 -> app -> Tasks ->build,其中 assemble 就是用于生成 APK 文件的,它会同时生成 debug 和 release 两个版本的 APK 文件,只需要双击即可执行这个 Task。当提示 BUILD SUCCESSFUL 时说明 assemble 执行成功了,APK 文件会自动生成在 app/build/outputs/apk 目录下。

可通过 adb install apk路径 命令安装 apk(可直接将 apk 拖动到命令行中来生成路径。注意不要和手机已安装的冲突。)

1
2
3
4
// 卸载应用
adb uninstall com.example.app
//安装应用
adb install E:...

多渠道打包

1、通过 Gradle 来配置

AndroidManifest.xml
1
2
3
4
<!--渠道 ${APP_CHANNEL_VALUE} -->
<meta-data
android:name="APP_CHANNEL"
android:value="${APP_CHANNEL_VALUE}" />
build.gradle(:app)
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
android {
defaultConfig {
// Android Studio 3.0 之后多渠道需要配置方向
flavorDimensions "default"
...
}

...

// 渠道配置
productFlavors{
xiaomi {
manifestPlaceholders = [APP_CHANNEL_VALUE:"xiaomi"]
}
baidu {
manifestPlaceholders = [APP_CHANNEL_VALUE:"baidu"]
}
wandoujia {
manifestPlaceholders = [APP_CHANNEL_VALUE:"wandoujia"]
}
yingyongbao {
manifestPlaceholders = [APP_CHANNEL_VALUE:"yingyongbao"]
}
//...
}
}

然后通过 Android Studio 中内置的 Gradle Tasks 来生成 APK 文件,点击右侧工具栏的 Gradle -> 项目名 -> app -> Tasks ->build,其中包含了 xiaomi、baidu、wandoujia 等工具。其中 assemble 用于生成全部的 APK 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* FileName: FlavorHelper
* Profile: 多渠道帮助类
*/
public class FlavorHelper {

/**
* 获取渠道号
* @param mContext
* @return
*/
public static String getFlavor(Context mContext) {
PackageManager pm = mContext.getPackageManager();
try {
ApplicationInfo info = pm.getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = info.metaData;
return bundle.getString("APP_CHANNEL");
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return "";
}
}
1
2
3
4
5
6
7
8
9
10
11
public class BaseApp extends Application {

@Override
public void onCreate() {
super.onCreate();
// 获取渠道
String flavor = FlavorHelper.getFlavor(this);
// 打印出 App 的下载渠道
Toast.makeText(this, "flavor:" + flavor, Toast.LENGTH_SHORT).show();
}
}

2、也可以使用美团 Walle 来打包渠道包打包神器


加固

加固:Loader -> 校验,动态解密,内存保护,反调试等逻辑。 -> 加载和启动原程序逻辑

Apk 解压后会包含如下文件:(加固前)

  • axml:程序配置文件和资源文件
  • dex:字节码文件
  • arsc:编译后的资源
  • assets:未编码的文件
  • libs:so 文件

加固后:

assets:会生成一些 so 加固

会在我们清单文件中增加一条权限:READ SD CARD

增加一个 Application

加固的方式有多种,这里使用360加固保

360加固保也可以用来实现多渠道打包。


备注

参考资料

第一行代码(第3版)