El error más extraño de Android – ¿Tal vez un problema de ProGuard?

Ni siquiera sé qué título dar esto, es tan extraño. Construí un pequeño rompecabezas de la lógica de Android que usa valores de color en un formato entero de ARGB. Para mezclar los colores de la animación cuando completes un nivel, tengo la siguiente función:

public static int blend(int color1, int color2, double position) { if (position<0) position=0; if (position>1) position=1; int a = (color1 >>> 24) & 0xFF; int r = (color1 >>> 16) & 0xFF; int g = (color1 >>> 8) & 0xFF; int b = color1 & 0xFF; int da = ((color2 >>> 24) & 0xFF) - a; int dr = ((color2 >>> 16) & 0xFF) - r; int dg = ((color2 >>> 8) & 0xFF) - g; int db = ( color2 & 0xFF) - b; a += da * position; r += dr * position; g += dg * position; b += db * position; return (a<<24) | (r<<16) | (g<<8) | b; } 

Llamo a esta función durante la animación con este código (incluye declaración de impresión de depuración):

 int color = blend(START_COLOR, END_COLOR, pos*pos*pos*pos*pos); System.out.println(Integer.toHexString(START_COLOR)+", "+Integer.toHexString(END_COLOR)+", "+pos+" -> "+Integer.toHexString(color)); 

Aquí, pos es simplemente un doble valor que cuenta de 0,0 a 1,0.

Si ejecuto este código directamente desde mi teléfono desde Eclipse a través del complemento para desarrolladores de Android, todo funciona bien.

PERO: Si empaquete la aplicación e instalo el APK, se atornilla de forma fiable , me da salida similar a esto:

 ... fff9b233, f785a307, 0.877 -> fabcaa1c fff9b233, f785a307, 0.881 -> fabbaa1b fff9b233, f785a307, 0.883 -> fabaa91b fff9b233, f785a307, 0.886 -> fab9a91a fff9b233, f785a307, 0.89 -> fab8a91a fff9b233, f785a307, 0.891 -> fa00a91a fff9b233, f785a307, 0.895 -> fab6a919 fff9b233, f785a307, 0.896 -> fa00a919 fff9b233, f785a307, 0.901 -> fab4a918 fff9b233, f785a307, 0.901 -> fab4a918 fff9b233, f785a307, 0.907 -> fab1a817 fff9b233, f785a307, 0.907 -> fab1a817 fff9b233, f785a307, 0.912 -> f9afa817 fff9b233, f785a307, 0.913 -> f900a817 fff9b233, f785a307, 0.919 -> f9aca816 fff9b233, f785a307, 0.919 -> f9aca816 fff9b233, f785a307, 0.925 -> f9aaa715 fff9b233, f785a307, 0.925 -> f9aaa715 fff9b233, f785a307, 0.93 -> f900a714 fff9b233, f785a307, 0.931 -> f900a714 fff9b233, f785a307, 0.936 -> f900a713 fff9b233, f785a307, 0.937 -> f900a713 fff9b233, f785a307, 0.942 -> f900a612 fff9b233, f785a307, 0.942 -> f900a612 fff9b233, f785a307, 0.947 -> f800a611 fff9b233, f785a307, 0.948 -> f800a611 fff9b233, f785a307, 0.954 -> f800a610 fff9b233, f785a307, 0.954 -> f800a610 fff9b233, f785a307, 0.959 -> f800a50f ... 

En este ejemplo, hasta la posición 0.89, todo está bien. Entonces, las cosas comienzan a oscilar entre trabajar y atornillar sólo el componente R (puesto a 0, es siempre el componente R que se atornilla), y eventualmente, a partir de 0.93 en este ejemplo, las cosas siempre estropean. Y luego, si vuelvo a ejecutar la misma animación, empieza a joder de inmediato …

¿Cómo es esto posible? ¿Este ProGuard está jugando con mi código? Si eso es una posibilidad, ¿hay una manera de averiguarlo con seguridad? Estoy realmente en una pérdida de ideas aquí … ¿Y cómo puede ser probabilística si funciona o no? ¿O simplemente estoy perdiendo algo totalmente obvio aquí?

Si pudiera ser un problema de ProGuard, ¿qué tipo de optimizaciones podría afectar a esta parte del código? ¿Hay una lista de conmutadores que podría tratar de desactivar uno por uno para encontrar el escamoso?

ACTUALIZAR:

Mi archivo project.properties tiene este aspecto (líneas comentadas eliminadas):

 proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt target=android-22 

Y proguard-project.txt como este:

 -flattenpackagehierarchy -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable 

proguard-android.txt en el directorio de SDK debe seguir siendo igual que fue enviado con las herramientas SDK v24.1.2 (suponiendo que es el paquete que incluye ProGuard …, excluyendo los comentarios):

 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -verbose -dontoptimize -dontpreverify -keepattributes *Annotation* -keep public class com.google.vending.licensing.ILicensingService -keep public class com.android.vending.licensing.ILicensingService -keepclasseswithmembernames class * { native <methods>; } -keepclassmembers public class * extends android.view.View { void set*(***); *** get*(); } -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } -keepclassmembers class **.R$* { public static <fields>; } -dontwarn android.support.** 

ACTUALIZACIÓN 2:

Creo que encontré la salida compilada de lo que ProGuard hace con el método blend en el archivo dump.txt :

 + Method: a(IID)I Access flags: 0x9 = public static int a(int,int,double) Class member attributes (count = 1): + Code attribute instructions (code length = 171, locals = 12, stack = 6): [0] dload_2 v2 [1] dconst_0 [2] dcmpg [3] ifge +5 (target=8) [6] dconst_0 [7] dstore_2 v2 [8] dload_2 v2 [9] dconst_1 [10] dcmpl [11] ifle +5 (target=16) [14] dconst_1 [15] dstore_2 v2 [16] iload_0 v0 [17] bipush 24 [19] iushr [20] sipush 255 [23] iand [24] istore v4 [26] iload_0 v0 [27] bipush 16 [29] iushr [30] sipush 255 [33] iand [34] istore v5 [36] iload_0 v0 [37] bipush 8 [39] iushr [40] sipush 255 [43] iand [44] istore v6 [46] iload_0 v0 [47] sipush 255 [50] iand [51] istore v7 [53] iload_1 v1 [54] bipush 24 [56] iushr [57] sipush 255 [60] iand [61] iload v4 [63] isub [64] istore v8 [66] iload_1 v1 [67] bipush 16 [69] iushr [70] sipush 255 [73] iand [74] iload v5 [76] isub [77] istore v9 [79] iload_1 v1 [80] bipush 8 [82] iushr [83] sipush 255 [86] iand [87] iload v6 [89] isub [90] istore v10 [92] iload_1 v1 [93] sipush 255 [96] iand [97] iload v7 [99] isub [100] istore v11 [102] iload v4 [104] i2d [105] iload v8 [107] i2d [108] dload_2 v2 [109] dmul [110] dadd [111] d2i [112] istore v4 [114] iload v5 [116] i2d [117] iload v9 [119] i2d [120] dload_2 v2 [121] dmul [122] dadd [123] d2i [124] istore v5 [126] iload v6 [128] i2d [129] iload v10 [131] i2d [132] dload_2 v2 [133] dmul [134] dadd [135] d2i [136] istore v6 [138] iload v7 [140] i2d [141] iload v11 [143] i2d [144] dload_2 v2 [145] dmul [146] dadd [147] d2i [148] istore v7 [150] iload v4 [152] bipush 24 [154] ishl [155] iload v5 [157] bipush 16 [159] ishl [160] ior [161] iload v6 [163] bipush 8 [165] ishl [166] ior [167] iload v7 [169] ior [170] ireturn Code attribute exceptions (count = 0): Code attribute attributes (attribute count = 2): + Line number table attribute (count = 15) [0] -> line 33 [8] -> line 34 [16] -> line 35 [26] -> line 36 [36] -> line 37 [46] -> line 38 [53] -> line 40 [66] -> line 41 [79] -> line 42 [92] -> line 43 [102] -> line 45 [114] -> line 46 [126] -> line 47 [138] -> line 48 [150] -> line 50 + Stack map table attribute (count = 2): - [8] Var: ..., Stack: (empty) - [16] Var: ..., Stack: (empty) 

ACTUALIZACIÓN 3 :

Intenté reescribir el método de la mezcla a esto (idea que es que si trato todos los componentes iguales, no debería ser posible atornillar para arriba apenas uno más):

 public static int blend(int color1, int color2, double position) { if (position<0) position=0; if (position>1) position=1; int result = 0; for (int shift = 0; shift<32; shift += 8) { int component = (color1 >>> shift) & 0xFF; int change = ((color2 >>> shift) & 0xFF) - component; component += change * position; result |= component << shift; } return result; } 

No es de extrañar, este código funciona ahora, como debería! Pero esto todavía no me hace más cerca de entender por qué el código original falló y en qué otros lugares de mi aplicación algo similar trivial podría fallar de maneras inesperadas.

ACTUALIZACIÓN 4:

Simplemente reordenar las líneas a esto también corrige el problema:

 public static int blend(int color1, int color2, double position) { if (position<0) position=0; if (position>1) position=1; int a = (color1 >>> 24) & 0xFF; int da = ((color2 >>> 24) & 0xFF) - a; a += da * position; int r = (color1 >>> 16) & 0xFF; int dr = ((color2 >>> 16) & 0xFF) - r; r += dr * position; int g = (color1 >>> 8) & 0xFF; int dg = ((color2 >>> 8) & 0xFF) - g; g += dg * position; int b = color1 & 0xFF; int db = ( color2 & 0xFF) - b; b += db * position; return (a<<24) | (r<<16) | (g<<8) | b; } 

Debe ser algo de reutilización de variables locales, simplemente no sé por qué eso no es evidente desde el archivo dump.txt anterior … ¿Es esto algo que Dalvik hace (pero sólo a firmado APKs?)?

Un tema realmente interesante para investigar y resolverlo definitivamente aumentará su nivel de experiencia con (posiblemente) ProGuard, pero por el bien de mirar la imagen más grande, yo recomendaría ir con las herramientas existentes para animar los cambios de color 🙂

ArgbEvaluator (o ValueAnimator # ofArgb (int …) para API 21+) al rescate!

API 21+:

 ValueAnimator animator = ValueAnimator.ofArgb(0xFFFF0000, 0xFF00FF00); //red->green 

API 11+:

 ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(), 0xFFFF0000, 0xFF00FF00); //red->green animator.setDuration(1000);//1second animator.start(); 

Le permite ajustarlo como lo necesite (diferentes interpoladores, retrasos, escuchas, etc) y también ya que viene de la plataforma hay una gran posibilidad de que ProGuard no lo toque

PD. Todavía realmente quiero ver la causa del problema que estás experimentando 🙂

No tengo una respuesta sobre las cosas de proguard, pero hay algunos métodos de ayuda de Color que pueden, por cualquier razón, darle el resultado correcto. Por lo menos, hará que su código sea más legible. Prueba esto:

 public static int blend(int color1, int color2, double position) { if (position < 0) { position = 0; } if (position > 1) { position = 1; } int a = Color.alpha(color1); int r = Color.red(color1); int g = Color.green(color1); int b = Color.blue(color1); int da = Color.alpha(color2) - a; int dr = Color.red(color2) - r; int dg = Color.green(color2) - g; int db = Color.blue(color2) - b; a += da * position; r += dr * position; g += dg * position; b += db * position; return Color.argb(a, r, g, b); } 
  • Xamarin proguard.ParseException: Opción desconocida '' en la línea 1 del archivo 'Properties / proguard.cfg'
  • SimpleXML falla en Lenovo P90
  • Usar Proguard sólo para deshabilitar el registro y para reducir recursos
  • Android ProGuard: no se puede encontrar la clase referenciada
  • Problemas al compilar la aplicación de Android en modo de liberación con proguard activado
  • Android Proguard ZipException al generar APK firmado
  • ¿Cómo sé las clases de probIem Proguard se refiere a
  • ¿Cómo realizar la minificación y ofuscación con el compilador JACK?
  • NullPointerExcepetion Facebook sdk v4.5.0 cuando habilite Proguard (cuando intente ingresar usando la biblioteca de Parse)
  • Obtener las referencias de "referencias no resueltas" y "no puede encontrar superclases" de Proguard
  • Android Studio - Creación incremental de Gradle
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.