如何保持带有本机函数的类

时间:2018-06-22 13:27:12

标签: android proguard android-proguard

如果其中包含本机函数,是否可以告诉ProGuard完全跳过该类?

FragmentManager.beginTransaction().replace(holder, PreferencesFragment()).commit()

以上对我不起作用,因为它保留了类名和本机函数名,但混淆了其他成员

我想知道是否可以在不显式指定每个类的情况下将所有内容保留在此类中

谢谢

2 个答案:

答案 0 :(得分:2)

  

如果其中包含本机函数,是否可以告诉ProGuard完全跳过该类

使用以下规则:

-keepclasseswithmembers class com.your.packages.** {
    native <methods>;
}
-keepclassmembers class com.your.packages.** {
    native <methods>;
}

请注意,使用Proguard“完全跳过”类始终是个坏主意,因为它还可能间接保留某些类,这些类是从保留类的代码中使用的。相反,我建议使用以下模式:

-keepclasseswithmembers,allowshrinking,allowoptimization class com.your.packages.** {
    native <methods>;
}
-keepclassmembers class com.your.packages.** {
    native <methods>;
}

它将允许收缩和优化同一类中存在的非本机方法的代码。

您甚至可以做得更好:如果您的本机方法是通过名称解析的(例如,它们被称为Java_com_your_packages_methodName之类的名称),并且您没有使用RegisterNatives进行显式注册,则可以允许通过删除第二条规则缩小未使用的本机方法,这将只留下

-keepclasseswithmembers,allowshrinking,allowoptimization class com.your.packages.** {
    native <methods>;
}

如果您希望通过JNI访问某些类成员(例如,可以从本机代码中调用某些静态回调方法),则应明确地保留它们:用专门的注释为每个此类成员注释并使用基于注释的规则来保留它们:

-keepclassmembers,allowoptimization,includedescriptorclasses class com.your.packages.** {
    @android.support.annotation.Keep *;
}

您可以使用自己的注释来代替Android支持库中的注释-实际上,最好使用自己的注释,以避免受到来自Android Gradle插件或其他库的现有使用者规则的干扰。


通常,我建议您尽可能减少JNI和Java代码之间的冲突。如果您有从JNI调用的多个相关Java方法,请尝试将它们放在同一方法中:

@Keep
public static void callback(int action, String arg) {
    switch (action) {
        ...
    }
}

从Java代码中抛出异常(无论如何,您都将以反射方式调用其构造函数,因此也可以调用静态辅助方法):

@Keep
public static void throwException(int type, String message) {
    switch (type) {
        case 0:
            throw new BadThingsHappenedException(message);
        case 1:
            throw new AllHopeIsLostError();
        ...
    }
}

如果您有一个必须传递给JNI的类,请尝试传递单个字段而不是该类:

public final class DataClass {
    int intField;
    String stringField;

    public void doSomeNativeOperation() {
        JNI.doSomeNativeOperation(this);
    }
}

public final class JNI {
    public static void doSomeNativeOperation(DataClass arg) {
        doSomeNativeOperation0(arg.intField, arg.stringField);
    }

    private static native void doSomeNativeOperation0(int intField, String stringField);
}

如果您有本机对等类(一个类,与本机内存中的某些结构紧密相连),则可以在long字段中保留指向该类中本机结构的指针,并将该字段传递给本机方法。然后在本机方法中,将long转换为指针:

public final class Peer {
    long pointer;

    // constructor is invoked from JNI
    @Keep
    protected Peer(long pointer) {
        this.pointer = pointer;
    }

    public void doSomeNativeOperation() {
        JNI.doSomeNativeOperation(this);
    }
}

public final class JNI {
    public static void doSomeNativeOperation(Peer peer) {
        doSomeNativeOperation0(peer.pointer);
    }

    private static native void doSomeNativeOperation0(long pointer);
}

在本机代码中:

void JNIEXPORT Java_com_your_packages_methodName(JNIEnv* env, jobject type, jlong ptr) {
    struct my_struct peer = (struct my_struc*) (intptr_t) ptr;
    ...
}

这些简单的规则将使您能够使用JNI完全混淆任何大型应用程序,但单个小型类除外,该类包含所有native方法。

我建议您更进一步,推动本机回调引用的重新包装类。

代替

@Keep
public static void callback(YourCustomType arg) {
    ...
}

您可以通过将参数类型替换为Object来省略参数类型:

@Keep
public static void callback(Object arg) {
    // this cast won't make much difference in performance, but it makes huge
    // difference for Proguard!
    YourCustomType instance = (YourCustomType) arg;

    ...
}

这将使您混淆甚至重新包装各种类型的回调参数。

答案 1 :(得分:0)

使用-keep代替-keepclasseswithmembernames

-keep class * { native <methods>; }

有关更多信息:https://jebware.com/blog/?p=418

  

-keep会禁用ProGuard的所有功能。无收缩,无混淆;不是针对班级,不是针对会员。