ActivityInstrumentationTestCase2 issues - test hangs because of invalidate() call in the code I'm writing a test for
I'm implementing a test for some code I've written which I have distilled down into a sample project pasted below. The problem I am having is that the test hangs the test runner and none of the test in my test case run.
I have tracked the problem down to a call to invalidate() in getChildStaticTransformation(View child, Transformation t) which I am overriding to do some scaling effects.
If I comment out the call to invalidate() then the tests run. The reason I'm calling invalidate() is to ensure that the view gets redrawn after I apply the scaling effect.
If I do not call invalidate() then the view dosen't look right until a user action forces the screen to get redrawn.
Two questions :
- Why is invalidate() blocking the test from running?
- Should I be doing the scaling of my view differently that does not require invalidate() to be called?
Following is the output of the Eclipse Android Console showing where the test hangs :
[2011-08-19 13:51:27 - HelloGalleryTest]
------------------------------
[2011-08-19 13:51:27 - HelloGalleryTest] Android Launch!
[2011-08-19 13:51:27 - HelloGalleryTest] adb is running normally.
[2011-08-19 13:51:27 - HelloGalleryTest] Performing
android.test.InstrumentationTestRunner JUnit launch
[2011-08-19 13:51:27 - HelloGalleryTest] Automatic Target Mode: using
device '428024642BF9457'
[2011-08-19 13:51:27 - HelloGalleryTest] Uploading
HelloGalleryTest.apk onto device '428024642BF9457'
[2011-08-19 13:51:27 - HelloGalleryTest] Installing
HelloGalleryTest.apk...
[2011-08-19 13:51:29 - HelloGalleryTest] Success!
[2011-08-19 13:51:29 - HelloGalleryTest] Project dependency found,
installing: HelloGallery
[2011-08-19 13:51:29 - HelloGallery] Uploading HelloGallery.apk onto
device '428024642BF9457'
[2011-08-19 13:51:29 - HelloGaller开发者_JS百科y] Installing HelloGallery.apk...
[2011-08-19 13:51:31 - HelloGallery] Success!
[2011-08-19 13:51:31 - HelloGalleryTest] Launching instrumentation
android.test.InstrumentationTestRunner on device 428024642BF9457
[2011-08-19 13:51:31 - HelloGalleryTest] Collecting test information
[2011-08-19 13:51:32 - HelloGalleryTest] Sending test information to
Eclipse
[2011-08-19 13:51:32 - HelloGalleryTest] Running tests...
Below is the sample project code and test case I've written that exhibits the problem I have described above. HelloGallery.getChildStaticTransformation(View child, Transformation t) is the method which has the call to invalidate().
The other files included below are for completeness incase you want to try and build the project and observe the behavior I have described.
START::HELLO_GALLERY_PROJECT-FILES
HelloGallery.java
package com.hello;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Transformation;
import android.widget.Gallery;
public class HelloGallery extends Gallery {
private Camera mCamera = new Camera();
/**
* The position (index) of the centered view in the gallery.
*/
public int mCenterViewPositionIndex;
public HelloGallery(Context context, AttributeSet attrs) {
super(context, attrs);
this.setStaticTransformationsEnabled(true);
}
/**
* {@inheritDoc}
*
* @see #setStaticTransformationsEnabled(boolean)
*/
protected boolean getChildStaticTransformation(View child,
Transformation t) {
t.clear();
mCamera.save();
final Matrix imageMatrix = t.getMatrix();
int position = getPositionForView(child);
t.setTransformationType(Transformation.TYPE_MATRIX);
if (child == getSelectedView()) {
mCenterViewPositionIndex = position;
t.setAlpha(1f);
mCamera.translate(-20, 20, -200);
}
else if(((mCenterViewPositionIndex - 1) == position )) {
t.setAlpha(0.5f);
// no need to zoom this view.
}
else if((mCenterViewPositionIndex + 1) == position ) {
t.setAlpha(0.5f);
// no need to zoom this view.
}
else if(((mCenterViewPositionIndex - 2) == position )) {
t.setAlpha(0.35f);
mCamera.translate(0, 0, 250);
}
else if((mCenterViewPositionIndex + 2) == position ) {
t.setAlpha(0.35f);
mCamera.translate(0, 0, 250);
}
else if(((mCenterViewPositionIndex - 3) == position )) {
t.setAlpha(0.1f);
mCamera.translate(0, 0, 350);
}
else if((mCenterViewPositionIndex + 3) == position ) {
t.setAlpha(0.2f);
mCamera.translate(0, 0, 350);
}else {
t.setAlpha(0.1f);
mCamera.translate(0, 0, 450);
}
mCamera.getMatrix(imageMatrix);
mCamera.restore();
// invalidate(); // <---- commenting out this line allows the Test
to run. Uncommenting this line will not allow the Test to run.
return true;
}
}
HelloGalleryActivity.java
package com.hello;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Gallery;
public class HelloGalleryActivity extends Activity implements OnItemClickListener {
private Gallery mGallery;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mGallery=(Gallery)findViewById(R.id.gallery);
//String array holding the values
String [] text=new String[]{"Hello","Hi","Alloha","Bonjour","Hallo","¡Hola"};
//Circular Array adapter to display our values in the gallery control
HomeGalleryAdapter adapter = new HomeGalleryAdapter(this, R.layout.gallery_item_layout, text);
mGallery.setAdapter(adapter);
mGallery.setSelection(adapter.MIDDLE);
mGallery.setOnItemClickListener(this);
mGallery.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View arg0, MotionEvent arg1) {
Log.d("HelloGalleryActivity", " --> onTouch");
return false;
}
});
}
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
Log.d("HelloGalleryActivity", " --> onItemClick");
}
}
HelloGalleryAdapter.java
package com.hello;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
public class HomeGalleryAdapter extends ArrayAdapter<String> {
private LayoutInflater mLayoutInflater;
private TextView mText;
private int mLayoutId;
public static final int HALF_MAX_VALUE = Integer.MAX_VALUE/2;
public final int MIDDLE;
private String[] mObjects;
public HomeGalleryAdapter(Context context, int layoutId, String[]
lables) {
super(context, 0, lables);
mLayoutInflater = ((Activity) context).getLayoutInflater();
mLayoutId = layoutId;
mObjects = lables;
MIDDLE = HALF_MAX_VALUE - HALF_MAX_VALUE % lables.length;
}
public int getCount() {
return Integer.MAX_VALUE;
}
public String getItem(int position) {
return mObjects[position % mObjects.length];
}
public long getItemId(int position) {
return position % mObjects.length;
}
public View getView(int position, View convertView, ViewGroup group)
{
if (convertView == null) {
convertView = mLayoutInflater.inflate(mLayoutId, group, false);
}
mText = (TextView) convertView.findViewById(R.id.gallery_item_text);
mText.setText((String) getItem(position));
mText.setTextColor(Color.WHITE);
convertView.setTag(mText.getText());
return convertView;
}
}
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/textview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<com.hello.HelloGallery
android:id="@+id/gallery"
android:layout_gravity="center"
android:spacing="0dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
res/layout/gallery_item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/home_gallery_item_layout"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<RelativeLayout
android:id="@+id/gallery_item_background"
android:layout_width="100dip"
android:layout_height="100dip"
android:orientation="horizontal"
android:background="@drawable/gallery_item_selector">
<ImageView
android:src="@drawable/icon"
android:id="@+id/gallery_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
<TextView
android:id="@+id/gallery_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="20sp" />
</LinearLayout>
res/drawable/gallery_item_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/background_selected" /> <!--
pressed -->
<item android:drawable="@drawable/background_normal" /> <!--
default -->
</selector>
res/drawable/background_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#80000000"
android:endColor="#80FFFFFF"
android:angle="45"/>
<padding android:left="7dp"
android:top="7dp"
android:right="7dp"
android:bottom="7dp" />
<corners android:radius="8dp" />
</shape>
res/drawable/background_selected.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#80000000"
android:endColor="#807EB6FF"
android:angle="45"/>
<padding android:left="7dp"
android:top="7dp"
android:right="7dp"
android:bottom="7dp" />
<corners android:radius="8dp" />
</shape>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hello"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk
android:minSdkVersion="7" />
<application
android:icon="@drawable/icon"
android:label="@string/app_name">
<activity
android:name=".HelloGalleryActivity"
android:label="@string/app_name">
<intent-filter>
<action
android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
END::HELLO_GALLERY_PROJECT-FILES
.
START::HELLO_GALLERY_TEST_PROJECT-FILES
HelloGalleryActivityTest.java ( <---- this file goes in the test project !!)
package com.hello.test;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.Gallery;
import android.widget.SpinnerAdapter;
import com.hello.HelloGalleryActivity;
public class HelloGalleryActivityTest extends
ActivityInstrumentationTestCase2<HelloGalleryActivity> {
private HelloGalleryActivity mActivity; // the activity under test
private Gallery mGallery;
private String[] mGalleryDataTestArray;
private SpinnerAdapter mGalleryData;
public HelloGalleryActivityTest() {
super("com.hello", HelloGalleryActivity.class);
mGalleryDataTestArray = new String[] { "Hello", "Hi", "Alloha",
"Bonjour", "Hallo", "¡Hola" };
}
@Override
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
mActivity = this.getActivity();
mGallery = (Gallery) mActivity.findViewById(com.hello.R.id.gallery);
mGalleryData = (SpinnerAdapter) mGallery.getAdapter();
}
public void testPreconditions() {
assertNotNull(mActivity);
assertNotNull(mGallery);
assertNotNull(mGallery.getOnItemClickListener());
assertNotNull(mGalleryData);
}
public void testInitialGalleryPosition() {
int position = mGallery.getSelectedItemPosition();
String galleryItemLabel = (String)
mGallery.getItemAtPosition(position);
assertEquals(mGalleryDataTestArray[0], galleryItemLabel);
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hello.test"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="12" />
<instrumentation android:targetPackage="com.hello"
android:name="android.test.InstrumentationTestRunner" />
<application android:icon="@drawable/icon" android:label="@string/
app_name">
<uses-library android:name="android.test.runner" />
</application>
</manifest>
END::HELLO_GALLERY_TEST_PROJECT-FILE
Try postInvalidate() instead of invalidate().
I'm not 100% sure what's going on but I suspect there's something in your code that's blocking the UI thread. Using postInvalidate() will return right away and let your code finish, which will unblock the UI thread.
Problem solved!
the invalidate call is NOT needed.. it screws things up PERIOD!
The trick was to add a method to HelloGallery.java to set the center position and to call setPosition on the super class.
HelloGallery.java
public void setSelectionToCenter(int position) {
mCenterViewPositionIndex = position;
setSelection(mCenterViewPositionIndex);
}
Then in HelloGalleryActivity call the setSelectionToCenter method rather than setSelection.
HelloGalleryActivity
public void onCreate(Bundle savedInstanceState) {
...
((HelloGallery)mGallery).setSelectionToCenter(adapter.MIDDLE);
...
}
The test runs like a charm now! Woot!
Thanks goes out to Betsy the mystery developer for tracking this down for me!
精彩评论