保持从Android JNI调用的Java方法

8
我正在尝试通过Proguard使Android应用程序代码混淆。在经过Proguard处理后,应用程序本身可以正常运行,但是从C到Java的本地调用失败,并显示“java.lang.NoSuchMethodError”错误。
以下代码来自本地部分,其中对Java类实例EngineStarted进行了调用:
void callEngineStarted( JNIEnv* env, bool isStreamOne )
{
    jclass cls;
    if(isStreamOne == true) {
        cls = ( *env )->GetObjectClass( env, currentObjectOne );
    } else {
        cls = ( *env )->GetObjectClass( env, currentObjectTwo );
    }

    jmethodID midCallBack = ( *env )->GetMethodID( env, cls, "EngineStarted", "(I)V" );
    if (0 == midCallBack) {
        LOGW("Could not find EngineStarted method in class");
        return;
    }

    if(isStreamOne == true) {
        ( *env )->CallVoidMethod( env, currentObjectOne, midCallBack, 1 );
    } else {
        ( *env )->CallVoidMethod( env, currentObjectTwo, midCallBack, 0 );
    }
}

Java有这个方法。它只能从本地部分调用,而不能在其他任何地方调用。因此,Proguard正在删除该方法。
  public void EngineStarted ( int isStreamOne )
  {
    Log.v( "decoderService", "PDecoder - Engine started, using stream " + ( isStreamOne == 1 ? "one" : "two" ) );
    this.isStreamOne = isStreamOne == 1;

    // Initialize the player
    InitializePlayer( isStreamOne );
  }

我尝试将此添加到proguard-project.txt中,但并没有解决问题。
-keep class com.emrahgunduz.AppBase.Services.PlayService.players.pDecoders.PDecoderNative {
    void EngineStarted( int );
    void PositionChanged( int );
    void SetDuration( int );
    void Completed();
    void CompletedWithFade();
    void Spectrum ( *** );
}

编译后,mapping.txt文件中不包括方法,我怀疑是proguard将它们删除了。如何防止这些方法被删除和/或重命名?

编辑/解决方案:

我通过使用通配符更改完整位置来解决了这个问题。这样可以保留一些方法,但还不够。不知道为什么,其中一个被转储的方法(void InitializePlayer(int)) 也被转储了,这在某种程度上创建了连锁反应。添加此方法解决了剩余的丢失方法。最终解决方案如下:

-keepclassmembers class **.PDecoderNative {
    native <methods>;
    void InitializePlayer(int);
    void EngineStarted(int);
    void PositionChanged(int);
    void SetDuration(int);
    void Completed();
    void CompletedWithFade();
    void Spectrum(float[]);
}
编辑:问题不在于proguard,而是proguard有时无法读取project.txt文件。将整个项目移动到磁盘上的新位置并重新创建该文件。现在它完美地工作了。

可能是由于Proguard更改了方法名称。有一种选项可以省略1个或多个方法的优化,请参阅手册中使用>保留下的Keep选项。 - Alex Barker
您可以将更改后的名称记录到mapping.txt中,这是默认行为。类似于PDecoderNative getPrevious() -> e的项目还有很多,但我提到的方法没有出现在日志中。 - emrahgunduz
1个回答

6
你的分析是正确的,你的配置也看起来正确。你应该仔细检查你的类的全限定名(com.emrahgunduz.AppBase.Services.PlayService.players.pDecoders.PDecoderNative)。请注意,如果适用,你必须使用“$”而不是“.”来分隔内部类。
如果你已经指定了正确的名称,你将在ProGuard在Android构建过程中编写的文件proguard/seeds.txt中看到它们。
一旦这个工作正常,你可以用-keepclassmembers替换-keep。ProGuard仍然会保留方法名,但会混淆类名,在这种情况下很好。

谢谢回复。我检查了类的名称,一切正常。相同的名称也出现在usage.txt中,带有转储回调方法。我不知道为什么,但是完整使用类名会导致保留方法的转储。然而,将名称更改为**.PDecoderNative解决了问题。我将接受您的答案作为正确答案,因为我通过您的名称/包建议成功解决了问题。 - emrahgunduz
我无法想象为什么ProGuard会错误地解析类名而没有通配符。为了双重检查,您可以尝试将其从proguard/seeds.txt复制/粘贴到dexguard-project.txt中。您还应该在proguard/seeds.txt中看到保留的方法名称。 - Eric Lafortune
猜猜看,我在使用通配符后也遇到了同样的问题。将项目文件夹移动并从零开始重新创建project.txt文件。现在它可以在没有通配符的情况下工作。可能是文件被锁定或损坏,或者一些安全问题阻止proguard时不时地读取文件。我认为sdk或macosx的最后一次更新搞乱了我的代码。 - emrahgunduz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接