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

Android From The Trenches

Android From The Trenches

These slides are for the talk "Android From the Trenches: Lessons learned from having two apps in the top 100 on Google Play for 3 years". You can watch a recording of this talk here: http://vimeo.com/92773940

Donn Felker

October 18, 2014
Tweet

More Decks by Donn Felker

Other Decks in Technology

Transcript

  1. Android From The Trenches Lessons learned from having two apps

    in the top free category on Google play for over 3 years.
  2. What are these crashes? Huh? These never happened in dev.

    java.io.IOException: Attempted read from closed stream. com.android.music.sync.common.SoftSyncException: java.io.IOException: Attempted read from closed stream. at com.android.music.sync.google.MusicSyncAdapter.getChangesFromServerAsDom(MusicSyncAdapter.java:545) at com.android.music.sync.google.MusicSyncAdapter.fetchDataFromServer(MusicSyncAdapter.java:488) at com.android.music.sync.common.AbstractSyncAdapter.download(AbstractSyncAdapter.java:417) at com.android.music.sync.common.AbstractSyncAdapter.innerPerformSync(AbstractSyncAdapter.java:313) at com.android.music.sync.common.AbstractSyncAdapter.onPerformLoggedSync(AbstractSyncAdapter.java:243) at com.google.android.common.LoggingThreadedSyncAdapter.onPerformSync(LoggingThreadedSyncAdapter.java:33) at android.content.AbstractThreadedSyncAdapter$SyncThread.run(AbstractThreadedSyncAdapter.java:164) Caused by: java.io.IOException: Attempted read from closed stream. at org.apache.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:148) at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:159) at java.util.zip.GZIPInputStream.readFully(GZIPInputStream.java:212) at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:81) at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:64) at android.net.http.AndroidHttpClient.getUngzippedContent(AndroidHttpClient.java:218) at com.android.music.sync.api.MusicApiClientImpl.createAndExecuteMethod(MusicApiClientImpl.java:312) at com.android.music.sync.api.MusicApiClientImpl.getItems(MusicApiClientImpl.java:588) at com.android.music.sync.api.MusicApiClientImpl.getTracks(MusicApiClientImpl.java:638) at com.android.music.sync.google.MusicSyncAdapter.getChangesFromServerAsDom(MusicSyncAdapter.java:512) ... 6 more
  3. Example @RunWith(RobolectricTestRunner.class) public class SomeValueServiceImplTest { UserValueService uvs; UserService user;

    @Before public void setup() { user = mock(UserService.class); // An interface when(user.getGoals()).thenReturn(mock(UserGoals.class)); // Mock another interface uvs = new UserValueService(user); } @Test public void shouldBeAbleToGetBasicWeightHeightInfo() { when(user.getGoals().getHeight()).thenReturn(1223.0012f); when(user.getGoals().getWeight()).thenReturn("1,223g"); assertThat(uvs.getHeightGoal()).isEqualTo(1223.0012f); assertThat(uvs.getWeightGoal()).isEqualTo("1,223g"); } }
  4. Click on a spinner to open the item selection onView(withId(R.id.spinner_simple)).perform(click());

    Click on the item 'Americano' onData(allOf(is(instanceOf(String.class)), is("Americano"))) .perform(click()); Verify that the TextView contains the String "Americano" onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
  5. build.gradle buildTypes { debug { packageNameSuffix '.debug' } release {

    proguardFile plugin.getDefaultDexGuardFile('dexguard-release.pro') proguardFile 'dexguard-project.txt' } }
  6. SIMPLE EXAMPLE @Override public void onListItemClick(final ListView l, final View

    v, final int position, final long id) { super.onListItemClick(l, v, position, id); Toast.makeText(getActivity(), "Seems Legit!", Toast.LENGTH_SHORT).show(); } Seems Legit?
  7. Lesson Always Check For Null Because at scale, it will

    be null Even when it should NEVER be null
  8. Boss Man Says The app has to have the feature

    this release. I know you just heard about it and we're shipping this afternoon.
  9. Example Android Injection From Android Bootstrap public class NewsListFragment extends

    ItemListFragment<News> { @Inject protected BootstrapServiceProvider serviceProvider; @Inject protected LogoutService logoutService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Injector.inject(this); } }
  10. Example Pojo CTOR Injection From Android Bootstrap public class LogoutService

    { protected final Context context; protected final AccountManager accountManager; @Inject public LogoutService(final Context context, final AccountManager accountManager) { this.context = context; this.accountManager = accountManager; } }
  11. Well This Looks Like Fun 04-19 10:15:14.164 464-477/system_process D/BluetoothManagerService Stored

    bluetooth Name=,Address=22:22:67:56:65:B2 04-19 10:15:14.164 464-477/system_process I/InputManager Starting input manager 04-19 10:15:14.164 464-477/system_process I/SystemServer Bluetooth Manager Service 04-19 10:15:14.172 464-477/system_process I/SystemServer Input Method Service 04-19 10:15:14.172 464-477/system_process W/InputMethodManagerService Couldnt create dir.: /data/system/inputmethod 04-19 10:15:14.184 464-477/system_process W/ResourceType Failure getting entry for 0x7f060000 (t=5 e=0) in package 0 (error -75) 04-19 10:15:14.188 464-477/system_process E/Trace error opening trace file: No such file or directory (2) 04-19 10:15:14.188 464-477/system_process I/SystemServer Accessibility Manager 04-19 10:15:14.192 484-486/? D/SurfaceFlinger setOrientation, mFbdev=0xb8969e90, mFbDev->setOrientation=0xb6774e20, orientation=0 04-19 10:15:14.192 484-486/? I/gralloc_vbox86 setOrientation: orientation=0 04-19 10:15:14.192 464-477/system_process I/SystemServer Mount Servic 04-19 10:15:14.196 485-485/? E/WVMExtractor Failed to open libwvm.so 04-19 10:15:14.196 464-477/system_process I/SystemServer LockSettingsService 04-19 10:15:14.196 464-477/system_process I/SystemServer Device Policy 04-19 10:15:14.196 464-477/system_process I/SystemServer Status Bar 04-19 10:15:14.196 464-477/system_process I/SystemServer Clipboard Service 04-19 10:15:14.196 464-477/system_process I/SystemServer NetworkManagement Service 04-19 10:15:14.196 464-477/system_process I/SystemServer Text Service Manager Service 04-19 10:15:14.204 464-477/system_process I/SystemServer NetworkStats Service 04-19 10:15:14.204 464-477/system_process I/SystemServer NetworkPolicy Service 04-19 10:15:14.208 464-524/system_process E/EventHub could not get driver version for /dev/input/mouse3, Not a typewriter 04-19 10:15:14.208 464-537/system_process I/PackageManager No secure containers on sdcard 04-19 10:15:14.208 464-537/system_process E/MountService Error processing initial volume state java.lang.NullPointerException at com.android.server.MountService.updatePublicVolumeState(MountService.java:630) at com.android.server.MountService.access$1400(MountService.java:103) at com.android.server.MountService$3.run(MountService.java:723) 04-19 10:15:14.208 464-477/system_process I/SystemServer Wi-Fi P2pService
  12. Like This 04-19 10:21:13.527 1152-1152/ com.myfitnesspal.android D/ COM.MYFITNESSPAL.ANDROID/ AbstractNavigationHelper.java:370 MFP

    main starting activity normally: Intent { act=android.intent.action.MAIN flg=0x16000000 cmp=com.myfitnesspal.android/ com.myfitnesspal.activity.Welcome2 }
  13. Ln.java Provides » File Names » Line Numbers » Package

    Name » Default Verbose logging for debug builds » Built in String formatting
  14. Hugo Annotation-Triggered method call logging for your debug builds @DebugLog

    public String getName(String first, String last) { SystemClock.sleep(15); // Don't ever really do this! return first + " " + last; } Result D/Example: ⇢ getName(first="Bill", last="Cosby") D/Example: ⇠ getName [16ms] = "Bill Cosby"
  15. What is a Deep Link? A URI your app intercepts

    and responds to I'm sure you've seen this happen with Twitter, YouTube, GooglePlus, Groupon and more
  16. Defined in the AndroidManifest.xml <activity android:name="com.donnfelker.activity.DeepLinkRouterActivity" android:noHistory="true"> <intent-filter> <action android:name="android.intent.action.VIEW"/>

    <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="donnfelker"/> <data android:host="www.donnfelker.com" android:scheme="http" path="..."/> </intent-filter> </activity>
  17. Fragments are Good For » Responsive Layout » Screen and

    UX Composition » Hosting Fragments in Fragments » View Pagers » Sliding Drawer/etc
  18. Very Similar to Other A/B Test Frameworks { "abtests": [

    { name: "201404-loginButtonColor", variants : [ { name: "orange", data: { color: "#FF9900" } }, { name: "yellow", data: { color: "#FEFEDF" } } ] } ] }
  19. Libraries - Dagger - Otto - Picasso - OkHttp -

    Retrofit - Spoon - Http-Request - RxJava - Gson - Jackson - Guava - Butterknife - Hugo