Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Evolution of Android Background tools

Evolution of Android Background tools

Vladimir Ivanov

May 18, 2018
Tweet

More Decks by Vladimir Ivanov

Other Decks in Programming

Transcript

  1. 1

  2. About me — Vladimir Ivanov - Lead Software Engineer in

    EPAM — Android apps: > 15 published, > 7 years 4
  3. About me — Vladimir Ivanov - Lead Software Engineer in

    EPAM — Android apps: > 15 published, > 7 years — Wide expertise in Mobile 4
  4. Why do we need background? — 60 FPS — UI

    updates only on the main thread 8
  5. public class LoadWeatherForecastTask extends AsyncTask<String, Integer, Forecast> { public Forecast

    doInBackground(String... params) { HttpClient client = new HttpClient(); HttpGet request = new HttpRequest(params[0]); HttpResponse response = client.execute(request); return parse(response); } } 10
  6. public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); ... private

    static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; 14
  7. public class LoadWeatherForecastTask extends AsyncTask<String, Integer, Forecast> { public void

    onPostExecute(Forecast forecast) { mTemperatureView.setText(forecast.getTemp()); } } 16
  8. Minuses — Boilerplate — Lifecycle not-aware — No result reusage

    across configuration changes — Easy way to introduce memory leaks 18
  9. inner class WeatherForecastLoaderCallbacks : LoaderManager.LoaderCallbacks<WeatherForecast> { override fun onCreateLoader(id: Int,

    args: Bundle?): Loader<WeatherForecast> { return WeatherForecastLoader(applicationContext) } } 21
  10. inner class WeatherForecastLoaderCallbacks : LoaderManager.LoaderCallbacks<WeatherForecast> { override fun onCreateLoader(id: Int,

    args: Bundle?): Loader<WeatherForecast> { return WeatherForecastLoader(applicationContext) } override fun onLoadFinished(loader: Loader<WeatherForecast>?, data: WeatherForecast) { temperatureTextView.text = data.temp.toString(); } } 22
  11. class WeatherForecastLoader(context: Context) : AsyncTaskLoader<WeatherForecast>(context) { override fun loadInBackground(): WeatherForecast

    { try { Thread.sleep(5000) } catch (e: InterruptedException) { return WeatherForecast("", 0F, "") } return WeatherForecast("Saint-Petersburg", 20F, "Sunny") } } 23
  12. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... val weatherForecastLoader =

    WeatherForecastLoaderCallbacks() loaderManager .initLoader(forecastLoaderId, Bundle(), weatherForecastLoader) } 24
  13. 26

  14. How does the loader gets reused? — LoaderManager saves all

    loaders internally — Activity saves all LoaderManagers inside NonConfigurationInstances 27
  15. How does the loader gets reused? — LoaderManager saves all

    loaders internally — Activity saves all LoaderManagers inside NonConfigurationInstances — On activity recreation NonConfigurationInstances got passed to a new activity 27
  16. Minuses — Still a lot of boilerplate — Complex interfaces

    and abstract classes — Loaders are Android API - you can’t create libs in pure Java 28
  17. public class Background { private val mService = ScheduledThreadPoolExecutor(5) fun

    execute(runnable: Runnable): Future<*> { return mService.submit(runnable) } fun <T> submit(runnable: Callable<T>): Future<T> { return mService.submit(runnable) } } 30
  18. public class Background { … private lateinit var mUiHandler: Handler

    public fun postOnUiThread(runnable: Runnable) { mUiHandler.post(runnable) } } 31
  19. 34

  20. public class Background { private lateinit val mEventBus: Bus fun

    postEvent(event: Any) { mEventBus.post(event) } } 36
  21. public class SplashActivity : Activity() { @Subscribe fun on(event: DatabaseLoadedEvent)

    { progressBar.setVisibility(View.GONE) showMainActivity() } override fun onStart() { super.onStart() eventBus.register(this) } override fun onStop() { eventBus.unregister(this) super.onStop() } } 38
  22. Minuses — The posting code knows which thread is needed

    on the listeners code — The actual projects begin to be unmaintanable with EventBus 42
  23. interface ApiClientRx { fun login(auth: Authorization) : Single<GithubUser> fun getRepositories

    (reposUrl: String, auth: Authorization) : Single<List<GithubRepository>> } 44
  24. private fun attemptLoginRx() { showProgress(true) apiClient.login(auth) .flatMap { user ->

    apiClient.getRepositories(user.repos_url, auth) } .map { list -> list.map { it.full_name } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doFinally { showProgress(false) } .subscribe( { list -> showRepositories(this@LoginActivity, list) }, { error -> Log.e("TAG", "Failed to show repos", error) } ) } 45
  25. private fun attemptLoginRx() { showProgress(true) apiClient.login(auth) .flatMap { user ->

    apiClient.getRepositories(user.repos_url, auth) } .map { list -> list.map { it.full_name } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doFinally { showProgress(false) } .subscribe( { list -> showRepositories(this@LoginActivity, list) }, { error -> Log.e("TAG", "Failed to show repos", error) } ) } 46
  26. private fun attemptLoginRx() { showProgress(true) apiClient.login(auth) .flatMap { user ->

    apiClient.getRepositories(user.repos_url, auth) } .map { list -> list.map { it.full_name } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doFinally { showProgress(false) } .subscribe( { list -> showRepositories(this@LoginActivity, list) }, { error -> Log.e("TAG", "Failed to show repos", error) } ) } 47
  27. private fun attemptLoginRx() { showProgress(true) apiClient.login(auth) .flatMap { user ->

    apiClient.getRepositories(user.repos_url, auth) } .map { list -> list.map { it.full_name } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doFinally { showProgress(false) } .subscribe( { list -> showRepositories(this@LoginActivity, list) }, { error -> Log.e("TAG", "Failed to show repos", error) } ) } 48
  28. private fun attemptLoginRx() { showProgress(true) apiClient.login(auth) .flatMap { user ->

    apiClient.getRepositories(user.repos_url, auth) } .map { list -> list.map { it.full_name } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doFinally { showProgress(false) } .subscribe( { list -> showRepositories(this@LoginActivity, list) }, { error -> Log.e("TAG", "Failed to show repos", error) } ) } 49
  29. private fun attemptLoginRx() { showProgress(true) apiClient.login(auth) .flatMap { user ->

    apiClient.getRepositories(user.repos_url, auth) } .map { list -> list.map { it.full_name } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doFinally { showProgress(false) } .subscribe( { list -> showRepositories(this@LoginActivity, list) }, { error -> Log.e("TAG", "Failed to show repos", error) } ) } 51
  30. Pluses — Standard de-facto, 65% of new projects will use

    RxJava — Powerful API, lots of operators 52
  31. Pluses — Standard de-facto, 65% of new projects will use

    RxJava — Powerful API, lots of operators — Opensource and community driven library 52
  32. Pluses — Standard de-facto, 65% of new projects will use

    RxJava — Powerful API, lots of operators — Opensource and community driven library — Adapters for popular libraries like Retrofit 52
  33. Pluses — Standard de-facto, 65% of new projects will use

    RxJava — Powerful API, lots of operators — Opensource and community driven library — Adapters for popular libraries like Retrofit — Relatively easy to provide unit-tests 52
  34. Minuses — Touch learning curve — Operators to learn —

    Callback style — Memory overhead 53
  35. Minuses — Touch learning curve — Operators to learn —

    Callback style — Memory overhead — Unrelated stacktraces 53
  36. private fun attemptLogin() { launch(UI) { val auth = BasicAuthorization(login,

    pass) try { showProgress(true) val userInfo = login(auth).await() val repoUrl = userInfo.repos_url val list = getRepositories(repoUrl, auth).await() showRepositories( this@LoginActivity, list.map { it -> it.full_name } ) } catch (e: RuntimeException) { Toast.makeText(this@LoginActivity, e.message, LENGTH_LONG).show() } finally { showProgress(false) } } } 56
  37. private fun attemptLogin() { … launch(UI) { val auth =

    BasicAuthorization(login, pass) try { showProgress(true) val userInfo = login(auth).await() val repoUrl = userInfo.repos_url val list = getRepositories(repoUrl, auth).await() showRepositories(this@LoginActivity, list.map { it -> it.full_name }) } catch (e: RuntimeException) { Toast.makeText(this@LoginActivity, e.message, LENGTH_LONG).show() } finally { showProgress(false) } } } 57
  38. 59

  39. Pluses — Non-blocking approach — Async code, sync styled —

    Language tools instead of operators — Learning curve is almost straight-line 60
  40. Pluses — Non-blocking approach — Async code, sync styled —

    Language tools instead of operators — Learning curve is almost straight-line — Unit-tests are easy 60
  41. Minuses — Fresh thing, status: experimental — Library adapters are

    not ready(retrofit?) — Not a replacement for Rx 61
  42. Summary — Android went long path — The modern tools

    are RxJava and Coroutines — But legacy is still there, hence the talk 62