当我尝试在我的活动中运行 Andriod JUnit 测试时出现错误:
Test run failed: Instrumentation run failed due to 'java.lang.NullPointerException'
05-31 15:57:19.159: E/AndroidRuntime(22342): FATAL EXCEPTION: main
05-31 15:57:19.159: E/AndroidRuntime(22342): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.wecharades/com.example.wecharades.views.GameDashboardActivity}: java.lang.NullPointerException
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1967)
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1992)
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.app.ActivityThread.access$600(ActivityThread.java:127)
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1158)
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.os.Handler.dispatchMessage(Handler.java:99)
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.os.Looper.loop(Looper.java:137)
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.app.ActivityThread.main(ActivityThread.java:4441)
05-31 15:57:19.159: E/AndroidRuntime(22342): at java.lang.reflect.Method.invokeNative(Native Method)
05-31 15:57:19.159: E/AndroidRuntime(22342): at java.lang.reflect.Method.invoke(Method.java:511)
05-31 15:57:19.159: E/AndroidRuntime(22342): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
05-31 15:57:19.159: E/AndroidRuntime(22342): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
05-31 15:57:19.159: E/AndroidRuntime(22342): at dalvik.system.NativeStart.main(Native Method)
05-31 15:57:19.159: E/AndroidRuntime(22342): Caused by: java.lang.NullPointerException
05-31 15:57:19.159: E/AndroidRuntime(22342): at java.util.TreeMap$1.compare(TreeMap.java:72)
05-31 15:57:19.159: E/AndroidRuntime(22342): at java.util.TreeMap$1.compare(TreeMap.java:70)
05-31 15:57:19.159: E/AndroidRuntime(22342): at java.util.TreeMap.find(TreeMap.java:277)
05-31 15:57:19.159: E/AndroidRuntime(22342): at java.util.TreeMap.findByObject(TreeMap.java:351)
05-31 15:57:19.159: E/AndroidRuntime(22342): at java.util.TreeMap.get(TreeMap.java:177)
05-31 15:57:19.159: E/AndroidRuntime(22342): at com.example.wecharades.model.Model.getTurns(Model.java:258)
05-31 15:57:19.159: E/AndroidRuntime(22342): at com.example.wecharades.model.DataController.getTurns(DataController.java:460)
05-31 15:57:19.159: E/AndroidRuntime(22342): at com.example.wecharades.presenter.GameDashboardPresenter.createDashboard(GameDashboardPresenter.java:53)
05-31 15:57:19.159: E/AndroidRuntime(22342): at com.example.wecharades.views.GameDashboardActivity.onStart(GameDashboardActivity.java:48)
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1133)
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.app.Activity.performStart(Activity.java:4475)
05-31 15:57:19.159: E/AndroidRuntime(22342): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1940)
05-31 15:57:19.159: E/AndroidRuntime(22342): ... 11 more
问题是我的一些测试得到了这个错误信息,而一些工作正常。它们都链接到同一个项目,所有测试都使用 ActivityInstrumentationTestCase2。即使我刚刚获得了一些测试的框架并且所有测试都基于相同的代码,但其中一些只是无法工作并不断出现错误。
这是一个非常简单的代码,它得到了这个错误:
public class GameDashboardActivityTest extends ActivityInstrumentationTestCase2<GameDashboardActivity> {
public GameDashboardActivityTest() {
super(GameDashboardActivity.class);
}
private GameDashboardActivity activity;
protected void setUp() throws Exception {
super.setUp();
activity = getActivity();
assertNotNull(activity);
}
/**
* Run the void updateScore(int currentPlayersScore, int otherPlayerScore) method test.
* @throws Throwable
*
*/
public void testUpdateScore_1()
throws Throwable {
assertTrue(true);
}
protected void tearDown() throws Exception {
super.tearDown();
}
}
GameDashboardActivity
package com.example.wecharades.views;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.ImageButton;
import android.widget.TableLayout;
import android.widget.TextView;
import com.example.wecharades.R;
import com.example.wecharades.presenter.GameDashboardPresenter;
/**
* View which displays the game dashboard
* @author weCharade
*/
public class GameDashboardActivity extends GenericActivity {
private TableLayout myTable;
private TextView title;
private TextView yourScore;
private TextView opponentsScore;
private GameDashboardPresenter presenter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState, new GameDashboardPresenter(this));
//Set the title bar
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.game_screen);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title_bar_home);
//Get references to instances
presenter = (GameDashboardPresenter) super.getPresenter();
title = (TextView) findViewById(R.id.titleText);
yourScore = (TextView) findViewById(R.id.yourScore);
opponentsScore = (TextView) findViewById(R.id.opponentScore);
myTable = (TableLayout) findViewById(R.id.table);
}
@Override
public void onStart(){
super.onStart();
presenter.createDashboard(myTable);
}
/**
* Updates both players' scores
* @param currentPlayersScore
* @param otherPlayerScore
*/
public void updateScore(int currentPlayersScore, int otherPlayerScore) {
yourScore.setText(Integer.toString(currentPlayersScore));
opponentsScore.setText(Integer.toString(otherPlayerScore));
}
/**
* Set title of the Game dashboard
* @param title
*/
public void setTitle(String title) {
this.title.setText(title);
}
/**
* Go to StartActivity
* @param v
*/
public void onClickHome(View v){
presenter.goToStartActivity();
}
@Override
protected RefreshProgressBar getProgressBar() {
return null;
}
}
Model.java
/**
* This class stores all the data available in the game locally.
* This class exist to reduce the number or request to parse.com
*
*/
public class Model implements Serializable{
private static final long serialVersionUID = -8167671678222883966L;
//The name of our model save file
private static final String SAVE_FILE = "model.save";
//Stored variables to use in other classes - should maybe be in another class.
public static final int
FINISHEDGAMES_SAVETIME = 168
, FINISHEDGAMES_NUMBERSAVED = 10
, INVITATIONS_SAVETIME = 72;
/*
* A variable that can be changed in order to purge the model - this is done manually when needed!
* When this is set to true, the model will be forced to be recreated. This is done to purge the
* Model and retrieve a mirror of the database information, while preserving login status
* (and avoid having to reinstall and log in and out again). This MIGHT be implemented as a feature later.
*
* -- DO NOT FORGET TO RESET THIS AFTERWARDS! --
*/
private static boolean PURGE = false;
//A variable to check if model is already saved.
private boolean SAVED = false;
//A variable which is called when a user logs out
// - the model exists a moment so we may finish any queries first
private static boolean RECREATE = false;
//Two maps for games for increased speed and ease of use
private TreeMap<Game, ArrayList<Turn>> gameList = new TreeMap<Game, ArrayList<Turn>>();
private TreeMap<String, Game> gameIdList = new TreeMap<String, Game>();
//Two maps for player names and id:s. The second one is used for increased speed and ease of use
private TreeMap<String, Player> storedPlayers = new TreeMap<String, Player>();
private TreeMap<String, String> storedPlayerNames = new TreeMap<String, String>();
private Player currentPlayer = null;
// Invitations are stored locally in two lists
private LinkedList<Invitation> sentInvitations = new LinkedList<Invitation>();
private LinkedList<Invitation> receiveInvitations = new LinkedList<Invitation>();
//Singleton
private static Model singleModel;
private Model(Context context){
//Creating a file to save to
if(context != null){
saveModel(context);
}
}
/**
* Use this method to get the singleton instance of the model where necessary.
* @return the Model
*/
public static Model getModelInstance(Context context){
if(PURGE){
//If the PURGE variable is set to true (done manually), the model will be recreated
eraseModel(context);
singleModel = null;
PURGE = false;
}
if(singleModel == null){
//Try to load from storage
singleModel = loadModel(context);
}
if(singleModel == null || RECREATE){
//If there were no previous models present, create a new one
singleModel = new Model(context);
RECREATE = false;
}
return singleModel;
}
/**
* A method to save the current model to memory.
* @param context - used to retrieve a save location
*/
public void saveModel(Context context){
if(!SAVED && context != null){
try {
FileOutputStream ops = context.openFileOutput(SAVE_FILE, Context.MODE_PRIVATE);
ObjectOutputStream oOut = new ObjectOutputStream(ops);
oOut.writeObject(singleModel);
oOut.close();
SAVED = true;
} catch (IOException e) {
Log.d("IO - Model save", e.getMessage());
}
}
}
/**
* Method to load a model form memory
* @param context
* @return
*/
private static Model loadModel(Context context){
Model singleModel = null;
if(context != null){
try {
ObjectInputStream oIn = new ObjectInputStream(context.openFileInput(SAVE_FILE));
Object obj = oIn.readObject();
if (obj != null && obj.getClass().equals(Model.class)){
singleModel = (Model) obj;
}
} catch (FileNotFoundException e1){
Log.d("IO - Model load", "No file found");
} catch (IOException e2){
Log.d("IO - Model load", "IOException");
} catch (ClassNotFoundException e3){
Log.d("IO - Model load", "ClassNotFound");
}
}
return singleModel;
}
/**
* Called to erase the current model from memory and disk.
* @param context
*/
private static void eraseModel(Context context){
if(context != null){
File modelFile = new File(context.getFilesDir(), SAVE_FILE);
if(modelFile.delete()){
Log.d("Model - File:","Removed file");
}
RECREATE = true;
}
}
//Games ---------------------------------------------------------------
/**
* Updates a list of games. If a game is not existant, it will be added to the list.
* @param games
*/
public void putGameList(ArrayList<Game> games){
for(Game game : games){
putGame(game);
}
SAVED = false;
}
/**
* Updates a game in the internal list of games. Will also create new games that does not exist.
* @param game - the game to be updated
*/
public void putGame(Game game){
//This is actually kind of fast, although it might look a bit weird.
ArrayList<Turn> tempTurns;
if(gameList.containsKey(game) && gameList.get(game) != null){
tempTurns = gameList.get(game);
gameList.remove(game);
gameList.put(game,tempTurns);
gameIdList.put(game.getGameId(), game);
} else{
gameList.put(game, null);
gameIdList.put(game.getGameId(), game);
}
SAVED = false;
}
/**
* Return an ArrayList with current games
* @return - an arraylist containing games
*/
public ArrayList<Game> getGames(){
return new ArrayList<Game>(gameList.keySet());
}
/**
* Gets a game from its game id
* @param parseId
* @return a Game, or null it does not exist
*/
public Game getGame(String parseId){
return gameIdList.get(parseId);
}
/**
* Removes a game form the model
* @param game - the game to be deleted
* @return - true if the game was in the list, false otherwise
*/
public void removeGame(Game game){
gameIdList.remove(game.getGameId());
gameList.remove(game);
SAVED = false;
}
/**
* Use to update a single turn of a game. This will add a turn if it does not exist,
* as well as update its state if it is existant.
* @param game - the game in question
* @param turn - the turn of the game
* @throws NoSuchElementException if no game is found
*/
public void putTurn(Turn turn){
if(turn != null){
if(!gameIdList.containsKey(turn.getGameId()))
throw new NoSuchElementException();
Game game = getGame(turn.getGameId());
ArrayList<Turn> listOfTurns = gameList.get(game);
if(listOfTurns == null){
listOfTurns = new ArrayList<Turn>();
gameList.put(game, listOfTurns);
} else if(listOfTurns.contains(turn)){
//If the turn contains the turn, we must delete it first
listOfTurns.remove(turn);
}
listOfTurns.add(turn);
}
SAVED = false;
}
/**
* Updates a list of turns at once - the existing list will be overwritten.
* @param turnList
* @throws NoSuchElementException if no game is found
*/
public void putTurns(ArrayList<Turn> turnList) throws NoSuchElementException{
//Do not simply replace the list, as this might cause problems with the amount of turns etc.
for(Turn turn : turnList){
putTurn(turn);
}
}
/**
* Get a list of turns for a game
* @param game - the game
* @return - an arraylist of turns
*/
public ArrayList<Turn> getTurns(Game game){
return gameList.get(game);
}
/**
* Returns the current turn from the model
* @param game - the game to fetch from
* @return a Turn
*/
public Turn getCurrentTurn(Game game) {
if(game != null){
ArrayList<Turn> turns = getTurns(game);
if(turns != null){
for(Turn t : turns){
//Find the turn with CurrentTurnNumber
if(t.getTurnNumber() == game.getTurnNumber()){
return t;
}
}
}
}
return null;
}
//Players ---------------------------------------------------------------
/**
* Puts a player in stored players
* @param player - the player to be stored
*/
public void putPlayer(Player player){
//The data for a player should always be updated
storedPlayerNames.put(player.getName(), player.getParseId());
storedPlayers.put(player.getParseId(),player);
SAVED = false;
}
/**
* Puts a collection of players into the model
* @param players - a collection of players
*/
public void putPlayers(Collection<Player> players){
storedPlayers.clear();
storedPlayerNames.clear();
for(Player player : players){
putPlayer(player);
}
}
/**
* Used to get a player representation from a username
* @param username - the player username
* @return a Player, or null if no player was found
*/
public Player getPlayer(String username){
Player retPlayer = null;
if(storedPlayerNames.containsKey(username)){
retPlayer = storedPlayers.get(storedPlayerNames.get(username));
}
return retPlayer;
}
/**
* Used to get a player representation from a username
* @param parseId - the player id
* @return a Player or null if not found
*/
public Player getPlayerById(String parseId){
return storedPlayers.get(parseId);
}
/**
* Designates a player as the current player. If the player does not exist in cache,
* it gets added.
*/
public void setCurrentPlayer(Player player){
currentPlayer = player;
putPlayer(player);
SAVED = false;
}
/**
* Returns the logged in player player (ParseUser)
* @return A Player representation of The current player, or null if this player does not exist.
*/
public Player getCurrentPlayer(){
return currentPlayer;
}
/**
* Deletes the current player entirely from the model. Should be done when user logs out.
*/
public void logOutCurrentPlayer(Context context){
eraseModel(context);
}
//Invitations ---------------------------------------------------------------
//Received invitations are not needed here, as they should allways be fetched from the database.
/**
* Set all sent invitations from this player. This replaces the local version of this game.
* @param invitations - The invitations to add
*/
public void setSentInvitations(LinkedList<Invitation> invitations){
if(invitations != null){
sentInvitations = invitations;
} else{
sentInvitations.clear();
}
SAVED = false;
}
/**
* Set all received invitations to this player
* @param invitations - The invitations to add
*/
public void setReceivedInvitations(LinkedList<Invitation> invitations){
if(invitations != null){
receiveInvitations = invitations;
} else{
receiveInvitations.clear();
}
SAVED = false;
}
/**
* Retrieve a list of Invitations sent from this device.
* @return A List containing invitations.
*/
public List<Invitation> getSentInvitations(){
return sentInvitations;
}
/**
* Retrieve a list of Invitations the current player has received
* @return A List containing invitations.
*/
public List<Invitation> getReceivedInvitations(){
return receiveInvitations;
}
}
通用活动.java
/**
* Abstract class which holds implementations of, for the activities, generic methods
* @author weCharade
*/
public abstract class GenericActivity extends Activity {
//Presenter object, declared protected and therefore enabling access to extending classes
protected Presenter presenter;
/**
* onCreate-method which sets the presenter
* @param savedInstanceState
* @param presenter
*/
public void onCreate(Bundle savedInstanceState, Presenter presenter) {
//Only send the Bundle-object to the super class
super.onCreate(savedInstanceState);
this.presenter = presenter;
}
/**
* This method will return a ProgressBar in the form of a spinner.
* Use this spinner to give a visual queue to the user that something is happening
* in the background.
* @return The progressbar of the view.
*/
protected abstract IProgress getProgressBar();
/**
* Called to show progress spinning when waiting for the server
*/
public void showProgressBar() {
if(getProgressBar() != null) {
getProgressBar().show();
}
}
/**
* Called to hide progress spinning when the server has responded
*/
public void hideProgressBar() {
if(getProgressBar() != null) {
getProgressBar().hide();
}
}
/**
* Private help method to get all clickable objects in a list from a view.
* @param view
* @return an ArrayList with all Views within the parameter view
*/
private ArrayList<View> getAllChildren(View view) {
//Check if the view is a "single" view
if (!(view instanceof ViewGroup)) {
ArrayList<View> viewArrayList = new ArrayList<View>();
viewArrayList.add(view);
return viewArrayList;
}
ArrayList<View> result = new ArrayList<View>();
/*
* Add all children of the ViewGroup and eventually return the
* ArryaList containing the views.Childrens of a children in a ViewGroup are
* added to the list by recursively calling the method.
*/
ViewGroup vg = (ViewGroup) view;
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);
ArrayList<View> viewArrayList = new ArrayList<View>();
viewArrayList.add(view);
viewArrayList.addAll(getAllChildren(child));
result.addAll(viewArrayList);
}
return result;
}
/**
* Enable all clickable objects in view.
* @param view a List containing all elements of the view to enable
*/
public void enabledView() {
List<View> viewList = getAllChildren(this.getWindow().getDecorView().findViewById(android.R.id.content));
//Loop through the list of views and enable them.
for (View child : viewList) {
child.setEnabled(true);
}
}
/**
* Disable or disable all clickable objects in view.
* @param view a List containing all elements of the view to disable
*/
public void disableView() {
List<View> viewList = getAllChildren(this.getWindow().getDecorView().findViewById(android.R.id.content));
//Loop through the list of views and disable them.
for (View child : viewList) {
child.setEnabled(false);
}
}
/**
* Show a toast to the user.
* @param msg
*/
public void showToast(String msg) {
//Declare and get reference to a LayoutInflater
LayoutInflater inflater = getLayoutInflater();
//Inflate custom Toast layout
View layout = inflater.inflate(R.layout.toast_success, (ViewGroup) findViewById(R.id.toast_layout_root));
TextView text = (TextView) layout.findViewById(R.id.toastText);
text.setText(msg);
//Create Toast, set duration and layout
Toast toast = new Toast(getApplicationContext());
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();
}
/**
* Show a message to the user, most often an error. Uses a dialog-box with one button.
* @param error
*/
public void showNegativeDialog(String negativeTitle, String negativeText, String buttonText) {
final Dialog dialog = new Dialog(this);
//Actions to set custom Dialog layout and set properties
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog_negative);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.setCanceledOnTouchOutside(false);
TextView title = (TextView) dialog.findViewById(R.id.negativeTitle);
title.setText(negativeTitle);
TextView text = (TextView) dialog.findViewById(R.id.negativeText);
text.setText(negativeText);
Button button = (Button) dialog.findViewById(R.id.dismiss);
button.setText(buttonText);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
}
/**
* Show a message to the user, most often an error. Uses a dialog-box with two buttons.
* @param negativeTitle
* @param negativeText
* @param buttonText1
* @param buttonText2
*/
public void showNegativeDialog(String negativeTitle, String negativeText, String buttonText1, String buttonText2) {
//Create new Dialog-object
final Dialog dialog = new Dialog(this);
//Actions to set custom Dialog layout and set properties
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog_negative_two);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.setCanceledOnTouchOutside(false);
TextView title = (TextView) dialog.findViewById(R.id.negativeTitle);
title.setText(negativeTitle);
TextView text = (TextView) dialog.findViewById(R.id.negativeText);
text.setText(negativeText);
Button button1 = (Button) dialog.findViewById(R.id.dismiss);
button1.setText(buttonText1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
Button button2 = (Button) dialog.findViewById(R.id.back);
button2.setText(buttonText2);
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
}
/**
* Show a positive dialog to the user. Most often a success message
* @param error
*/
public void showPositiveDialog(String positiveTitle, String positiveText, String buttonText) {
//Create new Dialog-object
final Dialog dialog = new Dialog(this);
//Actions to set custom Dialog layout and set properties
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog_positive);
dialog.setCanceledOnTouchOutside(false);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
TextView title = (TextView) dialog.findViewById(R.id.positiveTitle);
title.setText(positiveTitle);
TextView text = (TextView) dialog.findViewById(R.id.positiveText);
text.setText(positiveText);
Button button = (Button) dialog.findViewById(R.id.dismiss);
button.setText(buttonText);
button.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
}
/**
* Show a progress dialog to the user.
* @param error
*/
public void showProgressDialog(String positiveTitle, String positiveText, String buttonText) {
//TODO: To be implemented
}
@Override
protected void onStop(){
//Save the model to disk whenever an activity is closed.
presenter.saveState();
super.onStop();
}
/**
* Get the presenter associated with an activity.
* @return A presenter
*/
protected Presenter getPresenter(){
return presenter;
}
}
NullPointerException 似乎发生在 getActivity() 部分,但我不知道为什么。有任何想法吗?
//费利克斯