A Journey Towards Android App Log Subscriptions
I’ve got an Android project going that uses FFmpeg to build short video files (more on this at a later date). Normally, you run FFmpeg in a terminal window or command prompt. It updates you on the status of a task in that terminal window. I’ve instrumented FFmpeg on Android with some C code and a JNI interface. When I execute FFmpeg from an Android app those status updates are written to logcat.
There are some important details that I was to surface in my app, primarily the progress of an encoding process. In order to do this, I needed a way to read my Android apps logging statements. This is possible on Android and without needing to request the scary READ_LOGS Android permission. Note: All of my examples are written in Kotlin because I ❤ Kotlin. You can execute a process with the Runtime API:
val process = Runtime.getRuntime().exec("some\_command\_here")
You can then get an InputStream to the Process’s output:
val process = Runtime.getRuntime().exec("some\_command\_here")
val inputStream = process.inputStream
Putting it all together, this is how one would get your apps logging statements from logcat, within the scope of your app:
val process = Runtime.getRuntime().exec("logcat")
reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?
do {
line = reader.readLine()
if (line != null) {
// do something with this logging statement
}
} while (line != null)
reader.close()
This code essentially runs forever because, as far as I’ve seen, reader doesn’t return a null line. I’m close, but I still needed to add some sophistication. I needed to read and parse logs while FFmpeg was running so I can update the user on the status of their job. I needed to start parsing logs when I wanted to update the user while FFmpeg was running and end parsing when FFmpeg was complete. This problem is perfectly solved using RxJava:
fun readLogs(): Observable =
return Observable.create ({ emitter ->
var reader: BufferedReader? = null
try {
val process = Runtime.getRuntime().exec("logcat")
reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?
do {
line = reader.readLine()
if (line.isNotBlank()) {
emitter.onNext(line)
}
} while (line != null)
emitter.onComplete()
} catch (e: Exception) {
emitter.onError(e)
} finally {
reader?.close()
}
})
With RxJava, I can emit a logging statement as it’s received by logcat. Usage:
val disposable = readLogs()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ logLine ->
logTextView.setText(logLine)
}, { error -> throw error })
// later, when you no longer need to subscribe to your logs
disposable.dispose()
Because it’s all based on RxJava, you can do more advanced things like filtering
val disposable = readLogs()
.filter({ line -> line.startsWith("foo") })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ logLine ->
logTextView.setText(logLine)
}, { error -> throw error })
This allows you to “subscribe” to your apps logs!
🧇