1

While examining my Android app crash reports, I noticed that the java.lang.NullPointerException was one of top causes of crashes. This is an example of one of my frequent crashes:

    java.lang.RuntimeException:
      at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3364)
      at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:3548)
      at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:86)
      at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:108)
      at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:68)
      at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2155)
      at android.os.Handler.dispatchMessage (Handler.java:109)
      at android.os.Looper.loop (Looper.java:207)
      at android.app.ActivityThread.main (ActivityThread.java:7539)
      at java.lang.reflect.Method.invoke (Native Method)
      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:524)
      at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:958)
    Caused by: java.lang.NullPointerException:
      at com.myapp.ProductDetails.onCreate (ProductDetails.java:47)
      at android.app.Activity.performCreate (Activity.java:7441)
      at android.app.Activity.performCreate (Activity.java:7431)
      at android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1286)
      at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3343)

I went to examine what I had in com.myapp.ProductDetails.onCreate (ProductDetails.java:47) and it was this:

ProductDetail productDetail = MyApplication.getInstance().pDetail;

I know that line is causing the java.lang.NullPointerException and consequently the crash. The question is why? I am using Application, the base class for maintaining global application state explained at https://developer.android.com/reference/android/app/Application. I am subclassing Application:

import android.app.Application;
.....
public class MyApplication extends Application{
    static MyApplication _instance;
    public ProductDetail pDetail;
    .....
    @Override
    public void onCreate() {
        super.onCreate();
        _instance= this;
        .....
    }
    .....
    public static MyApplication getInstance(){
        return _instance;
    }
}

But when I use ProductDetail productDetail = MyApplication.getInstance().pDetail;, SOMETIMES I get the java.lang.NullPointerException. I am emphasizing that the line causes the java.lang.NullPointerException SOMETIMES, not all the time. My theory is that what is happening to my app is what is explained at https://github.com/codepath/android_guides/wiki/Understanding-the-Android-Application-Class:

However, you should never store mutable instance data inside the Application object because if you assume that your data will stay there, your application will inevitably crash at some point with a NullPointerException. The application object is not guaranteed to stay in memory forever, it will get killed. Contrary to popular belief, the app won’t be restarted from scratch. Android will create a new Application object and start the activity where the user was before to give the illusion that the application was never killed in the first place.

I tried logging using this:

ACRA.getErrorReporter().putCustomData("ACRA CUSTOM_DATA begins","ACRA CUSTOM_DATA begins");ACRA.getErrorReporter().putCustomData("MyApplication.getInstance()", MyApplication.getInstance().toString());ACRA.getErrorReporter().putCustomData("MyApplication.getInstance().pDetail", MyApplication.getInstance().pDetail.toString());ProductDetail productDetail = MyApplication.getInstance().pDetail;ACRA.getErrorReporter().putCustomData("ACRA CUSTOM_DATA ends","ACRA CUSTOM_DATA ends");

In the log results, I only see this:

CUSTOM_DATA=ACRA CUSTOM_DATA begins = ACRA CUSTOM_DATA begins
MyApplication.getInstance() = com.myapp.MyApplication@c190fea

The way I interpret it is that MyApplication.getInstance().toString() does not make the app to crash because even though the original MyApplication was lost (that is my assumption that somehow that object gets lost maybe if the process is killed at some point) because MyApplication.getInstance().toString() creates a new object, since the original was lost (my assumption). But when I try to execute MyApplication.getInstance().pDetail.toString(), then the app does crash because it had already lost the value of the member variable pDetail, since MyApplication would be holding a new object, not the original one that had a value for MyApplication.getInstance().pDetail properly assigned. So my interpretation is that even though I wanted MyApplication.getInstance() to always hold one and only one object and the same all the time until I explicitly close the app, maybe the MyApplication object gets lost and then using MyApplication.getInstance() creates a new object. Then the java.lang.NullPointerException would make sense. Two questions. 1. Does this explanation sound plausible to you? If not, why? 2. What would you recommend to do to rewrite my code so that I fix this java.lang.NullPointerException that I occasionally get? Interesting to note that I have seen this happen to me when I minimize the app for a long time, let's say hours or days and then reopen it. This would make sense because maybe the process is killed and when I bring the minimized app back to the foreground, MyApplication.getInstance() could be creating whole new process or object, not the original one that had been created and as a consequence, MyApplication.getInstance().pDetail would have already lost its value and then I get the java.lang.NullPointerException. Does this theory make sense?

UPDATE 1:

ProductDetail is a class that I have, which contains the line that causes the crash and the java.lang.NullPointerException:

public class ProductDetail extends FragmentActivity implements OnMapReadyCallback {
    .....
    static ProductDetail _instance;
    protected void onCreate(Bundle savedInstanceState) {
        .....
        ProductDetail productDetail = MyApplication.getInstance().pDetail; // This is the line that SOMETIMES causes the app to crash with a java.lang.NullPointerException.
        .....
    }
}
Jaime Montoya
  • 6,915
  • 14
  • 67
  • 103

2 Answers2

2

Well, yes your assumption that the application instance can be killed is right. Your error that happens sometimes is evidence of that. As this article says:

The OS may kill processes as necessary. All processes are divided into 5 levels of "killability" specified in the documentation.

So, for instance, if your app goes in the background due to the user answering to an incoming call, then depending on the state of the RAM, the OS may (or may not) kill your process (destroying the Application instance in the process).

Your application instance is not reliable as a storage class use-case. For instance, let's take this example:

  • Your app goes in the background because your user has gone to the main app drawer or your app is eating unnecessary memory or battery.
  • The documentation says that because your app is on the background your services, or sometimes the application state is "canceled".

That's why you don't see that many approaches to store data in the application instance. Most of the times, you would use SharedPreferences or you would write it to a local text, custom file. This article has way more information in it: Using the Android Application class to persist data

Community
  • 1
  • 1
Gaurav Mall
  • 2,372
  • 1
  • 17
  • 33
  • 2
    is there an order of when or where things will get killed off ? will application be the first or how does it work ? – a_local_nobody Aug 22 '19 at 19:13
  • Yes, there is. It's Android places each service with an importance level. Once something becomes unimportant, android kills it. Take a look at the docs: https://developer.android.com/guide/components/activities/process-lifecycle.html – Gaurav Mall Aug 23 '19 at 06:41
1

Great explanation about the root cause at http://www.developerphil.com/dont-store-data-in-the-application-object/. This is the solution according to that article:

There is no magic solution here, you can do one of the following:

  1. Explicitly pass the data to the Activity through the intent.
  2. Use one of the many ways to persist the data to disk.
  3. Always do a null-check and handle it manually.
Jaime Montoya
  • 6,915
  • 14
  • 67
  • 103