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

Improving UX through performance Droidcon Italy

Improving UX through performance Droidcon Italy

Droidcon Italy 2015

rejasupotaro

April 10, 2015
Tweet

More Decks by rejasupotaro

Other Decks in Technology

Transcript

  1. … Connection speed in Indonesia is 5x slower than in

    Japan http://en.wikipedia.org/wiki/List_of_countries_by_Internet_connection_speeds
  2. Performance is a Feature It is becoming increasingly important for

    mobile engineers to guarantee stable service under any environment
  3. http { ... gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied

    any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_types text/plain text/css application/json } nginx.conf Nginx Rails
  4. HTTP Client GZIP decoder // Set "Accept-Encoding: gzip" when you

    send a request connection.setRequestProperty( "Accept-Encoding", “gzip"); // Decompress input stream when you receive a response inputStream = new GZIPInputStream( connection.getInputStream());
  5. • AndroidHttpClient • HttpUrlConnection • OkHttp HTTP clients for Android

    Don’t support GZIP by default support GZIP by default
  6. Volley has 2 HTTP clients internally public static RequestQueue newRequestQueue(…)

    { ... if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { // use HttpUrlConnection stack = new HurlStack(); } else { // use AndroidHttpClient stack = new HttpClientStack(AndroidHttpClie } } 2.3+: HttpUrlConnection <2.2: AndroidHttpClient Volley
  7. OkHttp + RxJava = Reactive Data Store View Adapter Service

    API Client Server OkHttp RxJava SQLite Database SharedPreferences
  8. Caching in HTTP cache-request-directive = "no-cache" | "no-store" | "max-age"

    "=" delta-seconds | "max-stale" [ "=" delta-seconds ] | "min-fresh" "=" delta-seconds | "no-transform" | "only-if-cached" | cache-extension cache-response-directive = "public" | "private" [ "=" <"> 1#field-name <"> ] | "no-cache" [ "=" <"> 1#field-name <"> ] | "no-store" | "no-transform" | "must-revalidate" | "proxy-revalidate" | "max-age" "=" delta-seconds | "s-maxage" "=" delta-seconds | cache-extension
  9. OkHttpClient client = new OkHttpClient(); Cache cache = new Cache(cacheDir,

    MAX_CACHE_SIZE); client.setCache(cache); Enable cache
  10. # default # => Cache-Control: max-age=0, private, must-revalidate expires_in(1.hour, public:

    true) # => Cache-Control: max-age=3600, public expires_now # => Cache-Control: no-cache Rails
  11. In some situations, such as after a user clicks a

    'refresh' button, it may be necessary to skip the cache, and fetch data directly from the server Cache-Control: no-cache
  12. // RecipeService.java public Observable<Response<Recipe>> get(…) { ... return request(GET, “/recipes/:id”)

    .noCache() .noStore() .to(RECIPE); } // ApiClient.java if (isConnected) { headers.put(CACHE_CONTROL, “only-if-cached"); } else if (noCache && noStore) { headers.put(CACHE_CONTROL, "no-cache, no-store"); } else if (noCache) { headers.put(CACHE_CONTROL, "no-cache"); } else if (noStore) { headers.put(CACHE_CONTROL, "no-store"); } HttpRequestCreator RecipeService ApiClient
  13. Object Type Duration Categories 1 day Search recipes 3 hours

    Users Do not cache To enjoy the benefits of caching, you need to write carefully crafted cache control policies
  14. Image size is much larger than JSON response {"result":{"id":1,"title":"Penne with

    Spring Vegetables”,”description”:”..." Each pixel takes up 4 bytes
  15. • Specify URL to HTTP client • Get Input Steam

    • Decode Input Stream to Bitmap • Set Bitmap to ImageView Simple Image Loading
  16. In addition, we want to • reuse worker threads •

    set the priority of requests • cache decoded images
  17. Main Thread Worker Thread Request Image CloudFront • Transform •

    Decode • Cache Worker Thread Worker Thread
  18. new ThreadPoolExecutor( corePoolSize, // The number of threads to keep

    in the pool maximumPoolSize, // The maximum number of threads to allow in the pool keepAliveTime, // the maximum time that excess idle threads will wait for new tasks timeUnit, // for the keepAliveTime argument workQueue, // the queue to use for holding tasks before they are executed threadFactory // The factory to use when the executor creates a new thread ); Task Result
  19. Producer-consumer pattern Send a request from main thread Control order

    of requests Receive a request through channel. Send result through Hander.
  20. There is a trade-off between capacity and resource If there

    are many workers, tasks are processed concurrently. If there are too many workers, consume memory wastefully.
  21. switch (info.getType()) { case ConnectivityManager.TYPE_WIFI: case ConnectivityManager.TYPE_WIMAX: case ConnectivityManager.TYPE_ETHERNET: setThreadCount(4);

    break; case ConnectivityManager.TYPE_MOBILE: switch (info.getSubtype()) { case TelephonyManager.NETWORK_TYPE_LTE: // 4G case TelephonyManager.NETWORK_TYPE_HSPAP: case TelephonyManager.NETWORK_TYPE_EHRPD: setThreadCount(3); break; case TelephonyManager.NETWORK_TYPE_UMTS: // 3G case TelephonyManager.NETWORK_TYPE_CDMA: case TelephonyManager.NETWORK_TYPE_EVDO_0: case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_EVDO_B: setThreadCount(2); break; case TelephonyManager.NETWORK_TYPE_GPRS: // 2G case TelephonyManager.NETWORK_TYPE_EDGE: setThreadCount(1); break; Runtime.getRuntime().availableProcessors() Picasso Glide
  22. Which setting is better? It is depending on network environment,

    device spec, image size, transformation, …
  23. NUM_IO_BOUND_THREADS = 2; NUM_CPU_BOUND_THREADS = Runtime.getRuntime().availableProcessors(); Process Kind of Executor

    forLocalStorageRead IoBoundExecutor forLocalStorageWrite IoBoundExecutor forDecode CpuBoundExecutor forBackground CpuBoundExecutor Fresco has multiple Executors
  24. We can set priority to request Picasso.with(this) .load(url) .priority(HIGH) .into(imageView);

    Glide.with(this) .load(url) .priority(HIGH) .into(imageView);
  25. WebP lossless images are 26% smaller in size compared to

    PNGs WebP lossy images are 25-34% smaller in size compared to JPEGs
  26. Comparison of image size jpeg webp (q = 90) webp

    (q = 70) webp (q = 50) webp (q = 80) webp (q = 60) 74% 90,602 bytes 30,214 bytes 23,550 bytes 20,882 bytes 18,344 bytes 51,288 bytes
  27. Tofu Decoder S3 https://... 101001010101… CloudFront (Transformation) (Cache) • Fixed

    Width: (\d+) • Fixed Height: x(\d+) • Fixed Width and Height: (\d+)x(\d+) • Smaller than: (\d+)?(x\d+)?s • Cropping: (\d+)x(\d+)c • Manual Cropping: (\d+)x(\d+)c(\d+)_(\d+)_(\d+)_(\d+)_(\d+) • Quality Factor: [geometry]q(\d+) • … Tofu has these functions
  28. EXCELLENT: (1080 * 756) * 1.0 LOW: (756 * 530)

    * 0.7 86KB 49KB LOW images are 40% smaller than full images
  29. … 10,000 ms 200 ms or below Distance between phone

    and server is very very very … long Particularly in emerging markets
  30. GZIP Cache Controls Stetho Base64 encoded thumbnail Partial response Appropriate

    data model Stetho WebP Prioritized request Appropriate image size Generate documentation Auto validation App server Image server