2

My Android app uses JobService to perform my App Widget Refresh.

Today I received a single crash report of:-

Fatal Exception: java.lang.OutOfMemoryError: Could not allocate JNI Env
       at java.lang.Thread.nativeCreate(Thread.java)
       at java.lang.Thread.start(Thread.java:731)
       at com.research.app_widget.WidgetUpdateJobService.onStartJob(SourceFile:29)
       at android.app.job.JobService$JobHandler.handleMessage(JobService.java:143)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6776)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)

From the stack trace of this crash I have created over 500 instances of my HandlerThread

My onStartJob method resembles this:-

@Override
public boolean onStartJob(final JobParameters params) {

   mServerHandlerThread = new HandlerThread("ServerApplication");
   mServerHandlerThread.start();

    mServerHandler = new Handler(mServerHandlerThread.getLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(final Message msg) {
            final int what = msg.what;

            switch (what) {
                case WHAT_WIDGET_REFRESH:
                    refreshWidget(params);
                    break;
                default:
                    Log.e(TAG, "handleMessage: Unexpected WHAT = " + what);
            }

            return true;
        }
    });

    mServerHandler.sendEmptyMessage(WHAT_WIDGET_REFRESH);

    return true;
}

The refreshWidget(params) method is as follows:-

private void refreshWidget(final JobParameters params) {
    final PersistableBundle persistableBundle = params.getExtras();
    final int[] appWidgetIds = persistableBundle.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);

    if (appWidgetIds == null) {
        Log.e(TAG, "refreshWidget: appWidgetIds array is null");
    } else {
        for (int appWidgetId : appWidgetIds) {
            new WidgetRemoteViewsHelper().configureViews(this.getApplicationContext(), appWidgetId, isListEmpty(persistableBundle));
        }
    }

    jobFinished(params, false);

}

What I don't understand is that theres an Android limit of starting 100 scheduledJobs, so how have I managed to start over 500 HandlerThreads?

What I would like to know is, is the following an acceptable solution? e.g. reusing a single HandlerThread Looper instance?

private final HandlerThread mServerHandlerThread;

{
    mServerHandlerThread = new HandlerThread("ServerApplication");
    mServerHandlerThread.start();

}

@Override
public boolean onStartJob(final JobParameters params) {

    mServerHandler = new Handler(mServerHandlerThread.getLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(final Message msg) {
            final int what = msg.what;

            switch (what) {
                case WHAT_WIDGET_REFRESH:
                    refreshWidget(params);
                    break;
                default:
                    Log.e(TAG, "handleMessage: Unexpected WHAT = " + what);
            }

            return true;
        }
    });

    mServerHandler.sendEmptyMessage(WHAT_WIDGET_REFRESH);

    return true;
}

From my testing so far this approach seems to work as required. I just felt uneasy about reusing a single HandlerThread.

Hector
  • 4,016
  • 21
  • 112
  • 211
  • 2
    Why are you using a `HandlerThread` in the first place? Why not use an ordinary `Thread`? Where are you calling `quit()` or `quitSafely()` on the `HandlerThread` to shut it down? – CommonsWare Jan 08 '18 at 17:05
  • What's wrong with using handlerthread? If I switch to a normal thread I'm guessing I'll still have the same issue. I call quit slafely after my call to finish job, it's missing above as I lost it while copying and pasting my code – Hector Jan 08 '18 at 17:33
  • 1
    "What's wrong with using handlerthread?" -- there is no particular advantage to using it. "If I switch to a normal thread I'm guessing I'll still have the same issue" -- you shouldn't, as once your `run()` method exits, the thread terminates. If you have 500 running instances of `HandlerThread`, it feels as though you are not successfully calling `quit()` or `quitSafely()`, which you would not need to do when using `Thread`. – CommonsWare Jan 08 '18 at 17:55
  • @CommonsWare thanks for looking at my question. I've taken your advice and switched to a "normal" thread to offload my work. – Hector Jan 09 '18 at 09:17
  • @CommonsWare I have the same error too. And I am not sure if I am closing handler threads safely. I use it for database operations. How should I switch to normal thread? Would they work on database operations? I knew that I need to use handler thread for realm operations. Thanks. – Hilal Dec 19 '19 at 15:59
  • 1
    @Hilal: "How should I switch to normal thread?" -- I cannot really answer that, as I do not know your code. "Would they work on database operations?" -- yes. However, the use of plain threads and `HandlerThread` is rather old-fashioned. It is more popular nowadays to use reactive frameworks (`LiveData`, RxJava, or Kotlin coroutines) with a compatible database access layer (e.g., Room, SQLDelight). – CommonsWare Dec 19 '19 at 22:09
  • @CommonsWare Could you please provide me some sample code for that? Currently I am using realm for that but I do not know how to do them on background without using handler thread. Thanks – Hilal Dec 20 '19 at 14:46
  • @CommonsWare you may review my question about it. => https://stackoverflow.com/q/59416059/5528870 Thanks. – Hilal Dec 20 '19 at 14:46

0 Answers0