I'm experiencing strange behaviors and NullPointerException
(sometimes) with a Singleton
pattern using in an Activity
.
The activity in manifest (declared the orientation landscape):
<activity
android:name="com.lux.game.MainActivity"
android:screenOrientation="landscape" />
The activity class:
private GameManager.OnEventListener mEventListener = new GameManager.OnEventListener {
@Override
public void onEvent(Event event) {
if (event == Event.PLAYER_SELECTED_PUZZLE) {
// Do something on the UI
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// If the orientation is landscape, then initialize the Game Manager
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
GameManager.getInstance().init();
// Register an interface to deal with game events
GameManager.getInstance().registerForEvent(mEventListener);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// The activity was destroyed, remove the interface and clear the references
GameManager.getInstance().cleanUp();
}
The GameManager
singleton:
public class GameManager {
private static GameManager mInstance;
private GameManager() {
}
public static synchronized getInstance() {
if (mInstance == null) {
mInstance = new GameManager();
}
return mInstance;
}
}
As you can see, I'm using the GameManager
singleton to deal with game events (player selects a card, game end, etc). The singleton is instantiated at onCreate()
if the orientation is in landscape and an interface
is passed to the singleton and it's stored as a member of the class. The interface
is removed at activity's onDestroy()
.
Activity lifecycle and logs:
#1 onCreate(), orientation: LANDSCAPE
#2 back button pressed
#3 onDestroy(), orientation: PORTRAIT
Everything was working as I expected, until I started to test the application by locking/unlocking and pressing the HOME button on the device (real device, not emulator).
Activity lifecycle and logs:
#1 onCreate(), orientation: LANDSCAPE
#2 lock phone
#3 onDestroy(), orientation: PORTRAIT
#4 onCreate(), orientation: PORTRAIT
#5 unlock phone
#6 onDestroy(), orientation: PORTRAIT
#7 onCreate(), orientation: LANDSCAPE
onCreate
and onDestroy
is called sever times (normal behavior because orientation change), however this does not creates an issue, because the activity's lifecycle
methods are called in the excpeted order and the interface
is removed, and I'm not leaving any reference which could cause memory leak.
The problem (sometimes) comes to the surface if the steps above are getting called in a different order:
#1 onCreate(), orientation: LANDSCAPE (inst #1)
#2 lock phone
#3 onDestroy(), orientation: PORTRAIT (inst #1)
#4 onCreate(), orientation: PORTRAIT (inst #2)
#5 unlock phone
#6 onCreate(), orientation: LANDSCAPE (inst #3)
#7 onDestroy(), orientation: PORTRAIT (inst #2)
As you can see, step 6 and 7 are inverse compared to the previous steps. onCreate
is called before onDestroy
. Also I have added a number (inst #N) , which indicates how many activity instances are getting created and which is getting destroyed.
According to the logs, there are a total of 3 different activity instances which are getting created while I'm locking/unlocking the phone. The problem is caused by the last step, when the 3rd activity (inst #3) is created, and the 2nd activity (inst #2) is destroyed. I'm using a Singleton
(only 1 instance exists in the whole application) to deal with events and manage the game, at activity's onDestroy
the Singleton
expects that the game has ended, and it must remove the references to the activity. This way, if an event occur in the game a NullPointerException
is being thrown because the Singleton
has cleared all the references.
I think that you should create a new class that extends Application and init your Singleton in the onCreate() metod of them.
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
GameManager.getInstance().init();
}
}
In your manifest, in the application tag, add android:name="MyApplication" In any to have a better singleton thread safe, due to your costructor, I suggest you to create a static istance of your objet in application.
public class MyApplication extends Application {
public static GameManager gameManager = new GameManager();
@Override
public void onCreate() {
super.onCreate();
gameManager.init();
}
}
And after you can call your singleton by
MyApplication.gameManager
This is the best way to have a singleton thread safe
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.