Search code examples
androidrx-javatimber-android

Flushing RxJava buffer on application close


Using Timber, I wrote a FileTree logger that will write Android logs to files on the disk. I buffer the logs, flushing to disk every 2 minutes, or 100 log messages, whichever comes first. The buffer prevents each log message from triggering a IO write, so as to not overload the IO resources by immediately writing every single log message.

I'm using RxJava to handle this task. A short snippet (taken from here):

logBuffer.observeOn(Schedulers.computation())
    .doOnEach((log) -> {
        processed++;

        if(processed % 100 == 0) {
            flush();
        }
    })
    .buffer(flush.mergeWith(Observable.interval(2, TimeUnit.MINUTES)))
    .subscribeOn(Schedulers.io())
    .subscribe((logs) -> {
        // Flush the logs to the file
        try {
            File logFile = new File(logDir, "app.log");

            FileWriter fw = new FileWriter(logFile, true);

            for(LogMessage msg : logs) {
                fw.append(msg.toString());
            }

            fw.flush();

            flushCompleted.onNext(logFile.length());
        } catch(Exception e) {
            Timber.e(e, "Failed to flush logs");
        }
    });        

I use the "flush" subject if I need to trigger a flush manually. I plant the FileTree in Timber within the Application's onCreate():

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        Timber.plant(new Timber.DebugTree(), new FileTree(getApplicationContext()));
    }
}

This is also where the subscriptions for RxJava are setup. I have two questions about this:

  • If the application closes for some reason, presumably the log flush won't get triggered by a timer or by receiving enough log messages. Should I simply place a manual call to flush in every Activity's onDestroy() function? Is there a better way to do this?
  • Secondly, RxJava complains that I ignore the result of ".subscribe()", presumably because I won't ever call dispose() on it. I'm not sure exactly how to handle this case. The subscriptions have the same lifecycle as the Application itself, so I'm not sure that removing it within a particular Activity's onDestroy would make sense. Further, my app has several entry points (the main activity, a background service, and a few others) that all make use of the logging facilities, so when to unsubscribe/dispose the RxJava subscriptions isn't clear. My intuition is that I don't need to dispose them because they will be cleared whenever the Application is removed by the operating system. Any ideas?

Solution

    1. Instad of copying that code on each activity finish, you could use a BaseActivity, or use Application's activity state callbacks API ( https://developer.android.com/reference/android/app/Application#registerActivityLifecycleCallbacks(android.app.Application.ActivityLifecycleCallbacks) ).

    I should note that for the purposes of chosing a lifecycle method to flush, no method after onPause() is guaranteed to be called by the OS. If the operating system needs to kill your app for low-memory, it's not guaranteed that onDestroy() will be called ( rather it will just kill your process ).

    To quote https://developer.android.com/reference/android/app/Activity :

    Note the "Killable" column in the above table -- for those methods that are marked as being killable, after that method returns the process hosting the activity may be killed by the system at any time without another line of its code being executed. Because of this, you should use the onPause() method to write any persistent data (such as user edits) to storage



    1. If the 'lifecycle' of your code is the same as the application's, its perfectly OK to ignore the subscription result ( as you won't ever dispose of the subscription ). Just make sure that you aren't catching in that scope parts that need to have a shorter lifecycle ( for example don't keep a reference to an activity context there, or you'll be leaking it )