¿Por qué se bloquea getActivity () durante la prueba JUnit cuando las llamadas de ImageView personalizadas comienzanAnimation (Animation)?

Escribí una aplicación de Android que muestra un ImageView personalizado que se rota periódicamente, utilizando startAnimation(Animation) . La aplicación funciona correctamente, pero si creo una prueba JUnit de tipo ActivityInstrumentationTestCase2 y las llamadas de prueba getActivity() , esa llamada a getActivity() nunca vuelve hasta que la aplicación se va al fondo (por ejemplo, se presiona el botón de inicio del dispositivo).

Después de mucho tiempo y frustración, descubrí que getActivity() devuelve inmediatamente si comento la llamada a startAnimation(Animation) en mi clase de ImageView personalizada. Pero eso derrotaría el propósito de mi imagen personalizada, porque necesito animarla.

¿Puede alguien decirme por qué bloques getActivity() durante mi prueba de JUnit pero sólo cuando startAnimation se utiliza? Gracias de antemano a cualquier persona que puede sugerir una solución o decirme lo que estoy haciendo mal.

Nota: la solución debe funcionar con la API de Android mínimo 10.

Aquí está todo el código fuente que necesita para ejecutarlo (ponga cualquier imagen PNG en res / drawable y llámela the_image.png):

Activity_main.xml:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <com.example.rotatingimageviewapp.RotatingImageView android:id="@+id/rotatingImageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/the_image" /> </RelativeLayout> 

MainActivity.java:

 package com.example.rotatingimageviewapp; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class MainActivity extends Activity { private RotatingImageView rotatingImageView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); rotatingImageView = (RotatingImageView) findViewById( R.id.rotatingImageView); rotatingImageView.startRotation(); } @Override protected void onPause() { super.onPause(); rotatingImageView.stopRotation(); } @Override protected void onResume() { super.onResume(); rotatingImageView.startRotation(); } } 

RotatingImageView.java (personalizado ImageView):

 package com.example.rotatingimageviewapp; import java.util.Timer; import java.util.TimerTask; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.ImageView; public class RotatingImageView extends ImageView { private static final long ANIMATION_PERIOD_MS = 1000 / 24; //The Handler that does the rotation animation private final Handler handler = new Handler() { private float currentAngle = 0f; private final Object animLock = new Object(); private RotateAnimation anim = null; @Override public void handleMessage(Message msg) { float nextAngle = 360 - msg.getData().getFloat("rotation"); synchronized (animLock) { anim = new RotateAnimation( currentAngle, nextAngle, Animation.RELATIVE_TO_SELF, .5f, Animation.RELATIVE_TO_SELF, .5f); anim.setDuration(ANIMATION_PERIOD_MS); /** * Commenting out the following line allows getActivity() to * return immediately! */ startAnimation(anim); } currentAngle = nextAngle; } }; private float rotation = 0f; private final Timer timer = new Timer(true); private TimerTask timerTask = null; public RotatingImageView(Context context) { super(context); } public RotatingImageView(Context context, AttributeSet attrs) { super(context, attrs); } public RotatingImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void startRotation() { stopRotation(); /** * Set up the task that calculates the rotation value * and tells the Handler to do the rotation */ timerTask = new TimerTask() { @Override public void run() { //Calculate next rotation value rotation += 15f; while (rotation >= 360f) { rotation -= 360f; } //Tell the Handler to do the rotation Bundle bundle = new Bundle(); bundle.putFloat("rotation", rotation); Message msg = new Message(); msg.setData(bundle); handler.sendMessage(msg); } }; timer.schedule(timerTask, 0, ANIMATION_PERIOD_MS); } public void stopRotation() { if (null != timerTask) { timerTask.cancel(); } } } 

MainActivityTest.java:

 package com.example.rotatingimageviewapp.test; import android.app.Activity; import android.test.ActivityInstrumentationTestCase2; import com.example.rotatingimageviewapp.MainActivity; public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> { public MainActivityTest() { super(MainActivity.class); } protected void setUp() throws Exception { super.setUp(); } protected void tearDown() throws Exception { super.tearDown(); } public void test001() { assertEquals(1 + 2, 3 + 0); } public void test002() { //Test hangs on the following line until app goes to background Activity activity = getActivity(); assertNotNull(activity); } public void test003() { assertEquals(1 + 2, 3 + 0); } } 

No estoy seguro de si ustedes resuelven esto. Pero esta es mi solución, sólo el método override getActivity ():

 @Override public MyActivity getActivity() { if (mActivity == null) { Intent intent = new Intent(getInstrumentation().getTargetContext(), MyActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // register activity that need to be monitored. monitor = getInstrumentation().addMonitor(MyActivity.class.getName(), null, false); getInstrumentation().getTargetContext().startActivity(intent); mActivity = (MyActivity) getInstrumentation().waitForMonitor(monitor); setActivity(mActivity); } return mActivity; } 

Puedo decirle por qué esto está sucediendo y tienen una pequeña solución, creo que debería ser capaz de hacer algo con su punto de vista, pero esto debería funcionar por ahora.

El problema es que, cuando llamas a getActivity (), pasa por una serie de métodos hasta que toca lo siguiente en InstrumentationTestCase.java

 public final <T extends Activity> T launchActivityWithIntent( String pkg, Class<T> activityCls, Intent intent) { intent.setClassName(pkg, activityCls.getName()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); T activity = (T) getInstrumentation().startActivitySync(intent); getInstrumentation().waitForIdleSync(); return activity; } 

El problema es la pesky línea que tiene lo siguiente:

 getInstrumentation().waitForIdleSync(); 

Debido a su animación nunca hay un inactivo en el hilo principal y por lo que nunca vuelve de este método! ¿Cómo se puede arreglar esto? Así es bastante fácil que tendrá que anular este método por lo que ya no tiene esa línea pulg Usted puede tener que agregar en algún código para poner una espera para asegurarse de que la actividad se inicia, aunque de lo contrario este método volverá demasiado rápido! Sugiero esperar una vista específica para esta actividad.

ACTUALIZACIÓN: gracias a @nebula por la respuesta anterior: https://stackoverflow.com/a/24506584/720773


Aprendí sobre una solución sencilla para este problema: utilice un enfoque diferente para rotar la imagen, que no implica Animation :

Android: gira la imagen en la vista de la imagen por un ángulo

Eso realmente no responde a mi pregunta, pero funciona en torno al problema. Si alguien sabe cómo obtener ActivityInstrumentationTestCase2.getActivity() para devolver la Activity mientras utiliza la clase Animation en un ImageView personalizado, publica un SSCCE como respuesta y lo aceptaré en lugar de éste si funciona.

He aprendido sobre cada solución para este problema, y ​​esta es mi solución, funciona bien, thx todo el mundo;)

 public class TestApk extends ActivityInstrumentationTestCase2 { private static final String LAUNCHER_ACTIVITY_FULL_CLASSNAME = "com.notepad.MainActivity"; private static Class launcherActivityClass; static { try { launcherActivityClass = Class .forName(LAUNCHER_ACTIVITY_FULL_CLASSNAME); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } public TestApk () throws ClassNotFoundException { super(launcherActivityClass); } private Solo solo; @Override protected void setUp() throws Exception { solo = new Solo(getInstrumentation()); Intent intent = new Intent(getInstrumentation().getTargetContext(), launcherActivityClass); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getInstrumentation().getTargetContext().startActivity(intent); } public void test_ookla_speedtest() { Boolean expect = solo.waitForText("Login", 0, 60*1000); assertTrue("xxxxxxxxxxxxxxxxxxx", expect); } @Override public void tearDown() throws Exception { solo.finishOpenedActivities(); super.tearDown(); } } 
  • ¿Diferencia entre addOnGlobalLayoutListener y onWindowFocusChanged?
  • Alineación de un TextView y ImageView dentro de un LinearLayout
  • Glide hace que ImageView wrap_content sea inútil y sin animatiion usando target
  • ViewPager onClickListener hace referencia a la vista errónea en android
  • Múltiples animaciones en 1 imageview android
  • Android Volley - ¿Cómo animar la carga de imágenes?
  • Android: no se puede eliminar el espacio vertical entre las imágenes en LinearLayout
  • Cómo hacer una imagen tan grande como sea posible, manteniendo la relación de aspecto
  • Android: ¿Cómo puedo acelerar el desplazamiento de GridView?
  • Android: ¿Cómo puedo detener una animación infinita aplicada en un ImageView?
  • Anchura máxima y altura para ImageView en Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.