为什么getActivity()JUnit测试过程中阻止时,自定义ImageView的调用startAnimation(动画)?自定义、过程中、测试、动画

2023-09-13 02:09:35 作者:是敌是友别是狗。

我写了一个Android应用程序,显示自定义的的ImageView 定期自身旋转,使用 startAnimation(动画)。该应用程序工作正常,但如果我创建类型的JUnit测试 ActivityInstrumentationTestCase2 和测试呼叫 getActivity(),即来电 getActivity()永远不会返回,直到应用程序进入到背景(例如,设备的主按钮是pressed)。

在很多时间和挫折,我发现 getActivity()立即返回,如果我注释掉调用 startAnimation(动画)在我的自定义的ImageView 类。但是,这会破坏我的自定义的目的的ImageView ,因为我确实需要制作动画。

谁能告诉我,为什么只有当 startAnimation 用于 getActivity()我的JUnit测试过程中块,但?在此先感谢任何人谁可以提出一个解决办法或告诉我,我做错了。

注:该解决方案需要与Android API级别10最小工作

下面是你需要运行它的源$ C ​​$ C(投入任何资源PNG图像/绘制并调用它the_image.png):

activity_main.xml:

 < RelativeLayout的的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
    的xmlns:工具=htt​​p://schemas.android.com/tool​​s
    机器人:layout_width =match_parent
    机器人:layout_height =match_parent
    机器人:paddingBottom会=@扪/ activity_vertical_margin
    机器人:以下属性来=@扪/ activity_horizo​​ntal_margin
    机器人:paddingRight =@扪/ activity_horizo​​ntal_margin
    机器人:paddingTop =@扪/ activity_vertical_margin
    工具:上下文=MainActivity。>

    < com.example.rotatingimageviewapp.RotatingImageView
        机器人:ID =@ + ID / rotatingImageView
        机器人:layout_width =WRAP_CONTENT
        机器人:layout_height =WRAP_CONTENT
        机器人:背景=@可绘制/ the_image/>

< / RelativeLayout的>
 
UI自动化测试基于Activity的封装模式

MainActivity.java:

 包com.example.rotatingimageviewapp;

进口android.app.Activity;
进口android.os.Bundle;
进口android.util.Log;

公共类MainActivity延伸活动{

    私人RotatingImageView rotatingImageView = NULL;

    @覆盖
    保护无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        的setContentView(R.layout.activity_main);

        rotatingImageView =(RotatingImageView)findViewById(
                R.id.rotatingImageView);
        rotatingImageView.startRotation();
    }

    @覆盖
    保护无效的onPause(){
        super.onPause();
        rotatingImageView.stopRotation();
    }

    @覆盖
    保护无效onResume(){
        super.onResume();
        rotatingImageView.startRotation();
    }

}
 

RotatingImageView.java(自定义ImageView的):

 包com.example.rotatingimageviewapp;

进口java.util.Timer中;
进口java.util.TimerTask中;

进口android.content.Context;
进口android.os.Bundle;
进口android.os.Handler;
进口android.os.Message;
进口android.util.AttributeSet;
进口android.view.animation.Animation;
进口android.view.animation.RotateAnimation;
进口android.widget.ImageView;

公共类RotatingImageView扩展ImageView的{

    私有静态最后长ANIMATION_PERIOD_MS = 1000至24年;

    //,做旋转动画的处理程序
    私人最终处理程序处理程序=新的处理程序(){

        私人浮动currentAngle中= 0F;
        私人最终对象animLock =新的对象();
        私人RotateAnimation动画= NULL;

        @覆盖
        公共无效的handleMessage(信息MSG){
            浮nextAngle = 360  -  msg.getData()getFloat(旋转)。
            同步(animLock){
                动画=新RotateAnimation(
                        currentAngle中,
                        nextAngle,
                        Animation.RELATIVE_TO_SELF,
                        .5f,
                        Animation.RELATIVE_TO_SELF,
                        .5f);
                anim.setDuration(ANIMATION_PERIOD_MS);
                / **
                 *注释掉以下行允许getActivity()来
                 *立即返回!
                 * /
                startAnimation(动画);
            }

            currentAngle中= nextAngle;
        }

    };

    私人浮动旋转= 0F;
    私人最终定时器定时=新的定时器(真正的);
    私人的TimerTask的TimerTask = NULL;

    公共RotatingImageView(上下文的背景下){
        超(上下文);
    }

    公共RotatingImageView(上下文的背景下,ATTRS的AttributeSet){
        超(背景下,ATTRS);
    }

    公共RotatingImageView(上下文的背景下,ATTRS的AttributeSet,
            INT defStyle){
        超(背景下,ATTRS,defStyle);
    }

    公共无效startRotation(){
        stopRotation();

        / **
         *设置,计算旋转值任务
         *并告诉处理器做旋转
         * /
        TimerTask的=新的TimerTask(){

            @覆盖
            公共无效的run(){
                //计算下一个旋转值
                旋转+ = 15F;
                而(旋转> = 360°F){
                    旋转 -  = 360F;
                }

                //告诉处理器做旋转
                束束=新包();
                bundle.putFloat(旋转,旋转);
                消息味精=新的Message();
                msg.setData(包);
                handler.sendMessage(MSG);
            }

        };
        timer.schedule(TimerTask的,0,ANIMATION_PERIOD_MS);
    }

    公共无效stopRotation(){
        如果(NULL!=的TimerTask){
            timerTask.cancel();
        }
    }

}
 

MainActivityTest.java:

 包com.example.rotatingimageviewapp.test;

进口android.app.Activity;
进口android.test.ActivityInstrumentationTestCase2;

进口com.example.rotatingimageviewapp.MainActivity;

公共类MainActivityTest扩展
        ActivityInstrumentationTestCase2< MainActivity> {

    公共MainActivityTest(){
        超(MainActivity.class);
    }

    保护无效设置()抛出异常{
        super.setUp();
    }

    保护无效tearDown的()抛出异常{
        super.tearDown();
    }

    公共无效test001(){
        的assertEquals(1 + 2,+ 3 0);
    }

    公共无效test002(){
        以下行//测试挂起,直到应用程序被切换到后台
        活性活性= getActivity();
        assertNotNull(活动);
    }

    公共无效test003(){
        的assertEquals(1 + 2,+ 3 0);
    }

}
 

解决方案

不知道,如果你们解决这个问题。 但是,这是我的解决方案,只是重写方法getActivity():

  @覆盖
    公共MyActivity getActivity(){
        如果(mActivity == NULL){
            意向意图=新的意图(getInstrumentation()getTargetContext(),MyActivity.class。);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            //寄存器活性需要被监视。
            监测= getInstrumentation()添加监视器(MyActivity.class.getName(),空,假的)。
            。getInstrumentation()getTargetContext()startActivity(意向)。
            mActivity =(MyActivity)getInstrumentation()waitForMonitor(显示器)。
            setActivity(mActivity);
        }
        返回mActivity;
    }
 

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);
    }

}

解决方案

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;
    }