Android中AndroidStudio目录结构 APP构建流程 Jenkins持续集成构建 Gradle Proguard混淆

版权声明:本文为博主原创文章,转载请标明出处 https://blog.csdn.net/qq_30993595/article/details/81544685

Android Studio目录结构

通常我们是将工程设置成project模式,这个模式下我们的工程有很多目录:

  • .gradle:包含一些Gradle编译脚本,gradle是Google推荐的编译工具
  • .idea:包含AndroidStudio开发工具需要的文件,里面有一些版权声明,库文件,配置文件等
  • .app:如果我们不修改module,那默认就算app,这是我们的项目文件,包含我们编写的代码,资源文件等
  • build:这个是我们的module编译后生成的文件
  • gradle:目录下的gradle-wrapper.jar是Gradle Wrapper的主体功能包,Gradle Wrapper是Google对gradle的一层封装,使得项目的开发人员不需要操心于 Gradle 的安装上,会自动为我们下载Gradle版本;还有有个gradle-wrapper.properties文件,描述工程所依赖的gradle的版本信息,distributionUrl表示如果我们的工程中没有gradle,软件会根据这个url去下载gradle
  • .gitignore:这个跟git工具的使用有关系,通常在这里配置一些不用上传的文件
  • build.gradle:这是项目全局的.gradle构建脚本
    其中的classpath地址是Android Plugin for Gradle的下载地址,也是从中央仓库扒下来的,gradle插件的版本号和gradle版本号是对应的,较新的插件版本需要要求较新版的gradle,Android Plugin for Gradle是一堆适合Android开发的Gradle插件的集合,主要由Google的Android团队开发,Gradle不是Android的专属构建系统,但是有了Android Plugin for Gradle的话,你会发现使用Gradle构建Android项目尤其的简单。
    “jcenter()”的意思是所有通过gradle导入的jar包都是从http://bintray.com/bintray/jcenter这个中央仓库上扒下来的。
    如果你需要的jar包在这个网站上没有,那就无法通过gradle的方式来导入哦。
  • gardle.properties:这个文件是全局的.gradle配置文件,从它的名字可以看出,这个文件中定义了一系列“属性”。实际上,这个文件中定义了一系列供build.gradle使用的常量,比如keystore的存储路径,keyalias,gradle运行所用内存等等
  • gradlew和gradlew.bat:这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在linux或者Mac系统中使用的shell脚本,gradlew.bat是在windows系统中使用的;gradlew是gradle wrapper的缩写,也就是说它对gradle的命令进行了包装,比如我们进入到指定Module目录并执行“gradlew.bat assemble”即可完成对当前Module的构建(Windows系统下)
  • local.properties:这个文件用于指定本机中的Android SDK路径,通常内容都是自动生成的,我们不需要去修改,除非本机的SDK路径发生了变化,那么就要将这个文件中的路径修改成新的位置路径即可
  • setting.gradle:可以在这里添加用于项目构建所需的module,通常不需要我们修改
  • xxx.iml:iml文件是所有IntelliJ IDEA都会自动生成的一个文件,(Android Studio是基于IntelliJ IDEA开发的),用于标识是一个IntelliJ IDEA项目,我们不需要修改这个文件中的任何内容

module内的目录

  • build:这里包含一些我们的module编译后的文件
  • lib:存放一些第三方jar文件和aar包
  • src:包含我们编写的代码和资源文件
  • build.gradle:整个module的有关的Gradle配置写在这里,包括编译sdk版本,adt版本等;对应的图形界面其实是Project Stucture。
    这里面有一些参数:

    buildToolsVersion: android构建工具的版本,其中包括了打包工具aapt、dx等等,这个工具的目录位于..your_sdk_path/build-tools/XX.XX.XX,通过SDK Manager 更新;在SDK Manager中安装选择版本,buildToolsVersion的版本需要>=CompileSdkVersion; 高版本的build-tools 可以构建低版本编译的android程序。

    minSdkVersion:指定项目最低兼容的版本,这里指定为15,表示最低兼容到Android 4.0系统

    targetSdkVersion:指定的值表示在该目标版本上已经做过充分测试,系统会为该应用启动一些对应该目标系统的最新功能特性,Android系统平台的行为变更,只有targetSdkVersion的属性值被设置为大于或等于该系统平台的API版本时,才会生效。例如,若指定targetSdkVersion值为22,则表示该程序最高只在Android5.1版本上做过充分测试,在Android6.0系统上(对应targetSdkVersion为23)拥有的新特性如系统运行时权限等功能就不会被启用。

    buildTypes:一般包含两个选项,一个是debug,用于指定生成测试版安装文件的配置,可以忽略不写;另一个是release,用于指定生成正式版安装文件的配置。其中minifyEnabled表明是否对代码进行混淆,true表示对代码进行混淆。proguardFiles指定混淆的规则文件,这里指定了proguard-android.txt文件和proguard-rules.pro文件两个文件,proguard-android.txt文件为默认的混淆文件,里面定义了一些通用的混淆规则。proguard-rules.pro文件位于当前项目的根目录下,可以在该文件中定义一些项目特有的混淆规则。

    dependencies:该闭包定义了项目的依赖关系,一般项目都有三种依赖方式:本地依赖、库依赖和远程依赖。本地依赖可以对本地的jar包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖可以对jcener库上的开源项目添加依赖关系。

  • proguard-rules.pro:这个是项目混淆配置文件

Android构建流程

当我们在Androd Studio里将写好的代码通过点击run app就可以在build/outputs/apk目录下生成一个APK文件,那一系列android代码是怎么变成这个文件的呢?先看一张官方提供的构建图
这里写图片描述

可以总结如下:

  1. 通过AAPT(Android Asset Packaging Tool)打包res资源文件,比如AndroidManifest.xml、xml布局文件等,并将这些xml文件编译为二进制,当然assets文件夹中的文件不会被编译,图片及raw文件夹中的资源也会保持原来的形态,需要注意的是raw文件夹中的资源也会生成资源id;AAPT编译完成之后会生成R.java文件。这里提到的AAPT是Android中的资源打包工具
  2. AIDL工具会将所有的aidl接口转化为对应的Java接口
  3. 将所有的java代码,上述步骤产生的R.java文件和aidl文件通过Java编译器编译成.class文件
  4. Dex工具会将上述过程产生的.class文件,第三方库和其它.class文件编译成.dex文件,.dex文件是Dalvik虚拟机可以执行的文件,.dex文件最终会被打包进APK文件
  5. 上一步编译生成的.dex文件、编译过的资源、无需编译的资源(如图片等)会被ApkBuilder工具打包成APK文件。
  6. 生成APK文件后,需要对其签名才可安装到设备,这里使用JarSigner工具进行签名,平时测试时会使用debug keystore,当正式发布应用时必须使用release版的keystore对应用进行签名。
  7. 如果是对APK正式签名,还需要使用zipalign工具对APK进行对齐操作,这样应用运行时会减少内存的开销,最终就能生成一个正式APK文件

这里提到了一个zipalign工具:

Zipalign是一个android平台上整理APK文件的工具,它首次被引入是在Android 1.6版本的SDK软件开发工具包中。它能够对打包的Android应用程序进行优化, 以使Android操作系统与应用程序之间的交互作用更有效率,这能够让应用程序和整个系统运行得更快。用Zipalign处理过的应用程序执行时间达到最低限度,当设备运行APK应用程序时占更少的RAM(Random Access Memory)随机访问内存,我们强烈推荐在新的和已经发布的程序上使用zipalign工具来得到优化后的版本。

原因

Zipalign对apk文件中未压缩的数据在4个字节边界上对齐,当资源文件通过内存映射对齐到4字节边界时,访问资源文件的代码才是有效率的。4字节对齐后,android系统就可以通过调用mmap函数读取文件,进程可以像读写内存一样对普通文件的操作,系统共享内存IPC,以在读取资源上获得较高的性能。 如果资源本身没有进行对齐处理,它就必须显式地读取它们——这个过程将会比较缓慢且会花费额外的内存

使用

  1. 可以在build.gradle文件中操作
    release {

    //对齐优化
    zipAlignEnabled true
    

    }

    debug {

    //对齐优化
    zipAlignEnabled false
    

    }

  2. 使用命令行
    zipalign工具一般在android-sdk-2.2\build-tools\其中一个版本目录下
    然后在dos窗口中定位到当前目录,使用zipalign -v 4 优化前的名字.apk 优化后的名字.apk
    其中-v 表示输出优化后的详细信息,4代表对齐为4个字节
  3. 验证一个apk有没有使用对齐优化
    使用如下命令:zipalign -c -v 4 source.apk ;如果最终出现了Verification succesful提示,就表明是优化过的apk

zipalign优化的最根本目的是帮助操作系统更高效率的根据请求索引资源,将resource-handling code统一将Data structure alignment(数据结构对齐标准:DSA)限定为4-byte boundaries;如果不采取对齐的标准,处理器无法准确和快速的在内存地址中定位相关资源。

Jenkins持续集成构建

平时开发中APP的打包和提交测试的工作基本上是由开发人员来完成的,随着需求越来越多,项目复杂度越来越大,迭代需求很快,这样打包时间就很长,并且从严格的研发流程来讲,开发人员应该只负责提交代码,这些工作应该由测试和运维人员来做,但是可能岗位有限等原因,没办法只能另外想办法了,所以如果有一个自动化构建的工具在后台不断的去更新代码,然后进行项目编译,APP打包,最后提交到测试平台,那这样就很完美了

没错,Jenkins就是这样一个持续集成工具,且开源;它不光可以进行Android平台的打包,也可以用来进行iOS打包、NodeJs打包、Jave服务打包等

官方地址为:https://jenkins.io/。Jenkins是使用Java开发的,官方提供一个war包,并且自带servlet容器,可以独立运行也可以放在Tomcat中运行。我们这里使用独立运行的方式,运行命令:java -jar jenkins.war

详细使用可参考https://blog.csdn.net/ncepudmx/article/details/77451314

Gradle

gradle是什么:

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置。简单理解就是Gradle是一个构建工具,它是用来帮助我们构建app的,构建包括编译、打包等过程。我们可以为Gradle指定构建规则,然后它就会根据我们的“命令”自动为我们构建APP,我们所使用的Android Studio就是使用Gradle来构建项目的

Android为什么使用Gradle来作为项目构建工具

在Gradle出现之前,有几个基于Java的构建工具:Ant、Maven,它们被应用于Java或者Android开发中,我们来看看它们

Ant

全称Another Neat Tool,它是由 James Duncan Davidson 开发的(Tomcat 最初的开发者),最初是用来构建 Tomcat。在2000年,Ant成为一个独立的项目并被发布出来。Ant 是由 Java 编写的构建工具,它的核心代码是由Java编写的,因此具有平台无关性,构建脚本是XML格式的(默认为bulid.xml),如果你熟悉XML,那么Ant 就比较容易上手,它的构建脚本如下

<?xml version="1.0" encoding="UTF-8"?>
<project name="test" default="hello">
    <echo message="running build.xml which is equivalent to build.gant"/>
    <property file="build.properties"/>    
    <target name="init"  description="init target" > 
        <echo message="Executing init target"/>
    </target> 
    <target name="hello" depends="init" description="say hello target"> 
        <echo message="${echo.msg}"/>
    </target>
</project>

Ant的构建脚本由三个基本元素组成:一个project(工程)、多个target(目标)和可用的task(任务)。
Ant有以下缺点
Ant无法获取运行时的信息。
XML作为构建脚本的语言,如果构建逻辑复杂,那么构建脚本就会又长又难以维护。
Ant需要配合Ivy(一种管理项目依赖工具),否则Ant很难管理依赖。
Ant在如何组织项目结构方面没有给出任何指导,这导致Ant虽然灵活性高,但这样的灵活导致每个构建脚本都是唯一的而且很难被理解。

Maven

Maven于2004年发布,它的目标是改进开发人员在使用Ant时面临的一些问题。Maven最初是为了简化Jakarta Turbine项目的构建,它经历了Maven到Maven3的发展,Maven作为后来者, 继承了Ant的项目构建功能, 同样采用了XML作为构建脚本的格式。Maven具有依赖管理和项目管理的功能,提供了中央仓库,能帮助我们自动下载库文件。
Maven的构建脚本的样式如下所示

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>Maven Quick Start Archetype</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Maven相比Ant的优点
Ant是过程式的,开发者需要显示的指定每个目标,以及完成该目标锁需要执行的任务。每一个项目,开发着都需要重新编写这一过程,这样会产生大量的重复。Maven是声明式的,项目的构建过程和过程中的各个阶段都由插件实现,开发者只需要声明项目的基本元素就可以了,这很大程度消除了重复。
Ant本身是没有依赖管理,需要配合Ivy来管理依赖,而Maven本身就提供了依赖管理。
Maven 使用约定而不是配置,它为工程提供了合理的默认行为,项目会知道去哪个目录寻找源代码以及构建运行时有那些任务去执行,如果你的项目遵从默认值,那么只需要写几行XML配置脚本就可以了。而Ant是使用配置且没有默认行为的。

Maven的缺点
Maven的提供了默认的结构和生命周期,这些可能不适合你的项目需求。
为Maven写定制的扩展过于累赘。
Maven的中央仓库比较混乱,当无法从中央仓库中得到需要的类库时,我们可以手工下载复制到本地仓库中,也可以建立组织内部的仓库服务器。
国内连接Maven的中央仓库比较慢,需要连接国内的Maven镜像仓库。
Maven缺乏文档,不便于使用和理解。

最后看看Gradle的构建脚本

apply plugin:'java'
group='com.mycompany.app'
archivesBaseName='my-app'
version='1.0-SNAPSHOT'

repositories{
   mavenCentral()
}

dependencies{
   testCompile 'junit:4.11'
}

Gradle优点:

  • 轻松的可拓展性:Gradle 有非常良好的拓展性。如果你想要在多个构建或者项目中分享可重用代码,Gradle的插件会帮助你实现。将Gradle插件应用于你的项目中,它会在你的项目构建过程中提供很多帮助:为你的添加项目的依赖的第三方库、为你的项目添加有用的默认设置和约定(源代码位置、单元测试代码位置)。其中Android Gradle插件继承Java Gradle插件,在本系列后续的文章会介绍插件的内容。
  • 采用了Groovy:Ant和Maven的构建脚本是由XML来编写的,如果XML逻辑复杂内容太多就不容易维护。Gradle可以使用Groovy DSL来实现构建脚本,Groovy 是基于Jvm一种动态语言,它的语法和Java非常相似并兼容Java,因此你无需担心学习Groovy的成本。Groovy在Java的基础上增加了很多动态类型和灵活的特性,比起XML,Gradle更具有表达性和可读性
  • 强大的依赖管理:Gradle提供了可配置的可靠的依赖管理方案。一旦依赖的库被下载并存储到本地缓存中,我们的其它项目就可以使用了。依赖管理很好的实现了在不同的平台和机器上产生相同的构建结果
  • 灵活的约定:Gradle可以为构建你的项目提供引导和默认值,如果你使用这种约定,你的Gradle构建脚本不会有几行。比起Ant,Gradle不仅仅提供了约定,还可以让你轻松的打破约定
  • Gradle Wrapper:Gradle Wrapper是对Gradle 的包装,它的作用是简化Gradle本身的下载、安装和构建,比如它会在我们没有安装Gradle的情况下,去下载指定版本的Gradle并进行构建。Gradle的版本很多,所以有可能出现版本兼容的问题,这时就需要Gradle Wrapper去统一Gradle的版本,避免开发团队因为Gradle版本不一致而产生问题

就这样Android就选择了Gradle来作为app的构建工具了

一个常用的构建脚本build.gradle

apply plugin: 'com.android.application'


android {
    compileSdkVersion 22
    buildToolsVersion "25.0.1"

    defaultConfig {
        applicationId "com.mango.yang"
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.00build005"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true//dex突破65535的限制
    }

    signingConfigs {   //签名配置
        release {
            keyAlias 'keyAlias '  //别名
            keyPassword 'keyAlias ' //密码
            storeFile file('.\\keyAlias .keystore') //路径一般放在module的根路径下面
            storePassword 'keyAlias '
        }
    }


    buildTypes {//构建app
        debug {//测试版本
            minifyEnabled false
        }
        release {//正式版本
            pseudoLocalesEnabled true //如果没有提供混淆规则文件,则设置默认的混淆规则文件
            signingConfig signingConfigs.release //引用签名信息
            buildConfigField "boolean", "LOG_DEBUG", "false"// 不显示Log
            zipAlignEnabled true // zip align(对齐,排列)优化。
            shrinkResources true // 去掉没有用的资源文件
            minifyEnabled true//开启混淆
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'//引用混淆配置文件
        }

    }

    packagingOptions {
        exclude 'META-INF/ASL2.0'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/MANIFEST.MF'
    }
    //如果有AIDL文件
    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }

    //忽略Lint错误 有时候会由于Lint错误而终止。当这些错误来自第三方库中时,我们往往想要忽略这些错误从而继续构建进程
    lintOptions {
        abortOnError false
        disable "ResourceType"
    }

    //多渠道打包
    productFlavors {
        hsq{}
        hsq_dx{}
        hsq_wx{}
        baidu{}
        yingyongbao{}
        ppzhushou{}
        anzhi{}
        zhushou360{}
        huawei{}
        lenovomm{}
        wandoujia{}
        mumayi{}
        meizu{}
        youyi{}
        sougou{}
    }

    // 批量渠道包值替换(有些第三方库中需要用到渠道名)
    productFlavors.all { flavor ->
        // 友盟、极光推送渠道包, UMENG_CHANNEL 是根据你AndroidManifest.xml来配置的,请看下面。
        flavor.manifestPlaceholders = [UMENG_CHANNEL: name, JPUSH_CHANNEL: name]
    }

}

dependencies {//依赖
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:support-v4:22.+'
    compile project(':videoplayer')
    compile files('libs/butterknife-7.0.1.jar')
    .....
}

Proguard

说到项目构建就不得不说代码混淆配置了,那proguard是什么呢?

ProGuard是一个混淆代码的开源项目,ProGuard功能如下

  • 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)
  • 优化(Optimize):对字节码进行优化,移除无用的指令
  • 混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名
  • 预检(Preveirfy):主要是在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。

总而言之,Proguard可以移除代码中的无用类,字段,方法和属性同时可以混淆(类,字段,方法,属性的)命名。最终结果可以使我们的apk文件体积更加小,也会让我们的apk更加难以被他人逆向工程,这对于那些特别时包含一些安全性功能的apk来说是相当重要的

这样就可以回答我们为什么要使用Proguard混淆?

Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java 字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java 源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。被混淆过的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆前一样,只是混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,在缺乏相应的函数名和程序注释的况下,即使被反编译,也将难以阅读。同时混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。

Proguard工作原理

proguard工作流程图
这里写图片描述

混淆就是移除没有用到的代码,然后对代码里面的类、变量、方法重命名为人可读性很差的简短名字。
那么有一个问题,ProGuard怎么知道这个代码没有被用到呢?
这里引入一个Entry Point(入口点)概念,Entry Point是在ProGuard过程中不会被处理的类或方法。在压缩的步骤中,ProGuard会从上述的Entry Point开始递归遍历,搜索哪些类和类的成员在使用,对于没有被使用的类和类的成员,就会在压缩段丢弃,在接下来的优化过程中,那些非Entry Point的类、方法都会被设置为private、static或final,不使用的参数会被移除,此外,有些方法会被标记为内联的,在混淆的步骤中,ProGuard会对非Entry Point的类和方法进行重命名。
那么这个入口点怎么来呢?就是从ProGuard的配置文件来,只要这个配置了,那么就不会被移除。

对于ProGuard的原理更详细的介绍可以参考Proguard使用手册

如何编写Proguard混淆文件呢

一般三步走

  • 基本混淆
  • 针对APP的量身定制
  • 针对第三方jar包的解决方案
基本混淆

混淆文件的基本配置信息,可以当做模板给每个APP使用

 #指定代码压缩比 在0~7之间,默认为5,一般不需要改
 -optimizationpasses 5 
 #混淆时不使用大小写混合,混淆后类名称为小写
 -dontusemixedcaseclassnames
 #告诉Proguard 不要跳过对非公开类的处理,默认是跳过,如果应用程序引入的有jar包,并且混淆jar包里面的class
 -dontskipnonpubliclibraryclasses
 #混淆第三方库类的成员
 -dontskipnonpubliclibraryclassmembers
 #不做预校验,preverify是proguard的4个步骤之一,Android不需要preverify,去掉这一步可加快混淆速度
 -dontpreverify
 # 混淆时记录日志,混淆后就会生成映射文件,包含有 类名->混淆后类名 的映射关系
 # 然后使用printmapping指定映射文件的名称
 -verbose
 -printmapping proguardMapping.txt
 # 指定混淆时采用的算法,后面的参数是一个过滤器
 # 这个过滤器是谷歌推荐的算法,一般不改变
 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

# 假如项目中有用到注解,应加入这行配置,对JSON实体映射也很重要,比如fastJson
-keepattributes *Annotation*
#类型转换错误 添加如下代码以便过滤泛型(不写可能会出现类型转换错误,一般情况把这个加上就是了),即避免泛型被混淆
-keepattributes Signature
# 抛出异常时保留代码行号,在异常分析中可以方便定位
-keepattributes SourceFile,LineNumberTable,Exceptions

#保留我们使用的四大组件,自定义的Application等等这些类不被混淆
#因为这些子类都有可能被外部调用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-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 * extends android.app.Fragment
-keep public class com.android.vending.licensing.ILicensingService

#保留support下的所有类及其内部类不被混淆
-keep class android.support.** {*;}

#保留R下面的资源
-keep class **.R$* {*;}

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

#保持指定规则的方法不被混淆(Android layout 布局文件中为控件配置的onClick方法不能混淆)
-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);
}

# 保留Parcelable序列化的类不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}
#需要序列化和反序列化的类不能被混淆(注:Java反射用到的类也不能被混淆)
-keepnames class * implements java.io.Serializable
#保护实现接口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();
}

#对于带有回调函数onXXEvent的不被混淆
-keepclassmembers class * {
    void *(**On*Event);
}

对于-dontusemixedcaseclassnames这里有点要说明一下,proguard会默认我们的操作系统能够区分大小写字母的文件,如b.java和B.java会被认为是两个不同的文件,但是window系统并不这样认为(window系统对大小写不敏感的)。因此在window系统下必须在proguard文件中指明-dontusemixedcaseclassnames选项。如果没这样做并且我们的项目中类的数量超过26个的话,那么proguard就会默认混用大小写文件名,进而导致class文件相互覆盖。所以为了安全起见,我们都默认设置该选项。

针对APP的量身定制
#实体类不要混淆,项目中实体类基本上在同一个包下,下面这样写一次就够了
-keep public class com.mango.yang.entity.** {
    public void set*(***);
    public *** get*();
    public *** is*();
}

# 保留内部类不被混淆,$符号就是用来分割内部类与外部类的标志,这里只是举个例子,保留MainActivity下的内部类不被混淆
-keep class com.mango.yang.MainActivity$* { *; }

#对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);
}

#保留addJavaScriptInterface方法注入java对象不被混淆,这里JSInterface 你可以取自己的名称,包名也是
-keepclassmembers class com.mango.yang.JS.JSInterface {
    <methods>;
}
#保留反射类
-keep class com.mango.yang.reflex.** { *; }
针对第三方jar包的解决方案

这里分两种情况,一种是我们用的第三方开源库,另一种是我们使用的第三方的SDK。开源库是没有必要混淆的,因为源码都是开源的,大家都可以用的代码。而第三方SDK也没有必要混淆,因为这些SDK都是经过混淆的,所以对于这些SDK我们要做的是避免这些SDK的类和方法在我们的app代码中被混淆

# 不混淆 GSON
-keep class com.google.gson.** { *; }
-keep class com.google.gson.JsonObject {*;}
#支付宝
-libraryjars libs/alipaysdk.jar
-dontwarn com.alipay.android.app.**
-keep public class com.alipay.**  { *; }

#极光推送
-dontoptimize
-dontpreverify

-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }

#EventBus
-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}

#retroift
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions

#ButterKnife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }

-keepclasseswithmembernames class * {
    @butterknife.* <fields>;
}

-keepclasseswithmembernames class * {
    @butterknife.* <methods>;
}

#fastjson
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.** { *; }

#rxjava
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
 long producerIndex;
 long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
 rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
 rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
混淆的几点建议
  • 尽量在项目一开始的时候就把混淆配置加上去,然后打包给测试的时候发有混淆规则的APK,这样能把问题在一开始就解决掉;笔者有次是在项目开发完毕才把混淆规则加上去,结果出问题的时候难弄的很
  • 不要使用-ignorewarnings语句,要是忽略掉所有Log,我们在打包时候就没办法查看了,可能会看不到一些严重的错误
  • 打包的时候可能会碰到第三方库出现的could not reference class之类的warning信息,如果你确定APP的运行和那些没找到的类没关系,可以添加-dontwarn 标签,比如-dontwarn com.alibaba.fastjson.**
  • 别混淆了自定义的view类,实体类,反射类,否则运行时可能会抛空指针异常

猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/81544685