Android 组件化混淆

我们项目使用组件化进行开发,app外壳+业务module+公用module,业务组module之间严禁互相依赖,业务module之间使用ARouter路由进行通信,业务module之间共同依赖一个名为dataprovider的公用module进行数据传递。 随着业务开发进入尾声,代码混淆提上了日程,Android的代码混淆有两种:R8 和 ProGuard。ProGuard 是一个免费的 Java 字节码的压缩,优化,混淆器。它删除没有用的类,字段,方法与属性。使字节码最大程度地优化,使用简短且无意义的名字来重命名类、字段和方法。在 Android Gradle 插件 3.4.0 版本之前默认使用 ProGuard 来优化字节码;开启方法也很简单,在app的 build.gradle 中添加 minifyEnabled true 即可:

android {
        ...
        buildTypes {
            release {
                shrinkResources true
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'),
                        'proguard-rules.pro'
            }
        }
    }

在使用 Android Gradle 插件 3.4.0 或更高版本构建项目时,该插件默认使用 R8 编译器而不是 ProGuard 来执行编译时代码优化。在 gradle.properties 文件中可以通过开关来切换:

android.enableR8=false
android.enableR8.libraries=false

事实上,R8 支持所有现有 ProGuard 规则文件,因此您在更新 Android Gradle 插件以使用 R8 时,无需更改现有规则,ProGuard 规则这里不细说了。在没使用组件化之前项目只有一个app module,这样混淆就比较简单,直接在app module 里创建 proguard-rules.pro 混淆文件就行了;使用了组件化后项目中有多个module,而且业务module和公共module之间还有依赖关系,那么该怎么混淆呢?

混淆方法

1)在app module中统一配置混淆规则

我们可以直接在app module中build.gradle文件配置所有module需要混淆的规则。这样,其他module中就无需开启混淆。但是并不推荐使用这种方法,当我们取消依赖某些module的时候,这样很容易造成混淆规则冗余,我们还需要删除掉该module相关的混淆配置,很麻烦。

2)各个module单独配置混淆规则(推荐)

我们也可以单独为module配置混淆规则,比较推荐这种做法。每个module管理自己的混淆文件,当我们不依赖该module的时候,就不会发生第一种方法出现的问题了。一般来说,在组件化开发的情况下,app module的代码是很少的,依赖的第三方库也是很少的。我们可以把通用的混淆规则放到 app module 中,这样子module就无需配置通用混淆规则,只需要配置一些该module所需要的混淆规则即可,大大减少了混淆代码。

我们把app module称作为主模块,其依赖的其他module称作为子模块。主模块的混淆开关配置会直接影响到子模块,也就是说如果你的主模块开启的混淆,就算你的子模块关闭混淆开关,最终子模块还是会被混淆的。子模块混淆文件的指定是通过consumerProguardFiles这个属性来指定的,并不是proguardFiles 属性,而且我们无需配置其他的选项,只需要配置consumerProguardFiles属性就可以。该属性表示在打包的时候会自动寻找该module下我们指定的混淆文件对代码进行混淆。

主module 通用规则
#
#-------------------------------------------基本不用动区域----------------------------------------------
#
#
# -----------------------------基本 -----------------------------
#

# 指定代码的压缩级别 0 - 7(指定代码进行迭代优化的次数,在Android里面默认是5,这条指令也只有在可以优化时起作用。)
-optimizationpasses 5
# 混淆时不会产生形形色色的类名(混淆时不使用大小写混合类名)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的库类(不跳过library中的非public的类)
-dontskipnonpubliclibraryclasses
# 指定不去忽略包可见的库类的成员
-dontskipnonpubliclibraryclassmembers
#不进行优化,建议使用此选项,
-dontoptimize
 # 不进行预校验,Android不需要,可加快混淆速度。
-dontpreverify


# 屏蔽警告
-ignorewarnings
# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保护代码中的Annotation不被混淆
-keepattributes *Annotation*
# 避免混淆泛型, 这在JSON实体映射时非常重要
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
 #优化时允许访问并修改有修饰符的类和类的成员,这可以提高优化步骤的结果。
# 比如,当内联一个公共的getter方法时,这也可能需要外地公共访问。
# 虽然java二进制规范不需要这个,要不然有的虚拟机处理这些代码会有问题。当有优化和使用-repackageclasses时才适用。
#指示语:不能用这个指令处理库中的代码,因为有的类和类成员没有设计成public ,而在api中可能变成public
-allowaccessmodification
#当有优化和使用-repackageclasses时才适用。
#-repackageclasses com.test

 # 混淆时记录日志(打印混淆的详细信息)
 # 这句话能够使我们的项目混淆后产生映射文件
 # 包含有类名->混淆后类名的映射关系
-verbose

#
# ----------------------------- 默认保留 -----------------------------
#
#----------------------------------------------------
# 保持哪些类不被混淆
#继承activity,application,service,broadcastReceiver,contentprovider....不进行混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.support.multidex.MultiDexApplication
-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 class android.support.** {*;}## 保留support下的所有类及其内部类

-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
#表示不混淆上面声明的类,最后这两个类我们基本也用不上,是接入Google原生的一些服务时使用的。
#----------------------------------------------------

# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**


#表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致
-keepclasseswithmembernames class * {
    native <methods>;
}


#这个主要是在layout 中写的onclick方法android:οnclick="onClick",不进行混淆
#表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,
#当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}

#表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#表示不混淆任何一个View中的setXxx()和getXxx()方法,
#因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-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);
}

#表示不混淆Parcelable实现类中的CREATOR字段,
#毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}
# 这指定了继承Serizalizable的类的如下成员不被移除混淆
-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();
}
# 保留R下面的资源
-keep class **.R$* {
 *;
}
#不混淆资源类下static的
-keepclassmembers class **.R$* {
    public static <fields>;
}



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

# 保留我们自定义控件(继承自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);
}

#
#----------------------------- WebView(项目中没有可以忽略) -----------------------------
#
#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);
}
#在app中与HTML5的JavaScript的交互进行特殊处理
#我们需要确保这些js要调用的原生方法不能够被混淆,于是我们需要做如下处理:
-keepclassmembers class com.ljd.example.JSInterface {
    <methods>;
}

#(可选)避免Log打印输出
-assumenosideeffects class android.util.Log {
   public static *** v(...);
   public static *** d(...);
   public static *** i(...);
   public static *** w(...);
 }
子module 配置
android {
    buildTypes {
        release {
            consumerProguardFiles 'consumer-rules.pro'
        }
    }
}

只需要配置该子module需要的混淆规则即可:
在这里插入图片描述

混淆规则

一般我们去网上查找混淆规则,大部分会得到这样的结果:

-keep class com.foo.library.** { *; } 

这样直接使用 -keep 规则是非常糟糕的,简单粗暴的使用 -keep 规则相当于直接屏蔽了 ProGuard 的所有优点,所以在混淆的过程中,不建议使用 -keep 规则(接入的第三方框架除外,因为毕竟是第三方框架还是遵守框架的规范)。
我们可以使用颗粒度更细的keep规则去进行混淆:
-keepclassmembers,-keepnames,-keepclassmembernames,-keepclasseswithmembers,
-keepclasseswithmembernames,这些规则的具体使用场景,请自行百度。

举个例子,我们app调用后台的api接口返回的是json格式的数据,app端使用gson把json解析成数据bean,这些数据bean的字段名肯定是不能被混淆的,否则json解析就会出错,在这种场景下我们使用了 -keepclassmembernames 规则。

//保持 com.jd.dy.pos.commodity.data 包下,所有数据bean类的成员变量不被重命名
-keepclassmembernames class com.jd.dy.pos.commodity.data.**{ <fields>;}

参考:

老外的一篇“不同keep混淆规则介绍”的文章

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 如果你在 Android 应用的其他模块中使用 AspectJ 组件,但发现它并不起作用,那么可能是因为你没有在其他模块中正确配置 AspectJ。 首先,确保你已经在其他模块中引入了 AspectJ 相关的依赖,并在模块的 build.gradle 文件中正确配置了 AspectJ。 其次,确保你在其他模块中的 AndroidManifest.xml 文件中正确声明了 AspectJ 注解处理器(Annotation Processor)。 最后,如果你的 AspectJ 代码是通过独立的 Jar 包引入的,那么还需要在其他模块的 build.gradle 文件中正确配置对 Jar 包的依赖。 如果你还是无法解决问题,建议你尝试检查一下你的 AspectJ 代码和配置,并确保它们是正确的。另外,也可以考虑寻求帮助,例如在社区论坛或者 Stack Overflow 上寻找相关的资料。 ### 回答2: 当Android项目使用AspectJ进行组件时,出现只在主module中生效而其他module不生效的情况,可能有以下原因: 1. 配置问题:在其他module中可能没有正确配置AspectJ的相关插件和依赖项。请确保每个module的build.gradle文件中都包含AspectJ的相关配置,如apply plugin: 'android-aspectj'和aspectjx的依赖项。 2. 编译顺序问题:AspectJ需要在编译时织入代码,如果其他module的依赖或编译顺序不正确,就无法正确织入代码。请确保每个module的依赖关系和编译顺序正确,主module依赖其他module,并且在编译时先编译其他module再编译主module。 3. 混淆问题:如果在项目中启用了混淆,而且AspectJ的相关代码没有正确地被排除在混淆范围之外,那么可能会导致AspectJ代码被混淆,从而失去织入的效果。请检查混淆规则文件(proguard-rules.pro)是否正确配置了AspectJ代码的排除规则。 4. 代码侵入问题:AspectJ需要对代码进行侵入式修改,如果其他module存在自定义的编译流程、插桩框架或其他对代码结构进行修改的工具,可能会与AspectJ产生冲突,导致失效。请确保其他module没有使用与AspectJ冲突的代码处理工具。 总结来说,要确保Android项目中的AspectJ组件能够在所有module中生效,需要正确配置AspectJ的相关插件和依赖项、合理配置依赖关系和编译顺序、正确排除AspectJ代码的混淆、避免与其他代码处理工具冲突。如果遇到问题,请检查上述原因,并根据实际情况进行调试和排查。 ### 回答3: 在Android中,当使用AspectJ进行组件时,可能会出现组件失效,只在主module中生效,而在其他module中不生效的情况。这种情况通常是由于以下几个原因导致的: 1. 配置问题:检查每个module的配置是否正确。确保在每个module的build.gradle文件中正确地包含AspectJ插件的引用和相关配置。确保在每个需要使用AspectJ的module中都添加了`apply plugin: 'android-aspectj'`。 2. 依赖关系问题:检查每个module的依赖关系是否正确。确保所有需要应用AspectJ的module都正确地依赖了其他module。特别是确保每个module都依赖了AspectJ的注入库。 3. 编译顺序问题:AspectJ需要在编译阶段对代码进行特殊处理,以实现AOP功能。确保主module的编译顺序在其他module之前,以确保AspectJ的注入能够正确地运行。 4. 组件框架限制:一些组件框架可能对AspectJ的支持存在限制或冲突。检查所使用的组件框架是否兼容AspectJ,或者是否存在配置冲突。 5. 其他问题:如果以上步骤都没有解决问题,可能存在其他更复杂的原因。可以通过查看Gradle的构建日志、检查代码和AspectJ插件的版本及相关文档等方式来进一步排查问题。 综上所述,当Android中使用AspectJ进行组件时,如果只在主module中生效,而其他module不生效,可以通过检查配置、依赖关系、编译顺序等方面的问题来解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值