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!
/* fini */