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

[Droidcon Vienna '19] My love-hate relationship with Android Studio

[Droidcon Vienna '19] My love-hate relationship with Android Studio

Android Studio is the tool of the trade hands down. When it comes to Android (app/library/IoT) development, the first thing that comes to mind is to install Android Studio and get going from there. However, it has not been a smooth road as expected for me. In my own experience, I talk about my love-hate relationship with Android Studio. I try to cover what Android Studio gets right and what it doesn't. The talk focuses on how to be embrace performance when Android Studio is the bottleneck in the workflow (slow build times and reduced productivity), how switching to a terminal and various tools that exist in the ecosystem can give Android developers the much-needed speed boost and in-depth view in their development workflow. By the end of this talk, you would have embraces a workflow that allows for increased productivity and faster development.

Google Slides: https://docs.google.com/presentation/d/1AagrH37yMNRna2GAnpBxa150vKo1_wD31bvh7U-2t9E/edit?usp=sharing

Event: https://web.archive.org/web/20190921195636/https://droidcon.at/schedule/#session-108

Nishant Srivastava

September 19, 2019
Tweet

More Decks by Nishant Srivastava

Other Decks in Technology

Transcript

  1. 1

    View Slide

  2. The disclaimer
    @nisrulz 2

    View Slide

  3. Where it started
    @nisrulz 3

    View Slide

  4. What happened
    ● Trying to build an Android Project
    ○ Typical project
    ○ Multi module
    4
    @nisrulz

    View Slide

  5. What happened
    ● Trying to build an Android Project
    ○ Native code (NDK)
    ○ Common dependencies:
    Retrofit, Dagger, Rx, Support
    Libraries, etc
    5
    @nisrulz

    View Slide

  6. What happened
    ● Laptop: Macbook Air (early 2014)
    ○ Core i5(1.4 Ghz)
    ○ 4GB DDR3 RAM
    ○ 128GB SSD
    ○ Intel HD Graphics (1.5GB)
    6
    @nisrulz

    View Slide

  7. What happened
    My build times: ~37 min
    7
    @nisrulz

    View Slide

  8. What happened
    My build times: ~37 min
    Not just the first time, but every
    other build after that too
    8
    @nisrulz

    View Slide

  9. What happened
    My build times: ~37 min
    …and not only that
    9
    @nisrulz

    View Slide

  10. What happened
    ...but also
    10
    @nisrulz

    View Slide

  11. What happened
    With a few gradle tweaks in the
    project.
    11
    @nisrulz
    https://guides.gradle.org/performance/

    View Slide

  12. What happened
    Build times down to ~28 min
    With a few gradle tweaks in the
    project.
    12
    @nisrulz
    https://guides.gradle.org/performance/

    View Slide

  13. What happened
    13
    @nisrulz
    After exhausting all possibilities.

    View Slide

  14. What happened
    Switched to Terminal builds and
    VS Code Editor.
    14
    @nisrulz
    +
    After exhausting all possibilities.

    View Slide

  15. What happened
    15
    @nisrulz
    +

    View Slide

  16. What happened
    16
    @nisrulz
    +
    Final result.

    View Slide

  17. What happened
    17
    @nisrulz
    +
    Build times down to ~3
    minutes(89% reduction)
    Final result.

    View Slide

  18. 18
    @nisrulz
    How I reduced my Android build times by 89%

    View Slide

  19. 19
    @nisrulz
    I was just happy that I was not in a
    spot like this guy

    View Slide

  20. The experience.
    @nisrulz 20

    View Slide

  21. The experience
    State of speed: Slow
    21
    @nisrulz

    View Slide

  22. The experience
    Basically this...
    State of speed: Slow
    22
    @nisrulz

    View Slide

  23. The experience
    State of speed: Slow
    23
    @nisrulz

    View Slide

  24. The experience
    State of reliability: Critical issues
    24
    @nisrulz

    View Slide

  25. The experience
    State of reliability: Critical issues
    25
    @nisrulz
    https://issuetracker.google.com/issues/133864394

    View Slide

  26. The experience
    State of reliability: Critical issues
    26
    @nisrulz
    https://issuetracker.google.com/issues/133864394

    View Slide

  27. The experience
    State of reliability: Critical issues
    27
    @nisrulz
    ~3 months
    https://issuetracker.google.com/issues/133864394

    View Slide

  28. The experience
    State of reliability: Finicky
    28
    @nisrulz

    View Slide

  29. The experience
    State of reliability: Finicky
    29
    @nisrulz

    View Slide

  30. The experience
    To a point where someone had to
    build this:
    A nuke script!
    https://github.com/rock3r/deep-clean
    30
    @nisrulz

    View Slide

  31. The experience
    - Tweet dated: 26 July 2019
    - Android Studio 3.5 Stable
    (released): 20 August 2019
    > Was already on bleeding edge
    31
    @nisrulz
    https://twitter.com/nisrulz/status/1154760
    114026532864

    View Slide

  32. The experience
    32
    @nisrulz
    https://twitter.com/nisrulz/status/1154758411684392961

    View Slide

  33. The experience
    State of polish:
    33
    @nisrulz
    https://www.reddit.com/r/androiddev/c
    omments/d271hc/wildly_infuriating/

    View Slide

  34. The experience
    State of choice: Forced, deviates from base
    34
    @nisrulz

    View Slide

  35. The experience
    State of memory consumption: Not suitable
    35
    @nisrulz

    View Slide

  36. The experience
    State of memory consumption: Not suitable
    36
    @nisrulz

    View Slide

  37. The experience
    State of memory consumption: Not suitable
    37
    @nisrulz

    View Slide

  38. The experience
    State of CPU consumption: Unacceptable
    38
    @nisrulz

    View Slide

  39. The experience
    State of CPU consumption: Unacceptable
    39
    @nisrulz

    View Slide

  40. The experience
    State of updates: Unreliable
    40
    @nisrulz

    View Slide

  41. The experience
    State of result: false-positives
    41
    @nisrulz

    View Slide

  42. The experience
    State of result: false-positives
    42
    @nisrulz

    View Slide

  43. The experience
    State of tools: Limited
    43
    @nisrulz
    Energy Profiler

    View Slide

  44. The experience
    State of tools: Limited
    44
    @nisrulz
    Constraint Layout Editor

    View Slide

  45. The experience
    State of tools: Limited
    45
    @nisrulz
    Resource Manager

    View Slide

  46. The experience
    State of tools: Limited
    46
    @nisrulz
    Apply Changes

    View Slide

  47. The experience
    State of tools: Limited
    47
    @nisrulz
    Apply Changes
    https://medium.com/androiddevelopers/android-studio-pro
    ject-marble-apply-changes-e3048662e8cd

    View Slide

  48. The experience
    State of tools: Limited
    48
    @nisrulz
    Apply Changes
    https://developer.android.com/studio/known-issues#appl
    y-changes-ki-verification-errors

    View Slide

  49. The experience
    State of tools: Limited
    49
    @nisrulz
    Deployments

    View Slide

  50. The experience
    To summarize, Android Studio is
    ● Slow
    ● Finicky
    ● Forces its defaults and deviates from its base
    50
    @nisrulz

    View Slide

  51. The experience
    To summarize, Android Studio is
    ● New tools are limiting
    ○ Energy Profiler
    ○ Constraint Layout Editor
    ○ Resource Manager
    ○ Apply Changes
    ○ Deployments options
    51
    @nisrulz

    View Slide

  52. The experience
    To summarize, Android Studio is
    ● Uses a lot of Memory and CPU
    ● Has versioning quality problem
    (Stable, Beta, Alpha)
    52
    @nisrulz

    View Slide

  53. and Workarounds.
    @nisrulz 53

    View Slide

  54. Slow, but why?
    ● Gradle Configuration
    ● Indexing changes in code
    ● Plugins
    ● Code Insight: Inplace analysis/inspections
    54
    @nisrulz

    View Slide

  55. Configuring Gradle for speed
    Use the latest Gradle release.
    55
    @nisrulz
    Incremental compile 1000-module Java project
    https://gradle.org/whats-new/gradle-5/

    View Slide

  56. Configuring Gradle for speed
    Use the latest Gradle release.
    Caveat:
    Update to latest Canary release of Android Studio,
    since the support for latest Gradle is tied to
    Android Studio release.
    56
    @nisrulz

    View Slide

  57. Configuring Gradle for speed
    Build Cache: Reuse outputs from any previous
    invocation of Gradle
    // Add this in your global gradle.properties file
    // at ~/.gradle/gradle.properties
    // Enable Build Cache
    android.enableBuildCache=true
    57
    @nisrulz

    View Slide

  58. Configuring Gradle for speed
    Daemon: Dedicated background process to improve
    performance of Gradle
    // Add this in your global gradle.properties file
    // at ~/.gradle/gradle.properties
    // Enable Gradle Daemon
    org.gradle.daemon=true
    58
    @nisrulz

    View Slide

  59. Configuring Gradle for speed
    Allocate memory: Increase the memory provided to JVM
    // Add this in your global gradle.properties file
    // at ~/.gradle/gradle.properties
    // Configure allocated memory
    org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m
    59
    @nisrulz

    View Slide

  60. Configuring Gradle for speed
    Parallel builds: Force Gradle to execute tasks in
    parallel as long as those tasks are in different
    projects.
    // Add this in your global gradle.properties file
    // at ~/.gradle/gradle.properties
    // Enable Parallel builds
    org.gradle.parallel=true
    60
    @nisrulz

    View Slide

  61. Configuring Gradle for speed
    Execution: Prefer running specific task over general
    ones
    // Creates AAR for every module and all variants!
    ./gradlew assemble
    // Creates AAR for every module’s debug variant
    ./gradlew assembleDebug
    // Creates AAR for only `mymodule` in debug variant!
    ./gradlew :mymodule:assembleDebug
    61
    @nisrulz

    View Slide

  62. Reducing Indexing
    If your codebase has files changing on each build, it
    will trigger indexing.
    62
    @nisrulz

    View Slide

  63. Reducing Indexing
    Suggestions:
    ● Avoid using annotation processors
    Or use incremental annotation processors
    ● Using implementation as much as possible, instead
    of api.
    ● Use static build config values i.e version code,
    version name, resources
    63
    @nisrulz

    View Slide

  64. Plugins: Inspect what you use
    Android Studio comes with a lot of Plugins enabled,
    which contribute to its sluggishness.
    64
    @nisrulz

    View Slide

  65. Plugins: Inspect what you use
    Solution:
    Check and disable what you don’t need.
    65
    @nisrulz

    View Slide

  66. Code Insight
    Although required, it does make Android Studio slow.
    Solution:
    Enable Power Save Mode
    66
    @nisrulz

    View Slide

  67. Terminal to the rescue.
    @nisrulz 67

    View Slide

  68. ADB: Android Debug Bridge
    ● Command Line tool
    ● Shipped with Android SDK
    ● Allows interacting with debuggable Android devices
    ● Client-Server program
    ● Available as: adb
    68
    @nisrulz

    View Slide

  69. ADB: Android Debug Bridge
    ● Install App
    ○ adb install path_to_apk
    ● Uninstall App
    ○ adb uninstall com.package.name
    69
    @nisrulz

    View Slide

  70. ADB: Android Debug Bridge
    ● Copy a file from System to Android device
    ○ adb push path_to_file /path_to_dir_on_device
    ● Copy a file from Android device to System
    ○ adb pull /path_to_dir_on_device/file.png
    ~/path_on_system/
    70
    @nisrulz

    View Slide

  71. Other tools via ADB shell
    ● Activity Manager: am
    ● Emulator: emulator
    ● Package Manager: pm
    ● Device Policy Manager: dpm
    ● Logcat: logcat
    ● Dumpsys: dumpsys
    ...and many more.
    71
    @nisrulz

    View Slide

  72. Gradle
    Android projects use Gradle as the build system.
    Each Android project has a gradle wrapper, which is a
    proxy to local Gradle binary.
    Android Studio invokes Gradle to execute tasks
    72
    @nisrulz

    View Slide

  73. Gradle
    The same can be done via terminal:
    ./gradlew assembleDebug
    > is equivalent to Make in Android studio
    73
    @nisrulz

    View Slide

  74. Lint
    This is part of the gradle tasks, which checks the
    code for structural inconsistencies, thus allowing to
    write more maintainable code.
    // Execute lint in project
    ./gradlew lint
    > Similar to executing via Android Studio
    74
    @nisrulz

    View Slide

  75. Lint
    75
    @nisrulz

    View Slide

  76. Detekt
    https://github.com/arturbosch/detekt
    ● Static code analyzer for Kotlin language.
    ● Helps to detect code smell in kotlin codebase
    76
    @nisrulz

    View Slide

  77. Detekt
    https://github.com/arturbosch/detekt
    ● Highly configurable
    ● Enables complexity analysis of the code
    ● Also has a gradle plugin to setup in the project
    77
    @nisrulz

    View Slide

  78. Detekt
    https://github.com/arturbosch/detekt
    // Execute at the root of the project
    java -jar /path_to/detekt-cli.jar --debug
    78
    @nisrulz

    View Slide

  79. Detekt
    // Execute at the root of the project
    java -jar /path_to/detekt-cli.jar --debug
    // Output
    Complexity Report:
    - 686 lines of code (loc)
    - 563 source lines of code (sloc)
    - 425 logical lines of code (lloc)
    - 32 comment lines of code (cloc)
    ...
    - 218 code smells per 1000 lloc
    Project Statistics:
    - number of properties: 73
    - number of functions: 22
    - number of classes: 5
    - number of packages: 3
    - number of kt files: 5
    79
    @nisrulz

    View Slide

  80. Classyshark
    https://github.com/google/andr
    oid-classyshark
    Tool for decompiling and
    analyzing internals of an APK
    such as dex Count. Works with
    .apk, .jar, .aar, .class, .dex
    or .so files.
    80
    @nisrulz

    View Slide

  81. Classyshark
    https://github.com/google/andr
    oid-classyshark
    Execute:
    java -jar ClassyShark.jar
    81
    @nisrulz

    View Slide

  82. Classyshark
    82
    @nisrulz

    View Slide

  83. Classyshark
    83
    @nisrulz

    View Slide

  84. AVD Manager
    ● Command line tool to create and manage Android
    Virtual Devices (AVD)
    ● Located in android_sdk/tools/bin/
    84
    @nisrulz

    View Slide

  85. AVD Manager
    // List all avd
    avdmanager list
    // Create a new avd
    avdmanager create avd -n myavd -k
    "system-images;android-29;google_apis;x86"
    // Remove avd
    avdmanager delete avd -n myavd
    85
    @nisrulz

    View Slide

  86. Scrcpy
    https://github.com/Genymobile/scrcpy
    Command-line tool for mirroring and
    controlling your device.
    // Start mirroring the device
    scrcpy
    //Record while mirroring
    scrcpy --record file.mp4
    86
    @nisrulz

    View Slide

  87. Scrcpy
    https://github.com/Genymobile/scrcpy
    Command-line tool for mirroring and
    controlling your device.
    // Show touches
    scrcpy --show-touches
    // Switch off screen
    scrcpy --turn-screen-off
    87
    @nisrulz

    View Slide

  88. SDK Manager
    ● Command line tool to manage Android SDK
    ● Used to view, install, remove packages in Android
    SDK
    ● Located in android_sdk/tools/bin/
    88
    @nisrulz

    View Slide

  89. SDK Manager
    // List all available packages
    sdkmanager --list
    // Install packages
    sdkmanager packages
    // Remove packages
    sdkmanager --uninstall packages
    // Update all packages
    sdkmanager --update
    89
    @nisrulz

    View Slide

  90. Battery Historian
    https://github.com/google/battery-historian
    ● Python based tool
    ● Used to inspect battery related information and
    events, while the device was not plugged in
    90
    @nisrulz

    View Slide

  91. Battery Historian
    https://github.com/google/battery-historian
    ● For Android device running Android 5.0 Lollipop
    (API level 21) and later.
    ● Useful since the new Energy Profilers are Android
    Studio only.
    91
    @nisrulz

    View Slide

  92. Battery Historian
    92
    @nisrulz

    View Slide

  93. Battery Historian
    93
    @nisrulz

    View Slide

  94. IntelliJ Idea
    ● Android Studio is based on
    IntelliJ Idea
    ○ IntelliJ Idea can be used for
    Android Development
    ● Faster for development
    94
    @nisrulz

    View Slide

  95. IntelliJ Idea
    ● Consumes less memory/CPU than
    Android Studio
    ● Full plugin support
    ● More frequent updates
    95
    @nisrulz

    View Slide

  96. IntelliJ Idea
    ● Doesn’t have the Android Studio
    specific tools
    ○ Constraint Layout Editor
    ○ Profilers
    ○ Resource Manager
    ○ Apply Changes
    96
    @nisrulz

    View Slide

  97. Mainframer
    ● Build your project remotely on a
    better hardware
    ● Uses rsync to sync changes over
    ssh and then builds the project,
    syncs the apk back.
    https://github.com/buildfoundation/mainframer
    97
    @nisrulz

    View Slide

  98. Automation
    ● Alias creation enables executing complex tool
    configuration as a simple command.
    Example:
    // Create alias to push release apk to Downloads folder in Device
    alias pushReleaseToDevice="adb push ./apk-release.apk /sdcard/Downloads"
    // Execute
    pushReleaseToDevice ↵
    98
    @nisrulz

    View Slide

  99. Automation
    ● Command line tools enable ability to include in
    scripts. Using alias simplify the process.
    # Install APK to device
    # Use: apkinstall app-debug.apk
    alias apkinstall="adb devices | tail -n +2 | cut
    -sf 1 | xargs -I X adb -s X install -r $1"
    99
    @nisrulz

    View Slide

  100. Automation
    ● Command line tools enable ability to include in scripts. Using
    alias simplify the process.
    # As an alternative to apkinstall, you can also do just ./gradlew installDebug
    #
    # Alias for building and installing the apk to connected device
    # Run at the root of your project
    # Usage: buildAndInstallApk
    alias buildAndInstallApk='./gradlew assembleDebug && apkinstall
    ./app/build/outputs/apk/debug/app-debug.apk'
    100
    @nisrulz

    View Slide

  101. Automation
    ● Command line tools enable ability to include in scripts. Using alias
    simplify the process.
    # Launch your debug apk on your connected device
    # Execute at the root of your android project
    # Usage: launchDebugApk
    function launchDebugApk(){
    local APP_PACKAGE_NAME=$(getPackageName
    ./app/build/outputs/apk/debug/app-debug.apk)
    adb shell monkey -p $APP_PACKAGE_NAME 1 1>/dev/null 2>&1;
    }
    101
    @nisrulz

    View Slide

  102. Automation
    ● Command line tools enable ability to include in
    scripts. Using alias simplify the process.
    # ------------- Single command to build+install+launch apk------------#
    # Execute at the root of your android project
    # Use as: buildInstallLaunchDebugApk
    alias buildInstallLaunchDebugApk="buildAndInstallApk && launchDebugApk"
    102
    @nisrulz

    View Slide

  103. Automation
    ● Command line tools enable ability to include in scripts. Using
    alias simplify the process.
    # Take screenshot
    alias screenshot="adb exec-out screencap -p > screen-$(nowdate).png"
    # Remove app
    alias rmapp="adb devices | tail -n +2 | cut -sf 1 | xargs -I X adb -s X uninstall
    $1"
    # Clear data for app
    alias clearapp="adb devices | tail -n +2 | cut -sf 1 | xargs -I X adb -s X shell pm
    clear $1"
    More automation aliases:
    https://gist.github.com/nisrulz/b0e79f2b3e27f99ca8b5dba9db6281ec
    103
    @nisrulz

    View Slide

  104. Automation++
    ● adbe : ADB Enhanced
    - Script to execute adb commands on all connected devices.
    # Usage
    # Run any command you would run with adb for all the connected devices
    # ./adbe is the equivalent of ./adb -s
    #
    # Examples
    # ./adbe version
    ./adbe install demo_app.apk
    104
    @nisrulz
    https://github.com/nisrulz/terminal-utils/blob/master/android/adbe.sh

    View Slide

  105. Automation
    ● Use gradle plugin to setup all the automation commands
    Novoda’s Gradle Android command plugin creates handy gradle
    tasks in the project itself.
    https://github.com/novoda/gradle-android-command-plugin
    105
    @nisrulz

    View Slide

  106. Automation
    ● Use gradle plugin to setup all the automation commands
    Some commands available:
    ● installDevice - installs the app on a specific device.
    ● uninstallDevice - uninstalls the app from a specific device.
    ● run - installs and launches the app on a specific device.
    and many more.
    Execute: ./gradlew installDeviceDebug
    106
    @nisrulz

    View Slide

  107. The experience.
    @nisrulz 107

    View Slide

  108. The experience
    ● Project Marble
    Project Marble is a multi-release and focused
    effort on making fundamental features of the IDE
    rock-solid and polished.
    108
    @nisrulz

    View Slide

  109. The experience
    ● Project Marble
    Already made improvements under:
    1. UX and performance in Layout Editor
    2. Instant Run has been replaced by Apply Changes
    3. Emulator performance, Snapshots
    4. Lint performance
    109
    @nisrulz

    View Slide

  110. The experience
    ● Project Marble
    Issues: https://developer.android.com/studio/known-issues
    110
    @nisrulz

    View Slide

  111. The experience
    ● APK Analyzer(GUI)
    111
    @nisrulz

    View Slide

  112. The experience
    ● APK Analyzer(Command line)
    // To print the file size of the apk in a human readable format
    //Output: 1.7MB
    112
    @nisrulz

    View Slide

  113. The experience
    ● APK Analyzer
    // Prints the application ID, version code, and version name.
    // Output: in.excogitation.deviceinfo 17 2.0.1
    apkanalyzer apk summary myapp.apk
    113
    @nisrulz

    View Slide

  114. The experience
    ● APK Analyzer
    // Prints an estimate of the download size of the APK.
    // Output: 1.4MB
    apkanalyzer -h apk download-size myapp.apk
    More info: https://developer.android.com/studio/command-line/apkanalyzer
    114
    @nisrulz

    View Slide

  115. The experience
    ● Simpleperf
    A versatile command-line CPU profiling tool included in the NDK
    for Mac, Linux, and Windows.
    115
    @nisrulz

    View Slide

  116. The experience
    ● Simpleperf
    # Find the percentage of time spent in object modules
    simpleperf report --sort tid,comm
    # See how function calls are related, call-graph
    simpleperf report -g
    More info: https://developer.android.com/ndk/guides/simpleperf
    116
    @nisrulz

    View Slide

  117. The experience
    ● NDK-Stack
    It allows you to symbolize stack traces from adb logcat
    It replaces any address inside a shared library with the
    corresponding : from your source code,
    making debugging easier.
    117
    @nisrulz

    View Slide

  118. The experience
    ● NDK-Stack
    # Live mapping
    adb logcat | $NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi-v7a
    More info: https://developer.android.com/ndk/guides/ndk-stack
    118
    @nisrulz

    View Slide

  119. The experience
    ● Other tools
    ○ NDK-GDB: Tool to start a command-line native debugging
    session.
    ○ ASan (Address Sanitizer): Memory error detector for C/C++
    119
    @nisrulz

    View Slide

  120. The experience
    ● Other tools
    ○ systrace: Tool to collect and inspect timing information
    across all processes running on your device at the system
    level.
    ○ dumpsys: Tool to View the Java heap and memory allocations
    with Memory Profiler
    120
    @nisrulz

    View Slide

  121. The experience
    ● Other tools
    ○ bundletool: To recreate, inspect, and verify Google Play’s
    server-side build of your app’s APKs.
    ○ perfetto: collect performance information from your Android
    devices via the Android Debug Bridge (ADB)
    121
    @nisrulz

    View Slide

  122. The experience
    ● Other tools
    122
    @nisrulz

    View Slide

  123. The experience
    ● Improving on-demand dependency inclusion
    123
    @nisrulz

    View Slide

  124. The experience
    ● … and a lot of cool new things the Android Tools team builds
    124
    @nisrulz

    View Slide

  125. Links/References
    Gradle Performance Tips: https://guides.gradle.org/performance/
    Project Marble: https://medium.com/androiddevelopers/tagged/project-marble
    Blog Post:
    https://android.jlelse.eu/how-i-reduced-my-android-build-times-by-89-4242e51
    ce946
    Disable Plugins:
    https://www.reddit.com/r/androiddev/comments/7sxhig/android_studio_slower_wh
    en_using_kotlin/dt88pgn/
    Battery Historian:
    https://developer.android.com/studio/profile/battery-historian
    125
    @nisrulz

    View Slide

  126. Credits
    Icon made by Freepik from www.flaticon.com
    126
    @nisrulz

    View Slide

  127. 127
    twitter.com/nisrulz
    github.com/nisrulz
    www.nisrulz.com

    View Slide

  128. 128
    twitter.com/nisrulz
    github.com/nisrulz
    www.nisrulz.com

    View Slide