5

I am trying to use proguard with my android app and am using the samsung accesory sdk which keeps giving be trouble.

No matter what I try in the proguard configuration I can't seem to get past this runtime exception:

07-21 13:44:12.851: E/SAAgent(3563): <init> []

07-21 13:44:12.851: E/SAAgent(3563): java.lang.NoSuchMethodException: <init> []

...

07-21 13:44:12.851: E/AndroidRuntime(3563): Caused by: java.lang.RuntimeException: Invalid implemetation of SASocket. Provider a public default constructor.

...

Does anyone have any idea on what to try?

tomrozb
  • 25,773
  • 31
  • 101
  • 122

3 Answers3

5

The problem is that, with some optimization turned on, Proguard will change every inner class in a top-level class.

This means that the default constructor of the inner class will be exchanged with an one-parameter constructor which takes the instance of the outer class, because in java an inner class keeps a reference to the outer class.

The Samsung Accesory SDK requires a default constructor for the SASocket inner class implementation because I guess they use reflection to instantiate that object.

Here http://sourceforge.net/p/proguard/bugs/387/ you can read that: "Outer$Inner is not changed to a top-level class, unless you also add -repackageclasses and -allowaccessmodification to the configuration".

Unfortunately those flags are inherited usually from proguard-android-optimize.txt and if you want to keep the optimization on, the solution is adding to your proguard config:

-keepattributes InnerClasses

Please, notice, that in order to be able to use the whole functions of the Samsung Accesory SDK you should also include the following rules:

# Based on http://proguard.sourceforge.net/manual/examples.html#library 

-keep public class com.samsung.** { 
    public protected *; 
}   

-keepclassmembernames class com.samsung.** {    
    java.lang.Class class$(java.lang.String);   
    java.lang.Class class$(java.lang.String, boolean);  
}   

-keepclasseswithmembernames class com.samsung.** {  
    native <methods>;   
}   

-keepclassmembers enum com.samsung.** { 
    public static **[] values();    
    public static ** valueOf(java.lang.String); 
}   

-keepclassmembers class com.samsung.** 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(); 
}
Luigi Massa Gallerano
  • 2,347
  • 4
  • 23
  • 25
3

Just adding this answer as an alternative and update to the already existing answer.

Having recently integrated Samsung's Accessory SDK into an Android 'companion' app to support a Tizen app on the Galaxy Gear S2, I ran into the same problem when compiling a (minified) release build of the companion app.

@while's answer already explains the cause and remedy of the NoSuchMethodException thrown. However, I found the outlined Proguard rules to be rather relaxed and numerous. This may have necessary for older versions of the Accessory SDK, but these days you can probably do with far less exclusions.

Assuming you're using one of the more recent Accessory SDK versions (I tested both 2.2.2 and 2.3.0), you'll still want to start with:

-keepattributes InnerClasses 

You can't get around this one, because the SAAgent uses reflection to instantiate an instance of the SASocket you implement somewhere in your own code. This rule ensures that the relationship between (and naming of) the inner and outer classes doesn't change.

Now, you may be tempted to write a rule to keep the default constructor of your SASocket implementation by adding an exclusion for <init>(). Unfortunately, that won't work, because as part of the code optimisation Proguard will actually create a parameterised constructor in the inner class that accepts an instance of the outer class. As a result, that constructor isn't kept and stripped away because Proguard thinks no one is calling it.

So, long story short, to keep both your SASocket implementation and its constructor(s), add a rule:

-keep class * extends com.samsung.android.sdk.accessory.SASocket { <init>(...); }

Up until this far, your app was probably crashing at runtime without the above rules. Having added them, this should no longer be the case. However, you'll notice that the Accessory SDK still logs various errors and that your app isn't yet working as intended. Inspecting Logcat, you should see errors indicating that the SDK failed to bind to your service.

The cause for this may not be obvious, but if you dig around the Accessory SDK, you'll notice some IInterface and Binderextensions (i.e. in IDeathCallback and ISAFrameworkManager (2.2.2) or ISAFrameworkManagerV2 (2.3.0)). Since Proguard can't find any explicit calls to them and doesn't know these are in fact invoked at runtime by the Android framework, it'll strip them away. So, let's add a rule to stop Proguard from doing that:

-keep class com.samsung.accessory.api.* extends android.os.Binder { *; }

After this, it's time for congratulations: your service should now be bindable again. Depending on your implementation you may require further exceptions, but for a basic setup, above should do the trick.

Adding it all up, you should have the following rules in your config:

# 
# Samsung Accessory SDK Proguard Rules
# 

# Keep relationship between inner and outer classes
-keepattributes InnerClasses

# Keep any SASocket implementation and its constructors
-keep class * extends com.samsung.android.sdk.accessory.SASocket { <init>(...); }

# Keep the Accessory SDK's IInterface and Binder classes
-keep class com.samsung.accessory.api.* extends android.os.Binder { *; }

YMMV.

MH.
  • 45,303
  • 10
  • 103
  • 116
0

Tried suggestions above, but still got crashes attributed to Parcelable interfaces implemented by SAPeerAccessory (and probably by some others), and by other classes implementing IInterface:

public class SAPeerAccessory implements Parcelable

I also had problems with my own classes serializable through GSON (last line in example below). This is how my changes to proguard-rules.pro look like

-keepattributes SourceFile,LineNumberTable,InnerClasses,EnclosingMethod,Signature 

-keep class * extends com.samsung.android.sdk.accessory.SASocket { <init>(...); }

# Keep the Accessory SDK's IInterface, Binder, and Prcelable classes
-keep class com.samsung.** extends android.os.Binder { *; }
-keep class com.samsung.** extends android.os.IInterface { *; }
-keep class com.samsung.** extends android.os.Parcelable { *; }
# This is for my own class implementing a model serializable by GSON
-keep class my.gson.Model { <fields>; }

No crashes so far, while APK's size has been reduced by 20%

Samsung's lib versions in my project:

accessory-v2.5.3.jar
sdk-v1.0.0.0.jar
Oleg Gryb
  • 5,122
  • 1
  • 28
  • 40