public static void cleanIfNeeded(final Context context) {
        withAccessLock(() -> {

            if (databaseCleaned) {
                return;
            }
            databaseCleaned = true;

            try (ContextLogger ignore = new ContextLogger(true, "DataStore.cleanIfNeeded: cleans DB")) {
                Schedulers.io().scheduleDirect(() -> {
                    // check for UDC cleanup every time this method is called
                    deleteOrphanedUDC();

                    // reindex if needed
                    if (Settings.dbNeedsReindex()) {
                        Settings.setDbReindexLastCheck(false);
                        reindexDatabase();
                    }

                    // other cleanup will be done once a day at max
                    if (Settings.dbNeedsCleanup()) {
                        Settings.setDbCleanupLastCheck(false);

                        Log.d("Database clean: started");
                        try {
                            final Set<String> geocodes = new HashSet<>();
                            final String timestampString = Long.toString(System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED);
                            queryToColl(dbTableCaches,
                                    new String[]{"geocode"},
                                    "detailedupdate < ? AND visiteddate < ? AND geocode NOT IN (SELECT DISTINCT (geocode) FROM " + dbTableCachesLists + ")",
                                    new String[]{timestampString, timestampString},
                                    null,
                                    null,
                                    geocodes,
                                    GET_STRING_0);

                            final Set<String> withoutOfflineLogs = exceptCachesWithOfflineLog(geocodes);
                            Log.d("Database clean: removing " + withoutOfflineLogs.size() + " geocaches");
                            removeCaches(withoutOfflineLogs, LoadFlags.REMOVE_ALL);

                            deleteOrphanedRecords();
                            makeWaypointPrefixesUnique();

                            // Remove the obsolete "_others" directory where the user avatar used to be stored.
                            FileUtils.deleteDirectory(LocalStorage.getGeocacheDataDirectory("_others"));

                            final int version = Version.getVersionCode(context);
                            if (version > -1) {
                                Settings.setVersion(version);
                            }
                        } catch (final Exception e) {
                            Log.w("DataStore.clean", e);
                        }
                        Log.d("Database clean: finished");
                    }
                });
            }
        });
    }
Remove caches which are not on any list in the background. Once it has been executed once it will not do anything.
This must be called from the UI thread to ensure synchronization of an internal variable.
main/src/main/java/cgeo/geocaching/storage/DataStore.java