1

I am working on an app that just recently got big (65535 method count per dex reached). It reached the limit when I integrated the Samsung Spen SDK.

I decided to make use of proguard, and it compiled successfully. However, when I opened the spen draw activity, it crashed with the following log:

E/AndroidRuntime( 7144): java.lang.UnsatisfiedLinkError: Native method not found: com.bst.HwBeautify.BeautifyNative.nativeCBInitEngine:()I
E/AndroidRuntime( 7144):    at com.bst.HwBeautify.BeautifyNative.nativeCBInitEngine(Native Method)
E/AndroidRuntime( 7144):    at com.bst.HwBeautify.BeautifyNative.cbInitEngine(SourceFile:107)
E/AndroidRuntime( 7144):    at com.bst.HwBeautify.BeautifyManager.b(SourceFile:87)
E/AndroidRuntime( 7144):    at com.bst.HwBeautify.BeautifyManager.a(SourceFile:85)
E/AndroidRuntime( 7144):    at com.bst.HwBeautify.BeautifyManager$1.run(SourceFile:64)
E/AndroidRuntime( 7144):    at java.lang.Thread.run(Thread.java:856)

This missing method can be found in the proguard/seeds.txt

...
com.bst.HwBeautify.BeautifyNative
com.bst.HwBeautify.BeautifyNative: int nativeCBInitEngine()
com.bst.HwBeautify.BeautifyNative: void nativeCBCloseEngine()
...

and in proguard/dumps.txt

  + Method:       nativeCBInitEngine()I
    Access flags: 0x108
      = static native int nativeCBInitEngine()

and BeautifyNative.class is also in obfuscated.jar (obfuscated/com/bst/HwBeautify/), so I don't know why it says UnsatisfiedLinkError.

Then I decided to try custom class loading. This also did not work, but it is a different story. Besides, I will have to recode many parts to make my app compatible with secondary jars.

Investigating on the problem, I tried to create a bare minimum app that works with the Spen SDK. Slowly, I put in parts of my big app to the small -- but working -- app.

The bug was replicated unsuspectedly by adding more images. To be more clear, when I only have the images on res/drawable-hdpi, the app does not crash (crash here pertains to the same log as above). But when I add the res/drawable-xhdpi, it crashes. Same story with res/drawable-ldpi and res/drawable-mdpi.

One more thing: If I install the big app using adb install -r appname.apk while the small app (same package name, same app name) is still installed, the big app works fine. But if I uninstall the small app before installing the big app, the latter crashes.

For my questions:

  • Why does it say UnsatisfiedLinkError when the method is there? Or am I looking at the wrong files?
  • Is there something wrong with my proguard.cfg that leaves this native method?
  • Can this be an ant build bug?

Here is my proguard.cfg

#################################################################################################
# Standard Configuration for Android App
# See http://proguard.sourceforge.net/index.html#manual/examples.html

# -optimizationpasses 2
-dontoptimize
-dontobfuscate
-dontpreverify
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
# -allowaccessmodification
# -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable
-keepattributes *Annotation*

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-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.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
}

-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

-keepclassmembers class * extends android.content.Context {
   public void *(android.view.View);
   public void *(android.view.MenuItem);
}

-keepclassmembers class * implements android.os.Parcelable {
    static android.os.Parcelable$Creator CREATOR;
}

-keepclassmembers class * implements java.io.Serializable
{
    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();
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

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

# adding this in to preserve line numbers so that the stack traces
# can be remapped
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable


#################################################################################################
# For RoboSpice
# See https://groups.google.com/forum/?fromgroups=#!topic/robospice/xGLRbGkLwQU
#Request classes purged by Proguard as they are "empty", others are kept
-keep class com.limbocitizen.android.playground.model.**

#RoboSpice requests and Results must be kept as they are used by reflection via Jackson
-keepclassmembers class com.limbocitizen.android.playground.request.** {
  public void set*(***);
  public *** get*();
  public *** is*();
}


### XML SERIALIZER SETTINGS

-keepclassmembers,allowobfuscation class * {
    @org.simpleframework.xml.* <fields>;
    @org.simpleframework.xml.* <init>(...);
}


### Json SERIALIZER SETTINGS
-keepclassmembers,allowobfuscation class * {
    @org.codehaus.jackson.annotate.* <fields>;
    @org.codehaus.jackson.annotate.* <init>(...);
}

-keepclasseswithmembers class * {
    native <methods>;
}

#Warnings to be removed. Otherwise maven plugin stops, but not dangerous
-dontwarn android.support.**
-dontwarn com.sun.xml.internal.**
-dontwarn com.sun.istack.internal.**
-dontwarn org.codehaus.jackson.**
-dontwarn org.springframework.**
-dontwarn java.awt.**
-dontwarn javax.security.**
-dontwarn java.beans.**
-dontwarn javax.xml.**
-dontwarn java.util.**
-dontwarn org.w3c.dom.**
-dontwarn com.octo.android.robospice.persistence.**

-dontwarn org.bouncycastle.**
-dontwarn com.nostra13.**
-dontwarn com.opentok.**
-dontwarn com.pubnub.api.**
-dontwarn com.google.gson.**
-dontwarn com.google.android.**
-dontwarn chesspresso.**
-dontwarn com.parse.**
-dontwarn com.testflightapp.**
-dontwarn org.msgpack.**
-dontwarn com.bugsense.**
-dontwarn biz.source_code.base64Coder.**
-dontwarn org.codehaus.jackson.**
-dontwarn com.bst.**
-dontwarn com.google.common.**
-dontwarn com.samsung.**
-dontwarn org.apache.commons.pool.**
-dontwarn org.ccil.cowan.tagsoup.**
-dontwarn com.opentok.**
-dontwarn com.tokbox.**
-dontwarn main.java.tokbox.org.**
-dontwarn tokbox.org.**
-dontwarn com.google.android.youtube.player.**
-dontwarn org.slf4j.**
-dontwarn org.codehaus.jackson.**
-dontwarn com.facebook.**

-keep class android.support.v4.content.Loader
-keep class com.google.android.gms.maps.GoogleMapOptions
-keep class android.support.v4.content.Loader$OnLoadCompleteListener
-keep class com.icannhas.qualitytime.utils.parse.objects.ParseChatMessage
-keep class com.tokbox.rumor.client.message.Message
-keep class org.codehaus.jackson.node.ObjectNode

#################################################################################################
# For Actionbarsherlock
# See http://actionbarsherlock.com/faq.html

-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep class com.actionbarsherlock.** { *; }
-keep interface com.actionbarsherlock.** { *; }

-keepattributes *Annotation*


#################################################################################################

my build.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
    Copyright 2011 Google Inc.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->
<project name="appname" default="help">

    <!-- The local.properties file is created and updated by the 'android' tool.
         It contains the path to the SDK. It should *NOT* be checked into
         Version Control Systems. -->
    <property file="local.properties" />

    <!-- The ant.properties file can be created by you. It is only edited by the
         'android' tool to add properties to it.
         This is the place to change some Ant specific build properties.
         Here are some properties you may want to change/update:

         source.dir
             The name of the source directory. Default is 'src'.
         out.dir
             The name of the output directory. Default is 'bin'.

         For other overridable properties, look at the beginning of the rules
         files in the SDK, at tools/ant/build.xml

         Properties related to the SDK location or the project target should
         be updated using the 'android' tool with the 'update' action.

         This file is an integral part of the build system for your
         application and should be checked into Version Control Systems.

         -->
    <property file="ant.properties" />

    <!-- The project.properties file is created and updated by the 'android'
         tool, as well as ADT.

         This contains project specific properties such as project target, and library
         dependencies. Lower level build properties are stored in ant.properties
         (or in .classpath for Eclipse projects).

         This file is an integral part of the build system for your
         application and should be checked into Version Control Systems. -->
    <loadproperties srcFile="project.properties" />

    <!-- quick check on sdk.dir -->
    <fail
            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
            unless="sdk.dir"
    />

    <!--
        Import per project custom build rules if present at the root of the project.
        This is the place to put custom intermediary targets such as:
            -pre-build
            -pre-compile
            -post-compile (This is typically used for code obfuscation.
                           Compiled code location: ${out.classes.absolute.dir}
                           If this is not done in place, override ${out.dex.input.absolute.dir})
            -post-package
            -post-build
            -pre-clean
    -->
    <import file="custom_rules.xml" optional="true" />

    <!-- Import the actual build file.

         To customize existing targets, there are two options:
         - Customize only one target:
             - copy/paste the target into this file, *before* the
               <import> task.
             - customize it to your needs.
         - Customize the whole content of build.xml
             - copy/paste the content of the rules files (minus the top node)
               into this file, replacing the <import> task.
             - customize to your needs.

         ***********************
         ****** IMPORTANT ******
         ***********************
         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
         in order to avoid having your file be overridden by tools such as "android update project"
    -->
    <!-- version-tag: 1 -->
    <import file="${sdk.dir}/tools/ant/build.xml" />
</project>

and custom_rules.xml

<?xml version="1.0" encoding="UTF-8"?>
<project name="custom_rules">
    <!--
    Suppress "Warning: can't write resource [META-INF/MANIFEST.MF] (Duplicate zip entry [classes.jar:META-INF/MANIFEST.MF])".
    Per http://www.dancartoon.com/2012/01/14/fixing-proguard-warning-cant-write-resource-meta-infmanifest-mf/
    Target "-obfuscate" copied from ${sdk.dir}/tools/ant/build.xml v21.
    -->
    <target name="-obfuscate">
        <if condition="${proguard.enabled}">
            <then>
                <property name="obfuscate.absolute.dir" location="${out.absolute.dir}/proguard" />
                <property name="preobfuscate.jar.file" value="${obfuscate.absolute.dir}/original.jar" />
                <property name="obfuscated.jar.file" value="${obfuscate.absolute.dir}/obfuscated.jar" />
                <!-- input for dex will be proguard's output -->
                <property name="out.dex.input.absolute.dir" value="${obfuscated.jar.file}" />

                <!-- Add Proguard Tasks -->
                <property name="proguard.jar" location="${android.tools.dir}/proguard/lib/proguard.jar" />
                <taskdef name="proguard" classname="proguard.ant.ProGuardTask" classpath="${proguard.jar}" />

                <!-- Set the android classpath Path object into a single property. It'll be
                     all the jar files separated by a platform path-separator.
                     Each path must be quoted if it contains spaces.
                -->
                <pathconvert property="project.target.classpath.value" refid="project.target.class.path">
                    <firstmatchmapper>
                        <regexpmapper from='^([^ ]*)( .*)$$' to='"\1\2"'/>
                        <identitymapper/>
                    </firstmatchmapper>
                </pathconvert>

                <!-- Build a path object with all the jar files that must be obfuscated.
                     This include the project compiled source code and any 3rd party jar
                     files. -->
                <path id="project.all.classes.path">
                    <pathelement location="${preobfuscate.jar.file}" />
                    <path refid="project.all.jars.path" />
                </path>
                <!-- Set the project jar files Path object into a single property. It'll be
                     all the jar files separated by a platform path-separator.
                     Each path must be quoted if it contains spaces.
                -->
            <!--
            Old:
            <pathconvert property="project.all.classes.value" refid="project.all.classes.path">
            New:
            -->
                <pathconvert property="project.all.classes.value" refid="project.all.jars.path" pathsep=" ">
                    <firstmatchmapper>
                    <!--
                    Old:
                        <regexpmapper from='^([^ ]*)( .*)$$' to='"\1\2"'/>
                        <identitymapper/>
                    New:
                    -->
                    <regexpmapper from='^([^ ]*)( .*)$$' to='-injars "\1\2"(!META-INF/MANIFEST.MF)'/>
                    <regexpmapper from='(.*)' to='-injars \1(!META-INF/MANIFEST.MF)'/>                        
                    </firstmatchmapper>
                </pathconvert>

                <!-- Turn the path property ${proguard.config} from an A:B:C property
                     into a series of includes: -include A -include B -include C
                     suitable for processing by the ProGuard task. Note - this does
                     not include the leading '-include "' or the closing '"'; those
                     are added under the <proguard> call below.
                -->
                <path id="proguard.configpath">
                    <pathelement path="${proguard.config}"/>
                </path>
                <pathconvert pathsep='" -include "' property="proguard.configcmd" refid="proguard.configpath"/>

            <echo>proguard: $${project.all.classes.value}=${project.all.classes.value}</echo>                

                <mkdir   dir="${obfuscate.absolute.dir}" />
                <delete file="${preobfuscate.jar.file}"/>
                <delete file="${obfuscated.jar.file}"/>
                <jar basedir="${out.classes.absolute.dir}"
                    destfile="${preobfuscate.jar.file}" />
                <proguard>
                    -include      "${proguard.configcmd}"
                    -include      "${out.absolute.dir}/proguard.txt"
                -injars       ${preobfuscate.jar.file}
                    ${project.all.classes.value}
                    -outjars      "${obfuscated.jar.file}"
                    -libraryjars  ${project.target.classpath.value}
                    -dump         "${obfuscate.absolute.dir}/dump.txt"
                    -printseeds   "${obfuscate.absolute.dir}/seeds.txt"
                    -printusage   "${obfuscate.absolute.dir}/usage.txt"
                    -printmapping "${obfuscate.absolute.dir}/mapping.txt"
                </proguard>
            </then>
        </if>
    </target>
</project>
tomrozb
  • 25,773
  • 31
  • 101
  • 122
aysonje
  • 2,595
  • 1
  • 14
  • 16

0 Answers0