I have implemented simple drill-down pattern with several activities that pass data to display. But when I go back (either android back button or toolbar's back button) my app crashes in onCreate because parameters are gone.
Ok, there are hundreds similar questions. Please read on. I did investigation and even senior developer reviewed my code and he did not understood.
This is a log how it was called:
TestItemsActivity.onCreate()
TestItemsActivity.onStart()
TestItemsActivity.onResume()
TestItemsActivity.onPause()
RunTestActivity.onCreate()
RunTestActivity.onStart()
RunTestActivity.onResume()
TestItemsActivity.onSaveInstanceState()
TestItemsActivity.onStop()
RunTestActivity.onPause()
TestItemsActivity.onCreate()
The last sentence is important - why is onCreate called again? Activity lifecycle says that onResume is expected.
Let's look at the source code. I use appcompat 7:25.1.
TestItemsActivity
public class TestItemsActivity extends AppCompatActivity {
TestScript script;
protected void onCreate(Bundle state) {
log.debug("onCreate()");
super.onCreate(state);
setContentView(R.layout.act_script_items);
if (state != null) {
script = (TestScript) state.getSerializable(ScriptListActivity.KEY_SCRIPT);
} else {
script = (TestScript) getIntent().getSerializableExtra(ScriptListActivity.KEY_SCRIPT);
}
...
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
Intent intent = new Intent(TestItemsActivity.this, RunTestActivity.class);
intent.putExtra(RunTestActivity.KEY_POSITION, position);
intent.putExtra(ScriptListActivity.KEY_SCRIPT, script);
TestItemsActivity.this.startActivity(intent);
}
});
}
protected void onSaveInstanceState(Bundle state) {
state.putSerializable(ScriptListActivity.KEY_SCRIPT, script);
super.onSaveInstanceState(state);
}
AppCompatActivity
public class RunTestActivity extends AppCompatActivity {
protected void onCreate(Bundle state) {
log.debug("onCreate()");
super.onCreate(state);
setContentView(R.layout.act_script_items);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbarCalc);
if (toolbar != null) {
setSupportActionBar(toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
moveTaskToBack(true);
}
});
}
Intent intent = getIntent();
script = (TestScript) intent.getSerializableExtra(ScriptListActivity.KEY_SCRIPT);
}
And now the most interesting source code is the manifest:
<activity
android:name=".activities.TestItemsActivity"
android:label="#string/action_academy"
android:parentActivityName=".activities.ScriptListActivity">
</activity>
<activity
android:name=".activities.RunTestActivity"
android:label="#string/action_academy"
android:parentActivityName=".activities.TestItemsActivity">
</activity>
Accidentally I found that if I remove parentActivityName than android's back button does not start new activity (onCreate) and my app does not crashes! Unfortunatelly toolbar's back button disappears.
Some SO questions I read:
Saving Android Activity state using Save Instance State
save the state when back button is pressed
How to save activity state after pressing back button?
Bundle savedInstanceState is always null
SavedInstantState created during onSaveInstanceState not restored in onCreate
Can somebody explains what is going there?
Complete stacktrace including parts that I removed from source code above. It fails in adapter because scripts instance is null. The activity failed to initialize it either from intent or bundle (which is null despite that onSaveInstanceState was called)
java.lang.RuntimeException: Unable to start activity ComponentInfo{lelisoft.com.lelimath.debug/lelisoft.com.lelimath.activities.TestItemsActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List lelisoft.com.lelimath.data.TestScript.getItems()' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List lelisoft.com.lelimath.data.TestScript.getItems()' on a null object reference
at lelisoft.com.lelimath.adapter.TestItemAdapter.<init>(TestItemAdapter.java:97)
at lelisoft.com.lelimath.activities.TestItemsActivity.onCreate(TestItemsActivity.java:55)
at android.app.Activity.performCreate(Activity.java:6679)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2618)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
Update:
Workaround for this problem is to use a theme without a toolbar:
<activity
android:name=".activities.CampaignActivity"
android:label="#string/action_academy"
android:theme="#style/AppTheme.NoActionBar">
</activity>
Unfortunatelly I need the toolbar for one activity.
As you can see from the trace you provided, the activity was stopped and state was saved. Therefore later when activity is to be restored it will call onCreate().
TestItemsActivity.onSaveInstanceState()
TestItemsActivity.onStop()
Related
It seems that this unmarshalling exception always gets thrown when an activity is recreated with saved instance state during espresso tests. I have reproduced it with a very basic android app. Here are the steps:
Create an android app with two activities which each have one button. The button on the first activity opens the second activity. The button on the second activity closes the current activity.
Add an espresso test which simply opens the first activity, clicks the button (to open the second activity), then clicks the button on the second activity (to finish the second activity and go back to the first activity).
On your emulator, be sure to enable "Don't Keep Activities" on your emulator.
In my real app, it varies based on the activity which class will be "unknown" to cause the unmarshalling. In this specific example, it's apparently the toolbar. I have found that by removing specific entries ("androidx.lifecycle.BundlableSavedStateRegistry.key" and "android:viewHierarchyState") from the saved instance state that it will workaround this exception during espresso tests, but of course then things don't get restored correctly. And I'll reiterate that this is only a problem while running espresso tests. When performing the same exact test steps manually, everything unmarshalls correctly and there are no exceptions.
Changing sdk versions doesn't seem to help either.
That's it.
Here's all the gory code:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// // This will cause the exception to not be thrown during espresso tests
// if(savedInstanceState != null) {
// savedInstanceState.remove("androidx.lifecycle.BundlableSavedStateRegistry.key");
// savedInstanceState.remove("android:viewHierarchyState");
// }
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.open_button).setOnClickListener(v -> startActivity(new Intent(MainActivity.this, ChildActivity.class)));
}
}
public class ChildActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_child);
findViewById(R.id.close_button).setOnClickListener(v -> finish());
}
}
#LargeTest
#RunWith(AndroidJUnit4.class)
public class MainActivityTest {
#Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
#Test
public void test() {
onView(withId(R.id.open_button)).perform(click());
onView(withId(R.id.close_button)).perform(click());
onView(withId(R.id.open_button)).check(matches(isDisplayed()));
}
}
androidx.test.espresso.PerformException: Error performing 'single click - At Coordinates: 115, 272 and precision: 16, 16' on view 'view.getId() is <2131231192/com.example.myapplication:id/close_button>'.
at androidx.test.espresso.PerformException$Builder.build(PerformException.java:1)
at androidx.test.espresso.base.PerformExceptionHandler.handleSafely(PerformExceptionHandler.java:8)
at androidx.test.espresso.base.PerformExceptionHandler.handleSafely(PerformExceptionHandler.java:9)
at androidx.test.espresso.base.DefaultFailureHandler$TypedFailureHandler.handle(DefaultFailureHandler.java:4)
at androidx.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:5)
at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:8)
at androidx.test.espresso.ViewInteraction.desugaredPerform(ViewInteraction.java:11)
at androidx.test.espresso.ViewInteraction.perform(ViewInteraction.java:8)
at com.example.myapplication.MainActivityTest.mainActivityTest(MainActivityTest.java:31)
... 32 trimmed
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: androidx.appcompat.widget.Toolbar$SavedState
at android.os.Parcel.readParcelableCreator(Parcel.java:2839)
at android.os.Parcel.readParcelable(Parcel.java:2765)
at android.os.Parcel.readValue(Parcel.java:2668)
at android.os.Parcel.readSparseArrayInternal(Parcel.java:3118)
at android.os.Parcel.readSparseArray(Parcel.java:2351)
at android.os.Parcel.readValue(Parcel.java:2725)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3037)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:288)
at android.os.BaseBundle.unparcel(BaseBundle.java:232)
at android.os.Bundle.getSparseParcelableArray(Bundle.java:1010)
at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2133)
at android.app.Activity.onRestoreInstanceState(Activity.java:1135)
at android.app.Activity.performRestoreInstanceState(Activity.java:1090)
at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1317)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:2953)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at androidx.test.espresso.base.Interrogator.loopAndInterrogate(Interrogator.java:14)
at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:8)
at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:1)
at androidx.test.espresso.base.UiControllerImpl.injectMotionEvent(UiControllerImpl.java:6)
at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:7)
at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:1)
at androidx.test.espresso.action.Tap.sendSingleTap(Tap.java:5)
at androidx.test.espresso.action.Tap.sendSingleTap$bridge(Unknown Source:0)
at androidx.test.espresso.action.Tap$1.sendTap(Tap.java:3)
at androidx.test.espresso.action.GeneralClickAction.perform(GeneralClickAction.java:6)
at androidx.test.espresso.ViewInteraction$SingleExecutionViewAction.perform(ViewInteraction.java:2)
at androidx.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:25)
at androidx.test.espresso.ViewInteraction.doPerform$bridge(Unknown Source:0)
at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:2)
at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:1)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
From what I can tell, you're supposed to instead use ActivityScenario and
then test the different activities' states using ActivityScenario.moveToState or ActivityScenario.recreate.
I have the following stack trace for a crash caused by an NPE:
Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.daybreak.my.app/com.daybreak.my.app.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ViewSwitcher.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2430)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2490)
at android.app.ActivityThread.access$900(ActivityThread.java:153)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1358)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5456)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:735)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:625)
Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ViewSwitcher.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
at com.daybreak.my.app.TimesFragment.onLocationChange(TimesFragment.java:446)
at com.daybreak.my.app.MainActivity.onLocationChange(MainActivity.java:289)
at com.daybreak.my.app.MainActivity.onCreate(MainActivity.java:112)
at android.app.Activity.performCreate(Activity.java:6302)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1108)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2383)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2490)
at android.app.ActivityThread.access$900(ActivityThread.java:153)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1358)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5456)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:735)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:625)
The way I have setup my app is as follows:
MainActivity
public class MainActivity extends AppCompatActivity implements LocationChangeListener {
#Override
public void onCreate(Bundle savedInstanceState) {
//...
onLocationChange(LocationManager.getSavedLocation(this)); // Manually calling onLocationChange() method
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) return;
showFragment(new TimesFragment(), TimesFragment.TAG);
}
}
#Override
public void onLocationChange(Locatin location) {
if (location == null) return;
//...
// Call attached onLocationChange() if it implements LocationChangeListener
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (f instanceof LocationChangeListener)
((LocationChangeListener) f).onLocationChange(location);
}
}
TimesFragment
public class TimesFragment extends Fragment implements LocationChangeListener {
private ViewSwitcher viewSwitcher;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//...
viewSwitcher = (ViewSwitcher) view.findViewById(R.id.view_switcher);
}
#Override
public void onLocationChange(Location location) {
this.location = location;
viewSwitcher.setOnClickListener(null); //<-- NPE Cause here
updateContent();
}
}
MY EXPECTATION
From what I understand, the Activity.onCreate() will only be called after a fresh start or after the user navigates back to the app after the app has been killed (explicitly by the user or by memory management when other apps need memory). If this happens the fragments will also be destroyed and will need to be created, i.e., fragment's onCreateView() will be called. Therefore calling onLocationChange() from the MainActivity.onCreate() before attaching the fragment is safe as findFragmentById() within the onLocationChange() would not find any fragment.
REALITY
From the stack trace we can see that the call initiated from MainActivity.onCreate(). But what's puzzling for me is that at the time onLocationChange() is called from within MainActivity.onCreate(), findFragmentById() within the onLocationChange() finds the fragment in the view container and calls the fragments onLocationChange(). When this happens the viewSwitcher is NULL and causes the app to crash.
Obviously, fragment has already been added to the view container and the fragments onCreateView() has not been called yet.
THE QUESTION
I am not able to recreate this crash, and not sure of the lifecycle process that is causing this.
So can anyone tell me
how to reproduce this error and
the lifecycle process that is responsible for the flow that is causing the NPE?
This is being caused by device rotation. Can recreate the stack trace by rotating the device.
NOTE: This can happen even when app's orientation is locked (as in my case); if the user is in another app in an orientation that is different than the orientation your app is locked to and they switch back to the app, the orientation lifecycle for your app will be fired.
SOLUTION
Add f != null && f.isResumed() before calling methods from the fragment. isResumed() will return false if fragment hasn't been resumed after recreation.
Everytime the application go in background, and the RAM start filling out, the application start loosing variables (if im not wrong, this is the normal behavior of Android, it loose the instances of variables)
When I bring back the app from background the application crash, because I loose the MainActivity.
There is a way to relaunch this activity if there is not activity at all?
Is an ambiguous question but I found this question but I don't think is the best choice
ActivityManager mngr = (ActivityManager) getSystemService( ACTIVITY_SERVICE );
List<ActivityManager.RunningTaskInfo> taskList = mngr.getRunningTasks(10);
if(taskList.get(0).numActivities == 1 &&
taskList.get(0).topActivity.getClassName().equals(this.getClass().getName())) {
Log.i(TAG, "This is last activity in the stack");
}
Im loosing the full activity, well I can get the activity
#Override
public void onAttach(Activity activity) {
LocalNotification.registerReceiver(reloadReceiver, LocalNotification.RELOAD_CARTMG);
try {
mListener = (OnFragmentInteractionListener) activity;
MainActivity ma = (MainActivity)activity; //THIS IS LOOSE
ma.openMyList = false;
ma.openMyAddress = false;
ma.hideSearchButton();
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
}
super.onAttach(activity);
}
This is the Log inside the app
com.XXXX.mg E/Uncaught Exception detected in thread {}: Unable to start activity ComponentInfo{com.XXXX.mg/com.XXXX.view.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.XXXX.view.MainActivity.showShoppingCartIcon()' on a null object reference
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.XXXX.mg/com.XXXX.view.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.XXXX.view.MainActivity.showShoppingCartIcon()' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2426)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2490)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1354)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5443)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.XXXX.view.MainActivity.showShoppingCartIcon()' on a null object reference
at com.XXXX.view.shoppingCart.ShoppingCartFragment.onDetach(ShoppingCartFragment.java:411)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1202)
at android.support.v4.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1349)
at android.support.v4.app.BackStackRecord.popFromBackStack(BackStackRecord.java:915)
at android.support.v4.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1722)
at android.support.v4.app.FragmentManagerImpl$3.run(FragmentManager.java:593)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1617)
at android.support.v4.app.FragmentController.execPendingActions(FragmentController.java:339)
at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:602)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1260)
at android.app.Activity.performStart(Activity.java:6261)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2389)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2490)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1354)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5443)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
I'm not sure if your app crashes just because you loose your MainActivity, you should be coding expecting this kind of behaviour, using onSaveInstanceState like #pablobu suggested is the right approach.
Can we see the logs? If you're loosing the state of your instances that's fine, you should save those states and restore them Recreating an Activity is it because of Fragments? you can also save those too, Handling Fragments Tutorial this is a good read.
Please, can you give us your logcat output, that might help a lot for us to see whats really going on.
EDIT
With your Logcat edit, try to take a look to the fragment lifecycle, onAttach executes first than onActivityCreated(), so if your Activity is already destroyed, your onAttach method will not have your activity, you should wait until your Activity is recreated .
Furthermore, you have your
super.onAttach(activity);
at the end of your code block, put it before your code block.
/**
This method was deprecated in API level 23.
Use onAttach(Context) instead.
*/
#Override
public void onAttach(Context context) {
super.onAttach(context);
MainActivity mainActivity;
if (context instanceof Activity) {
mainActivity = (MainActivity) context;
try {
mListener = (OnFragmentInteractionListener) mainActivity;
/**
Since you are getting a reference and accesing attributes you should be careful
with NullPointerException, check if not null first.
Or better yet refactor a little your code using an interface to handle this behaviour or use
the one you already created and just tell the activity what to do.
*/
/*if(mainActivity != null) {
ma.openMyList = false;
ma.openMyAddress = false;
ma.hideSearchButton();
}*/
} catch (ClassCastException e) {
throw new ClassCastException(mainActivity.getClass().getSimpleName() + " must implement OnFragmentInteractionListener");
}
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//since your Listener is global use mListener behaviour that I suggested here.
if (mListener != null){
mListener.openListOfSomething(false);
mListener.hideSearch();
}
}
You can use the Bundle savedInstatceState, wich is given in OnCreate and similar methods:
private String aVariable;
#Override
public void onCreate (Bundle savedInstantceState) {
super.onCreate(savedInstanceState);
// Get the variable from savedInstanceState, if it isn't empty.
// If it is empty, it could be the first Activity run.
try {
aVariable = savedInstanceState.get("Key");
} catch (NullPointerException e) {
aVariable = "Baum";
}
}
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putString(aVariable, "Key");
}
These methods set the Variable "aVariable" to "Baum" on the first run
(NullPointerException, because there is no String to "Key").
Later, when the Activity is going to be destroyed, onSaveInstanceState will be called. There you save your variables (aVariable) under a Key ("Key").
I have an app on Google Play that tends to make the app crash if it is in the background when the app is updated and then re-opened by the user. The crash only happens sometimes, which is hinting towards a race condition somewhere. I am having a hard time debugging this and have only been able to reproduce this a few times myself, though I have got some stack traces reported by users. The stack traces are never pointing to the same line in the code, but the stack trace always enters my code through performResumeActivity() in Android and thus onResume() in my code. Note that this has never occurred to me or been reported by users in any other context than when updating the app (as far as I am able to tell). This does not mean that it only occurs when updating the app, but this is the only way I have seen the crash occur.
Steps to (sometimes) reproduce:
Install an old app version, e.g. by adb install app-release-previous-version.apk.
Open the app, play around a little bit and then minimize the app.
Open Google Play and update the app to the latest version.
Open the app from Google Play (we can open it from there after update).
The app crashes in performResumeActivity()/onResume() from time to time.
I'll add the code for the Activity where the crash occurs. Non-important code for this crash is not pasted, but let me know if I should add more code. The various stack traces I have are pasted at the end of the question.
MyActivity (important parts only)
public class MyActivity extends AppCompatActivity
{
private MyMainFragment myMainFragment = null;
private FilteredRecipes myFilteredRecipes = null;
private MyShoppingList myShoppingList = null;
private boolean mShouldReadAutomaticSave = false;
// <snip> other members
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mShouldReadAutomaticSave = savedInstanceState == null;
setContentView(R.layout.my_activity);
// Setup Toolbar / ActionBar.
Toolbar toolbar = (Toolbar) findViewById(R.id.my_main_toolbar);
setSupportActionBar(toolbar);
NavigationView navigationView = (NavigationView) findViewById(R.id.my_navigationview);
if (navigationView != null)
{
// <snip> Setup navigationView
}
DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.my_drawer_layout);
if (drawerLayout != null)
{
// <snip> Setup DrawerLayout
}
if (myShoppingList == null) { myShoppingList = new MyShoppingList(this); }
if (myFilteredRecipes == null) { myFilteredRecipes = new MyFilteredRecipes(this); }
if (myMainFragment == null) { myMainFragment = new MyMainFragment(); }
if (savedInstanceState == null)
{
// Runs a thread to read recipes from an SQLite database. This is not the culprit.
filterAllRecipes(null);
}
else
{
restoreState(savedInstanceState); // See method below
}
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.fade_in, R.anim.none)
.replace(R.id.my_content_frame, myMainFragment, "main_fragment")
.commit();
}
#Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
if (myFilteredRecipes != null)
{
myFilteredRecipes.onSaveInstanceState(outState); // Restores from Bundle
}
if (myShoppingList != null)
{
myShoppingList.onSaveInstanceState(outState); // Restores from Bundle
}
if (myMainFragment != null)
{
getSupportFragmentManager().putFragment(outState, "myFragment", myMainFragment); // Restores from Bundle
}
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
}
private void restoreState(Bundle savedInstanceState)
{
myShoppingList.onRestoreInstanceState(savedInstanceState);
myFilteredRecipes.onRestoreInstanceState(savedInstanceState);
myMainFragment = (MyMainFragment)
getSupportFragmentManager().getFragment(savedInstanceState, "myFragment");
}
#Override
public void onResume()
{
super.onResume();
// These tests were added as an attempt to cure the crash.
// Probably fixed some of the crashes (stack traces below), but not the entire issue.
if (myShoppingList == null) { myShoppingList = new MyShoppingList(this); }
if (myFilteredRecipes == null) { myFilteredRecipes = new MyFilteredRecipes(this); }
if (myMainFragment == null)
{
myMainFragment = new MyMainFragment();
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.fade_in, R.anim.none)
.replace(R.id.my_content_frame, myMainFragment, "main_fragment")
.commit();
}
if (mShouldReadAutomaticSave) // Set in onCreate() when applicable
{
mShouldReadAutomaticSave = false;
final MyActivity mThis = this;
Thread loadAutomaticSaveThread = new Thread()
{
#Override
public void run()
{
// Load automatic save if any. Must be done last to avoid race condition in LoadShoppingListTaks.
JSONObject automaticSave = ShoppingListFileHandler.readAutomaticSavedShoppingList(mThis);
if (automaticSave != null)
{
// This task takes the JSON data from the file read above, reads data from an SQLite database and sets up the myShoppingList data.
// Calls back to MyActivity.onShoppingListLoaded() when done.
// NOTE: There has been a crash in here when myShoppingList was null. This was before the "== null" tests above was added.
new ShoppingListFileHandler.LoadShoppingListTask(mThis, myShoppingList, automaticSave, false).execute();
}
}
};
loadAutomaticSaveThread.setName("LoadAutomaticSaveThread");
loadAutomaticSaveThread.start();
}
}
// This is called from the LoadShoppingListTask when done.
#Override
public synchronized void onShoppingListLoaded()
{
if (myShoppingList != null)
{
// MyShoppingList.notifyDataSetChanged will call notifyDataSetChanged on a
// RecyclerView adapter in the main Fragment. There is a stack trace for this below.
myShoppingList.notifyDataSetChanged();
}
}
}
So that's the Activity where the crashes occur. Here are the stack traces I have got, some of them are partly obfuscated, since I hadn't understood what it did at that time.
Stack trace for a missing Fragment
Notes: It seems that we get a Fragment that is null, i.e. it is probably myMainFragment that is null when trying to restart the Activity.
java.lang.RuntimeException: Unable to resume activity {com.my.app/com.my.app.activities.MyActivity}: java.lang.IllegalArgumentException: No view found for id 0x7f0f0089 (com.my.app/my_content_frame) for fragment a{215e6dc #0 id=0x7f0f0089 main_fragment}
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3403)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3434)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2772)
at android.app.ActivityThread.access$900(ActivityThread.java:177)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1449)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:5951)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1400)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1195)
Caused by: java.lang.IllegalArgumentException: No view found for id 0x7f0f0089 (com.my.app/my_content_frame) for fragment a{215e6dc #0 id=0x7f0f0089 main_fragment}
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1098)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1286)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:758)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1671)
at android.support.v4.app.FragmentController.execPendingActions(FragmentController.java:388)
at android.support.v4.app.FragmentActivity.onPostResume(FragmentActivity.java:512)
at android.support.v7.app.AppCompatActivity.onPostResume(AppCompatActivity.java:178)
at android.app.Activity.performResume(Activity.java:6430)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3392)
... 11 more
Stack trace for Shopping List == null
Notes: Crash in the LoadShoppingListTask since the myShoppingList member was null in the task. This happened before adding the tests in the start of onResume().
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:304)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
at java.util.concurrent.FutureTask.run(FutureTask.java:242)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String[] com.my.app.shoppinglist.j.a(android.content.Context, java.util.ArrayList, java.lang.String, android.util.SparseIntArray, org.json.JSONArray, java.util.ArrayList, boolean)' on a null object reference
at com.my.app.filehandling.ShoppingListFileHandler$LoadShoppingListTask.doInBackground(ShoppingListFileHandler.java:882)
at com.my.app.filehandling.ShoppingListFileHandler$LoadShoppingListTask.doInBackground(ShoppingListFileHandler.java:770)
at android.os.AsyncTask$2.call(AsyncTask.java:292)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
... 4 more
Stack trace from RecyclerView in the main Fragment
Notes: There are multiple of these in various places in the RecyclerView when binding data. This particular RecyclerView lives in the main Fragment in the Activity. These crashes are the only times I have had problems with it. The RecyclerView reads its data from the myShoppingList member in the Activity. It seems like the Views are not created yet.
java.lang.NullPointerException: Attempt to read from field 'android.widget.TextView com.my.app.gui.fragments.shoppinglist.k.a' on a null object reference
at com.my.app.gui.fragments.shoppinglist.ShoppingListAdapter.bindRecipeName(ShoppingListAdapter.java:401)
at com.my.app.gui.fragments.shoppinglist.ShoppingListAdapter.onBindViewHolder(ShoppingListAdapter.java:249)
at com.my.app.gui.fragments.shoppinglist.ShoppingListAdapter.onBindViewHolder(ShoppingListAdapter.java:31)
at com.my.app.gui.fragments.shoppinglist.ShoppingListAdapter.access$000(ShoppingListAdapter.java:31)
at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:5471)
at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:5504)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4741)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4617)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1994)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1390)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1353)
<snip> Long stack trace inside Android classes...
Stack trace with Fragment == null
Notes: This was the first stack trace I got. Unfortunately I had not realized that I had to add deobscufation files at this point, so the stack trace is not that useful. But it seems obvious that the member myMainFragment was null at the crash.
java.lang.RuntimeException: Unable to resume activity {com.my.app/com.my.app.activities.MyActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'int com.my.app.gui.fragments.c.a.L()' on a null object reference
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3403)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3434)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2772)
at android.app.ActivityThread.access$900(ActivityThread.java:177)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1449)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:5951)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1400)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1195)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int com.my.app.gui.fragments.c.a.L()' on a null object reference
at com.my.app.activities.MyActivity.t(Unknown Source)
at com.my.app.activities.MyActivity.onResume(Unknown Source)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1255)
at android.app.Activity.performResume(Activity.java:6412)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3392)
... 11 more
I cannot see where I have taken the wrong turn. Any hints and comments are appreciated.
I'm getting an NPE when I start an activity in my application. It doesn't happen right away when I boot my phone and start debugging on it. After several dozen new builds are pushed to my phone it eventually starts crashing with this error every single time. I can remedy it temporarily by opening a few other activities first before I activate the errant one.
Any ideas what could cause this? It's a somewhat complicated Activity with a DrawerLayout, and a fragment that contains a SwipeRefreshLayout with a ListView.
Exception
java.lang.RuntimeException: Unable to start activity ComponentInfo{}: java.lang.NullPointerException: Attempt to read from field 'boolean android.support.v4.app.BackStackRecord.mAddToBackStack' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
at android.app.ActivityThread.access$800(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1279)
Activity Code
public class TastingsActivity extends BaseActivity implements TastingListFragment.Callbacks {
public static final String TAG = TastingsActivity.class.getSimpleName();
private TastingListFragment mTastingListFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tastings);
//create fragment
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create a new Fragment to be placed in the activity layout
TastingListFragment fragment = new TastingListFragment();
// In case this activity was started with special instructions from an
// Intent, pass the Intent's extras to the fragment as arguments
fragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.container, fragment).commit();
}
}
#Override
protected int getSelfNavDrawerItem() {
return NAVDRAWER_ITEM_TASTINGS;
}
//================================================================================
// Handlers
//================================================================================
#Override
public void onTastingSelected(Tasting tasting) {
Intent intent = new Intent(this, TastingActivity.class);
intent.putExtra(TastingDetailsFragment.EXTRA_TASTING_ID, tasting.getId());
startActivity(intent);
}
}