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

Evolution of Android Background tools

Evolution of Android Background tools

Avatar for Vladimir Ivanov

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