Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
391 views
in Technique[技术] by (71.8m points)

android - Why does getActivity() block during JUnit test when custom ImageView calls startAnimation(Animation)?

I wrote an Android app that displays a custom ImageView that rotates itself periodically, using startAnimation(Animation). The app works fine, but if I create a JUnit test of type ActivityInstrumentationTestCase2 and the test calls getActivity(), that call to getActivity() never returns until the app goes to the background (for example, the device's home button is pressed).

After much time and frustration, I found that getActivity() returns immediately if I comment out the call to startAnimation(Animation) in my custom ImageView class. But that would defeat the purpose of my custom ImageView, because I do need to animate it.

Can anyone tell me why getActivity() blocks during my JUnit test but only when startAnimation is used? Thanks in advance to anyone who can suggest a workaround or tell me what I'm doing wrong.

Note: the solution needs to work with Android API level 10 minimum.

Here is all the source code you need to run it (put any PNG image in res/drawable and call it 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 (custom 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);
    }

}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

not sure if you guys solve this. But this is my solution, just override method 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;
    }

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
...