<NME> StatusUpdater.java
<BEF> package cgeo.geocaching.network;

import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.utils.MemorySubject;
import cgeo.geocaching.utils.PeriodicHandler;
import cgeo.geocaching.utils.PeriodicHandler.PeriodicHandlerListener;
import cgeo.geocaching.utils.Version;

import org.json.JSONException;
import org.json.JSONObject;

import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Looper;

import java.util.Locale;

public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implements Runnable, PeriodicHandlerListener {

    static public class Status {
        final public String message;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
import org.apache.commons.lang3.StringUtils;

public class StatusUpdater {

    /**
     * An observable with the successive status. Contains {@link Status#NO_STATUS} if there is no status to display.
     */
            this.url = url;
        }

        final static public Status closeoutStatus =
                new Status("", "status_closeout_warning", "attribute_abandonedbuilding", "http://faq.cgeo.org/#7_69");

        final static public Status defaultStatus() {
            return VERSION.SDK_INT < VERSION_CODES.ECLAIR_MR1 ? closeoutStatus : null;
        }
    }

    @Override
    public void onPeriodic() {
        final JSONObject response =
                Network.requestJSON("http://status.cgeo.org/api/status.json",
                        new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())),
                                "version_name", Version.getVersionName(CgeoApplication.getInstance()),
                                "locale", Locale.getDefault().toString()));
        if (response != null) {
            notifyObservers(new Status(get(response, "message"), get(response, "message_id"), get(response, "icon"), get(response, "url")));
        }
    }

    private static String get(final JSONObject json, final String key) {
                    Log.d("Still running version from " + versionYear + " (version: " + version + ", system year: " + currentYear + ")");
                    return VERSION_DEPRECATED_STATUS;
                }
            } catch (NumberFormatException e) {
        }
    }

    @Override
    public void run() {
        Looper.prepare();
        new PeriodicHandler(1800000L, this).start();
        Looper.loop();
    }

}
    }

    private static String getActiveConnectorsString() {
        final List<IConnector> activeConnectors = ConnectorFactory.getActiveConnectors();
        final List<String> activeConnectorsList = new ArrayList<>();
        for (final IConnector conn : activeConnectors) {
            try {
                activeConnectorsList.add(conn.getNameAbbreviated());
            } catch (IllegalStateException ignored) {
            }
        }
        return TextUtils.join(",", activeConnectorsList);
    }

    static {
        AndroidRxUtils.networkScheduler.schedulePeriodicallyDirect(() -> {
            final Application app = CgeoApplication.getInstance();
            final String installer = Version.getPackageInstaller(app);
            final Parameters installerParameters = StringUtils.isNotBlank(installer) ? new Parameters("installer", installer) : null;
            final Parameters gcMembershipParameters;
            if (Settings.isGCConnectorActive()) {
                gcMembershipParameters = new Parameters("gc_membership",
                        Settings.getGCMemberStatus().isPremium() ? "premium" : "basic");
            } else {
                gcMembershipParameters = null;
            }
            Network.requestJSON("https://status.cgeo.org/api/status.json",
                    Parameters.merge(new Parameters("version_code", String.valueOf(Version.getVersionCode(app)),
                                    "version_name", Version.getVersionName(app),
                                    "locale", Locale.getDefault().toString()), installerParameters, gcMembershipParameters,
                            new Parameters("active_connectors", getActiveConnectorsString())))
                    .subscribe(json -> LATEST_STATUS.onNext(Status.defaultStatus(new Status(json))), throwable -> {
                        // Error has already been signaled during the request
                    });
        }, 0, 1800, TimeUnit.SECONDS);
    }

}

<MSG> Use RxJava for status updates rather than own observers

<DFF> @@ -1,21 +1,22 @@
 package cgeo.geocaching.network;
 
 import cgeo.geocaching.CgeoApplication;
-import cgeo.geocaching.utils.MemorySubject;
-import cgeo.geocaching.utils.PeriodicHandler;
-import cgeo.geocaching.utils.PeriodicHandler.PeriodicHandlerListener;
 import cgeo.geocaching.utils.Version;
 
 import org.json.JSONException;
 import org.json.JSONObject;
+import rx.Observable;
+import rx.concurrency.Schedulers;
+import rx.subjects.BehaviorSubject;
+import rx.util.functions.Func1;
 
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
-import android.os.Looper;
 
 import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
-public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implements Runnable, PeriodicHandlerListener {
+public class StatusUpdater {
 
     static public class Status {
         final public String message;
@@ -30,24 +31,41 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement
             this.url = url;
         }
 
+        Status(final JSONObject response) {
+            message = get(response, "message");
+            messageId = get(response, "message_id");
+            icon = get(response, "icon");
+            url = get(response, "url");
+        }
+
         final static public Status closeoutStatus =
                 new Status("", "status_closeout_warning", "attribute_abandonedbuilding", "http://faq.cgeo.org/#7_69");
 
-        final static public Status defaultStatus() {
+        final static public Status defaultStatus(final Status upToDate) {
+            if (upToDate != null && upToDate.message != null) {
+                return upToDate;
+            }
             return VERSION.SDK_INT < VERSION_CODES.ECLAIR_MR1 ? closeoutStatus : null;
         }
     }
 
-    @Override
-    public void onPeriodic() {
-        final JSONObject response =
-                Network.requestJSON("http://status.cgeo.org/api/status.json",
-                        new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())),
-                                "version_name", Version.getVersionName(CgeoApplication.getInstance()),
-                                "locale", Locale.getDefault().toString()));
-        if (response != null) {
-            notifyObservers(new Status(get(response, "message"), get(response, "message_id"), get(response, "icon"), get(response, "url")));
-        }
+    final static private Observable<Status> statusObservable =
+            Observable.interval(30, TimeUnit.MINUTES).startWith(-1L).flatMap(new Func1<Long, Observable<Status>>() {
+                @Override
+                public Observable<Status> call(Long id) {
+                    final JSONObject response =
+                            Network.requestJSON("http://status.cgeo.org/api/status.json",
+                                    new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())),
+                                            "version_name", Version.getVersionName(CgeoApplication.getInstance()),
+                                            "locale", Locale.getDefault().toString()));
+                    return response != null ? Observable.just(Status.defaultStatus((new Status(response)))) : Observable.<Status>empty();
+                }
+            }).subscribeOn(Schedulers.threadPoolForIO());
+
+    final static public BehaviorSubject<Status> latestStatus = BehaviorSubject.create(Status.defaultStatus(null));
+
+    static {
+        statusObservable.subscribe(latestStatus);
     }
 
     private static String get(final JSONObject json, final String key) {
@@ -58,11 +76,4 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement
         }
     }
 
-    @Override
-    public void run() {
-        Looper.prepare();
-        new PeriodicHandler(1800000L, this).start();
-        Looper.loop();
-    }
-
 }

Use RxJava for status updates rather than own observers