APP签名打包与代码混淆

一、签名文件的生成

    打开Android Studio选择Build->Generate Singed Apk,如果有签名文件则选择签名文件路径,如果没有则进行创建create new...进行创建,如下:


    在生成签名的过程中需要注意Signature Version V1 V2 两个选项:

  • 只勾选V1签名:不会影响什么,但是在7.0上不会使用更安全的验证方式;
  • 只勾选V2签名:7.0以下会直接安装完显示未安装,7.0以上则使用了V2的方式验证;
  • 同时勾选V1、V2则所有的机型都没有问题。


    为了避免忘记了选择某一项可以在app的build.gradle的android标签下加入如下配置,默认帮我们勾选上。

    signingConfigs {
            debug {
                v1SigningEnabled true
                v2SigningEnabled true
            }
            release {
                v1SigningEnabled true
                v2SigningEnabled true
            }
        }

二、手动多渠道打包

1、在清单文件中配置meta-data标签信息

    <meta-data
        android:name="CHANNEL_NAME"
        android:value="${CHANNEL_VALUE}"/>

2、在APP的Build.gradle中配置productFlavors渠道

	productFlavors {
		xiaomi {
			manifestPlaceholders = [CHANNEL_VALUE: "xiaomi"]
		}
		huawei {
			manifestPlaceholders = [CHANNEL_VALUE: "huawei"]
		}
		zhushou360 {
			manifestPlaceholders = [CHANNEL_VALUE: "zhushou360"]
		}
		vivo {
			manifestPlaceholders = [CHANNEL_VALUE: "vivo"]
		}
		oppo {
			manifestPlaceholders = [CHANNEL_VALUE: "oppo"]
		}
		yingyongbao {
			manifestPlaceholders = [CHANNEL_VALUE: "yingyongbao"]
		}
	}

3、打包

    选择配置信息里面需要的打包渠道,进行打包:



三、命令行多渠道打包

1、打包信息配置

    命令多渠道打包需要在gradle中配置好打包的信息,包括release打包信息和debug打包信息,需要在signingConfigs中进行配置。

    signingConfigs {
        //release  debug配置信息:用于命令打包,图形化界面手动打包不需要此
        debug {
            keyAlias 'androiddebugkey'
            keyPassword 'android'
            storeFile file('../debug.keystore')
            storePassword 'android'
        }
        release {
            storeFile file("../appkey.jks")
            keyAlias "key0"
            storePassword "123456"
            keyPassword "123456"
        
  •     keyAlias:文件别名
  •     keyPassword、storePassword:密码
  •     storeFile:文件路径

    注意:debug签名文件位于C:\Users\user\.android下面,需要把它复制到项目的根目录下

2、apk包输入目录设定

    同样包括包括release打包信息和debug包,在gradle的buildTypes下配置。

    buildTypes {
        debug {
            // 显示Log
            buildConfigField "boolean", "LOG_DEBUG", "true"
            //混淆
            minifyEnabled false
            //Zipalign优化
            zipAlignEnabled false
            // 移除无用的resource文件
            shrinkResources false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //签名
            signingConfig signingConfigs.debug

            // 自定义输出配置
            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith('.apk')) {
                        // 输出apk名称为
                        def fileName = "debug_${releaseTime()}_${variant.productFlavors[0].name}_${defaultConfig.versionName}.apk"
                        output.outputFile = new File(outputFile.parent, fileName)
                    }
                }
            }
        }

        release {
            // 显示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"
            //混淆
            minifyEnabled true
            //Zipalign优化
            zipAlignEnabled true
            // 移除无用的resource文件
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //签名
            signingConfig signingConfigs.release

            // 自定义输出配置
            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith('.apk')) {
                        // 输出apk名称为
                        def fileName = "relrese_${releaseTime()}_${variant.productFlavors[0].name}_${defaultConfig.versionName}.apk"
                        output.outputFile = new File(outputFile.parent, fileName)
                    }
                }
            }
        }
    }

3、命令打包

    在AndroidStudio窗口左下角打开Terminal面板,输入gradlew assembleRelease命令,会自动完成release打包,输入gradlew assembleDebug命令,会自动完成debug打包,显示:BUILD SUCCESSFUL表明打包完成,可以在outputs/apk里面查看。


debug包


release包

    这样打包存在一个弊端,就是把密码和别名写在signingConfigs里面,当提交代码到远程服务器的时候很容易被别人窃取信息,很不安全可以把账户密码等信息写在一个配置文件里面,然后再gradle中获取,这时候当提交代码的时候,把这个文件过滤掉,被人就查看不到了,这里不再进一步的进行说明。

4、注意事项

在命令打包过程中可能出现以下问题:


如果执行命令打包出现以上状况则需要下载gradle,重试一两次,还不行了话建议连接手机移动网络进行下载,亲测可以,如下:


等待下载完毕后就可以愉快的进行命令行打包了。

四、美团多渠道打包方案

    直接解压apk,解压后的根目录会有一个META-INF目录,在META-INF目录内添加空文件,通过为不同渠道的应用添加不同名称的空文件,可以唯一标识一个渠道,之后就可以在Java代码中读取空渠道文件名称获取不同渠道名称。

    但是在Android7.0之后这中方案不行了,Google为了保证安装包的安全性,在打包的时候,配置了一个Signature V2可选择的打包配置项,该配置能提供更快的应用安装时间、安装的时候会严格的校验完整性,如果有一点变动就是安装失败。美团又开源了新的一款自动化打包工具Walle。

1、原理

    新的签名方案中整个APK(ZIP文件格式)会被分为下四个区块,其中区块2保存签名信息是不受保护的,而其他三个是否保护的,区块2中ID-value进行扩展,扩展自定义的渠道id,并保存在APK中,安装apk文件时候是忽略这个区块2的检验的在安装后运行时候可以通过相关代码获取到ID-value的信息,进而进行渠道区分。

2、组成

  • 用于写入ID-value信息的Java类库
  • Gradle构建插件用来和Android的打包流程进行结合
  • 用于读取ID-value信息的Java类库
  • 用于供com.android.application使用的读取渠道信息的AAR

3、集成

    (1)在位于项目的根目录 build.gradle 文件中添加Walle Gradle插件的依赖, 如下:

      buildscript {
          dependencies {
              classpath 'com.meituan.android.walle:plugin:1.1.6'
          }
      }

    (2)并在当前App的 build.gradle 文件中apply这个插件,并添加上用于读取渠道号的AAR

       apply plugin: 'walle'

       dependencies {
           compile 'com.meituan.android.walle:library:1.1.6'
       }

    (3)设置渠道包列表:channel

    (4)配置插件

    walle {
        // 指定渠道包的输出路径
        apkOutputFolder = new File("${project.buildDir}/outputs/channels");
        // 定制渠道包的APK的文件名称
        apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
        // 渠道配置文件
        channelFile = new File("${project.getProjectDir()}/channel")
    }

    (5)输出渠道包

生成渠道包 ./gradlew clean assembleReleaseChannels

如下:


    源码地址:https://download.csdn.net/download/yoonerloop/10385608点击打开链接

五、腾讯多渠道打包方案

    腾讯多渠道打包方案为VasDolly,原理和美团类似,不再介绍,直接上集成步骤。

1、在根工程的build.gradle中,添加对打包Plugin的依赖:

dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        classpath 'com.leon.channel:plugin:1.1.7'
}

2、在主App工程的build.gradle中,添加对VasDolly Plugin的引用:

apply plugin: 'channel'

3、在主App工程的build.gradle中,添加读取渠道信息的helper类库依赖:

dependencies {
    api 'com.leon.channel:helper:1.1.7'
}

4、配置渠道列表

在gradle.properties文件指定渠道文件名称,该渠道文件必须位于根工程目录下,一行一个渠道信息。

channel_file=channel.txt

5、配置渠道包路径和输出目录

channel{
    //指定渠道文件
    channelFile = file("/Users/leon/Downloads/testChannel.txt")
     //多渠道包的输出目录,默认为new File(project.buildDir,"channel")
    baseOutputDir = new File(project.buildDir,"xxx")
    //多渠道包的命名规则,默认为:${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}
    apkNameFormat ='${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}'
    //快速模式:生成渠道包时不进行校验(速度可以提升10倍以上)
    isFastMode = false
}

6、输出渠道包

通过gradle channelDebug、gradle channelRelease命令分别生成Debug和Release的多渠道包。如下:



源码地址:https://download.csdn.net/download/yoonerloop/10385708点击打开链接

六、360加固

    360加固是一个非常方便简洁的加固工具,无需额外的开发成本,按照步骤上传应用到官网就可以快速完成加固发布,关于360加固的流程在这里不再叙述。需要注意以下几点:

  1. 加固前必须对apk包进行签名。
  2. 加固前不需要对代码进行混淆。
  3. 加固后apk文件包变大。

七、代码混淆

    代码混淆的目的就是防止应用被破解、反编译、二次打包、恶意篡改,保护应用数据信息不会被黑客窃取。在打包的配置项中“minifyEnabled true”表示启动代码混淆,在proguard-android.txt文件中配置混淆过滤项。

  1. 四大组件不能混淆
  2. support.v4/v7包不混淆
  3. Serializable实现类、枚举类不被混淆
  4. 自定义组件不被混淆
  5. 资源类不被混淆
  6. 引用的第三方资源jar/SDK/库文件等不能混淆

    经过混淆后打包和原来为混淆的debug包进行进行简单的反编译对比,很容易的看出混淆后的代码更加安全,而未混淆的代码就很容易的看到完整的源代码。如下效果图:


    需要记住的是反编译只是增加了破解apk的难度,使其破解、反编译、二次打包、恶意篡改的难度增加,由于一些代码在混淆中没有被混淆掉,依然增加被破解的风险,因此在开发中应对重要的数据注意代码的安全加密,打包的时候使用更加安全的加固手段。

    最后附上常用的代码混淆内容和build.gralde文件内容:

    1、代码混淆内容

#基本指令区
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-verbose
-ignorewarning
-printmapping proguardMapping.txt
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-keepattributes *Annotation*,InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable

#默认保留区
-keep public class * extends android.app.Activity
-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.content.ContentProvider
-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.** {*;}

-keepclasseswithmembernames class * {
    native <methods>;
}
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
-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);
}
-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}
-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 **.R$* {
 *;
}
-keepclassmembers class * {
    void *(**On*Event);
}

#webview
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, jav.lang.String);
}

#第三方库、jar、SDK
# …………………………………………………………………根据实际项目写……………………………………………………………………………………………

    2、build.gralde文件内容

pply plugin: 'com.android.application'

android {

    compileSdkVersion 26
    buildToolsVersion "26.0.2"

    //默认配置信息
    defaultConfig {
        applicationId "com.channel.channepackage"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    //签名配置信息
    signingConfigs {
        //为了避免忘记了选择Signature Version V1 V2引起安装错误,配置此信息可以一路next
        debug {
            v1SigningEnabled true
            v2SigningEnabled true
        }
        release {
            v1SigningEnabled true
            v2SigningEnabled true
        }

        //release  debug配置信息:用于命令打包,手动打包不需要此debug.keystore
        debug {
            keyAlias 'androiddebugkey'
            keyPassword 'android'
            storeFile file('../debug.keystore')
            storePassword 'android'
        }
        release {
            storeFile file("../appkey.jks")
            keyAlias "key0"
            storePassword "123456"
            keyPassword "123456"
        }
    }

    //打包渠道类型
    productFlavors {
        xiaomi {
            manifestPlaceholders = [CHANNEL_VALUE: "xiaomi"]
        }
        huawei {
            manifestPlaceholders = [CHANNEL_VALUE: "huawei"]
        }
        zhushou360 {
            manifestPlaceholders = [CHANNEL_VALUE: "zhushou360"]
        }
        vivo {
            manifestPlaceholders = [CHANNEL_VALUE: "vivo"]
        }
        oppo {
            manifestPlaceholders = [CHANNEL_VALUE: "oppo"]
        }
        yingyongbao {
            manifestPlaceholders = [CHANNEL_VALUE: "yingyongbao"]
        }
    }

    //打包输出目录
    buildTypes {
        debug {
            // 显示Log
            buildConfigField "boolean", "LOG_DEBUG", "true"
            //混淆
            minifyEnabled false
            //Zipalign优化
            zipAlignEnabled false
            // 移除无用的resource文件
            shrinkResources false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //签名
            signingConfig signingConfigs.debug
        }

        release {
            // 显示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"
            //混淆
            minifyEnabled true
            //Zipalign优化
            zipAlignEnabled true
            // 移除无用的resource文件
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //签名
            signingConfig signingConfigs.release
        }

        // 自定义输出配置
        applicationVariants.all { variant ->
            variant.outputs.each { output ->
                def outputFile = output.outputFile
                if (outputFile != null && outputFile.name.endsWith('.apk')) {
                    // 输出apk名称为
                    outputFile.name.substring(3,6)
                    def fileName = buildType.name+"_${releaseTime()}_${variant.productFlavors[0].name}_${defaultConfig.versionName}.apk"
                    output.outputFile = new File(outputFile.parent,fileName)
                }
            }
        }
    }
}

def releaseTime() {
    return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
}


猜你喜欢

转载自blog.csdn.net/yoonerloop/article/details/79618939