Slide 1

Slide 1 text

© 2014 Building First Class Android SDKs Ty Smith @tsmith Twitter 1

Slide 2

Slide 2 text

Developers Are Lazy

Slide 3

Slide 3 text

© 2014 3 SDKs are taking over! ‣ 1.2M Play Store Apps (“Literally Zillions” - Thanks Kevin!) ‣ Abs in 7.34% of all apps ‣ 88K apps

Slide 4

Slide 4 text

Crashlytics for Android

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

© 2014 6 Dashboard

Slide 7

Slide 7 text

© 2014 7 Dashboard

Slide 8

Slide 8 text

© 2014 8 Dashboard

Slide 9

Slide 9 text

© 2014 9 IDE Integrations

Slide 10

Slide 10 text

© 2014 CLI Build Tools 10

Slide 11

Slide 11 text

© 2014 Great SDK Qualities ‣ Easy to use ‣ Flexible ‣ Lightweight ‣ Performant ‣ Reliable ‣ Available 11

Slide 12

Slide 12 text

© 2014 A useful SDK Let’s build an SDK! 12

Slide 13

Slide 13 text

© 2014 Before we get started ‣ Jar vs AAR vs APKlib ‣ Don’t use APKlib ‣ Majority of Android Devs still on Eclipse :-( ‣ Eclipse has no AAR support today ‣ JAR has no resources support 13

Slide 14

Slide 14 text

© 2014 14 android { compileSdkVersion 19 buildToolsVersion "19.1.0" defaultConfig { minSdkVersion 14 targetSdkVersion 19 } } SDK build script apply plugin: 'com.android.library'

Slide 15

Slide 15 text

© 2014 15 public class Sdk { public static Sdk with(Activity activity) { ... } } Sdk class

Slide 16

Slide 16 text

© 2014 16 apply plugin: 'android' android { compileSdkVersion 19 buildToolsVersion "19.1.0" defaultConfig { minSdkVersion 14 targetSdkVersion 19 } repositories { maven { url 'http:/yourSDKrepo.com' } } dependencies { compile 'com.example:sdk:1.0' } } Sample Code Build Script

Slide 17

Slide 17 text

© 2014 public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } final Sdk sdk = Sdk.with(this); 17 Initializing the SDK

Slide 18

Slide 18 text

© 2014 Easy to use 18

Slide 19

Slide 19 text

final Sdk sdk = Sdk.with(this);

Slide 20

Slide 20 text

© 2014 ... Api Key: Getting dependencies

Slide 21

Slide 21 text

© 2014 21 public class Sdk { private static final String API_KEY = "com.example.ApiKey"; private final Context context; private final String apiKey; Sdk(Context context, String apiKey) { this.context = context; this.apiKey = apiKey; } public static Sdk with(Activity activity) { Context ac = activity.getApplicationContext(); ApplicationInfo ai = ac.getPackageManager().getApplicationInfo(ac.getPackageName(),
 PackageManager.GET_META_DATA); String apiKey = ai.metaData.getString(API_KEY); return new Sdk(ac, apiKey); } } Api Key: Getting dependencies

Slide 22

Slide 22 text

© 2014 API Design 22

Slide 23

Slide 23 text

© 2014 Great API Qualities ‣ Intuitive ‣ Consistent ‣ Simple ‣ Easy to use, hard to misuse 23

Slide 24

Slide 24 text

© 2014 public class SDK implements SensorEventListener { @Override public void onSensorChanged(SensorEvent event) { if (canVibrate(event)) { Vibrator vib = (Vibrator) _appContext
 .getSystemService(Service.VIBRATOR_SERVICE); vib.vibrate(500); } } callServer(); API Design: Generic Hooks

Slide 25

Slide 25 text

© 2014 /** * Set implementation specific parameters, to be send as header parameters * @param key * @param value */ public void setParam(String key, String value){ mParamsTable.put(key, value); } private void callServer() { HttpURLConnection conn; try { conn = (HttpURLConnection) mUrl.openConnection(); for (String key : mParamsTable.keySet() ) { conn.addRequestProperty(key, mParamsTable.get(key)); } } catch (IOException e) { e.printStackTrace(); } } API Design: Generic Hooks

Slide 26

Slide 26 text

© 2014 // Requires android.permission.READ_PHONE_STATE TelephonyManager tMgr = (TelephonyManager)this
 .getSystemService(Context.TELEPHONY_SERVICE); sdk.setParam("phone", tMgr.getLine1Number()); API Design: Generic Hooks

Slide 27

Slide 27 text

© 2014 public interface SdkCallback { public void proximityAlert(); } public static void setProximityCallBack(SdkCallback callback) { this.callback = callback; } @Override public void onSensorChanged(SensorEvent event) { if (shouldTrigger(event)){ if (canVibrate()) { vibrate(); } callServer(); if (callback != null) { callback.proximityAlert(); } } } API Design: Callbacks

Slide 28

Slide 28 text

© 2014 API Design: Callbacks 28 sdk.setProximityCallBack(new SdkCallback() { @Override public void proximityAlert() { //do something UI or App Specific that the SDK can't do! } });

Slide 29

Slide 29 text

© 2014 sdk.setDebugMode(true); @Deprecated /** * Allows developer to set debug mode * @see #setDeveloperMode * @deprecated */ public void setDebugMode(boolean debugMode){ debugMode = debugMode; } API Design: Deprecation

Slide 30

Slide 30 text

© 2014 Flexible 30

Slide 31

Slide 31 text

© 2014 31 Builder Object public class Sdk { class Builder { private Context context; private String apiKey; public Builder(Context context) { this.context = context; } public Builder setApiKey(String apiKey) { this.apiKey = apiKey; return this; } public Sdk build() { if (apiKey == null) { /* get from manifest */ } return new Sdk(context, apiKey); } } }

Slide 32

Slide 32 text

© 2014 32 Builder Object public class Sdk { class Builder { … private SdkCallback callback; public Builder setSdkCallback(SdkCallback callback) { this.apiKey = apiKey; return this; } public Sdk build() { … return new Sdk(context, apiKey, callback); } } }

Slide 33

Slide 33 text

© 2014 33 Custom Logger interface Logger { void d(String tag, String text, Throwable throwable); void v(String tag, String text, Throwable throwable); void i(String tag, String text, Throwable throwable); void w(String tag, String text, Throwable throwable); void e(String tag, String text, Throwable throwable); }

Slide 34

Slide 34 text

© 2014 34 Custom Logger public class Sdk { class Builder { private Logger logger; public Builder setLogger(Logger logger) { this.logger = logger; } public Sdk build() { if (logger == null) { logger = new DefaultLogger(); } return new Sdk(context, apiKey, logger); } } }

Slide 35

Slide 35 text

© 2014 Permissions 35

Slide 36

Slide 36 text

© 2014 protected boolean canVibrate() { String permission = "android.permission.VIBRATE"; int result = context.checkCallingOrSelfPermission(permission); return (result == PackageManager.PERMISSION_GRANTED); } Permissions: Runtime Detection

Slide 37

Slide 37 text

© 2014 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { return formatID(Build.HARDWARE);
 } return null; Feature Detection

Slide 38

Slide 38 text

© 2014 try { Activity a = startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0); } catch (ActivityNotFoundException e){ } PackageManager pm = 
 context.getApplicationContext().getPackageManager(); List activities = pm.queryIntentActivities(
 new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0); if (activities.size() > 0) { // you have an app that can recognize speech } Feature Detection

Slide 39

Slide 39 text

© 2014 Multiple Application types 39 package com.example; import android.app.Service; public class MyService extends Service { }

Slide 40

Slide 40 text

© 2014 40 public class SDK { ... public static Sdk with(Activity activity) { Context ac = activity.getApplicationContext(); ApplicationInfo ai = ac.getPackageManager().getApplicationInfo(
 ac.getPackageName(),PackageManager.GET_META_DATA); String apiKey = ai.metaData.getString(API_KEY);
 return new Sdk(ac, apiKey); } } Support All Application Types

Slide 41

Slide 41 text

© 2014 41 public class SDK { ...
 /** *Start SDK without UI functionality */ public static Sdk with(Context context) { 
 Context ac = activity.getApplicationContext(); ApplicationInfo ai = 
 ac.getPackageManager().getApplicationInfo(
 ac.getPackageName(),PackageManager.GET_META_DATA); String apiKey = ai.metaData.getString(API_KEY); return new Sdk(ac, apiKey); } } Support All Application Types

Slide 42

Slide 42 text

© 2014 42 private WeakReference currentActivity = 
 new WeakReference(); … @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private void registerLifecycleCallbacks() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO && context instanceof Application) { final Application app = ((Application)context); app.registerActivityLifecycleCallbacks(
 new ActivityLifecycleCallbacks() { … @Override public void onActivityResumed(Activity activity) { currentActivity.set(activity); } … }); } } UI from an Application Context

Slide 43

Slide 43 text

© 2014 43 public class Sdk { … private WeakReference currentActivity = 
 new WeakReference(); … public void showErrorDialog(String error) { final Activity activity = currentActivity.get(); if (activity != null && !activity.isFinishing()) { new AlertDialog.Builder(activity)
 .setMessage(error).create().show(); } } } UI from an Application Context

Slide 44

Slide 44 text

© 2014 Lightweight 44

Slide 45

Slide 45 text

© 2014 Binary Footprint ‣ Users prefer fast downloads ‣ Association between small and fast ‣ Not all networks are created equal ‣ Reluctance to add large libraries 45

Slide 46

Slide 46 text

© 2014 Third party libraries 46 protobuf ours KB KB

Slide 47

Slide 47 text

© 2014 ProGuard - .apk Size Without ProGuard With ProGuard Reduction ApiDemo 2.6MB 2.5MB 4% Google I/O App 1.9MB 906KB 53% http://www.saikoa.com/downloads/ProGuard_DroidconLondon2012.pdf ProGuard

Slide 48

Slide 48 text

© 2014 task :sdkfootprint => [:testclient] do sdk_project_size = File.size?("#{testclient_dir}/SDKAndroidProject/bin/SDKAndroidProject-debug.apk") project_size = File.size?("#{testclient_dir}/NonSDKAndroidProject/bin/NonSDKAndroidProject-debug.apk") sdk_footprint = (sdk_project_size - project_size) / 1000 build_time = DateTime.now.strftime("%F %r") `echo "SDK footprint is #{sdk_footprint} kb at #{build_time}" > #{artifacts_dir}/sdkfootprint.txt` end SDK footprint is 61 kb at 2013-11-13 Reporting Size

Slide 49

Slide 49 text

© 2014 >./gradlew assemble Dalvik Method Count … Unable to execute dex: method ID not in [0, 0xffff]: 65536 Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536

Slide 50

Slide 50 text

© 2014 > git clone [email protected]:mihaip/dex-method-counts.git > cd dex-method-counts > ant jar > ./dex-method-counts path/to/App.apk Dalvik Method Count https://github.com/mihaip/dex-method-counts Read in 65490 method IDs. : 65490 : 3 android: 6837 accessibilityservice: 6 bluetooth: 2 content: 248 pm: 22 res: 45 ... com: 53881 adjust: 283 sdk: 283

Slide 51

Slide 51 text

© 2014 Performant 51

Slide 52

Slide 52 text

© 2014 Startup Time 52 Thread.start(); Executors.newSingleThreadExecutor();

Slide 53

Slide 53 text

© 2014 Startup Time 53 class MyThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable runnable) { final Thread thread = new Thread(runnable); thread.setPriority(Process.
 THREAD_PRIORITY_BACKGROUND); return thread; } }

Slide 54

Slide 54 text

© 2014 54 public class Sdk { Sdk(Context context, String apiKey, Logger logger) { this.context = context; this.apiKey = apiKey; this.logger = logger; initialize(); initializeAsync(); } private initialize() { //Do light amount of work to get started immediately } private initializeAsync() { executorService.submit(/* Heavyweight work - Network and IO*/); } } Startup Time

Slide 55

Slide 55 text

© 2014 Network usage 55 10x smaller 100x faster XML protobuf

Slide 56

Slide 56 text

© 2014 // Log.d logs are stripped when you app is compiled with 
 // debuggable=false in the manifest sdk.getLogger().d(tag, "Debugging message"); CPU Usage: Log only in Debug // Use your own flag for better control if (debugMode){ sdk.getLogger().w(tag, "Warning message"); } or

Slide 57

Slide 57 text

© 2014 > adb shell top -m 10 CPU Usage: Monitoring User 11%, System 11%, IOW 0%, IRQ 0% User 36 + Nice 2 + Sys 36 + Idle 249 + IOW 1 + IRQ 0 + SIRQ 0 = 324 PID PR CPU% S #THR VSS RSS PCY UID Name 7668 0 10% S 36 424680K 95592K fg app_40 com.facebook.katana 8625 0 5% R 1 1124K 472K fg shell top 15997 0 4% S 1 1092K 868K fg root /sbin/tpd 640 0 0% S 33 377260K 54840K fg system com.android.systemui 202 0 0% S 22 104676K 7556K fg system /system/bin/surfaceflinger 7395 0 0% S 1 0K 0K fg root kworker/u:2 93 0 0% S 1 0K 0K fg root mmcqd/0 438 0 0% S 89 443620K 77172K fg system system_server 1188 0 0% S 5 5324K 372K fg root /system/bin/mpdecision 28663 0 0% S 1 0K 0K fg root kworker/0:3

Slide 58

Slide 58 text

© 2014 Battery consumption 58

Slide 59

Slide 59 text

© 2014 Battery Consumption 59 High Power Low Power Idle Unbundled Transfers Bundled Transfers

Slide 60

Slide 60 text

© 2014 Reliable 60

Slide 61

Slide 61 text

© 2014 try { //Do all the things! } catch (Exception e){ //Log something meaningful } Catch all Exceptions

Slide 62

Slide 62 text

© 2014 62 private static final String API_KEY = "com.example.ApiKey"; private static AtomicBoolean debugMode = new AtomicBoolean(); public Sdk void with(Context context) { final String apiKey = bundle.getString(API_KEY); if (debugMode.get() && TextUtils.isEmpty(apiKey)){ throw new IllegalArgumentException("apiKey cannot be null!"); } else { return null; } return new Sdk(context, apiKey); } public static void setDebugMode(boolean debugMode){ debugMode.set(debugMode); } Degrade Gracefully

Slide 63

Slide 63 text

© 2014 > ./gradlew connectedAndroidTests com.example.android.SDKListenerTest:.... com.example.android.SDKTest:.......... com.example.android.UtilsTest:............. Test results for InstrumentationTestRunner=................................... Time: 44.579 OK (35 tests) Testing

Slide 64

Slide 64 text

© 2014 More Testing ‣ Android Testing is hard ‣ Split into Java project and Android project ‣ Use a Mocking framework ‣ Use Robolectric 64

Slide 65

Slide 65 text

© 2014 Available 65

Slide 66

Slide 66 text

© 2014 Javadoc 66 /** * Sets the proximity callback that is triggered when the 
 * SDK gets a SensorChanged event for PROXIMITY * @param callback {@link SdkCallback} the callback to trigger */ public static void setProximityCallBack(SdkCallback callback) { callback = callback; } void com.example.SDK.setProximityCallBack(SdkCallback callback) Sets the proximity callback that is triggered when the 
 SDK gets a SensorChanged event for PROXIMITY Parameters: callback the callback to trigger

Slide 67

Slide 67 text

© 2014 Sample code ‣ Will be referenced for usage ‣ People will copy and paste ‣ Keep it as concise 67

Slide 68

Slide 68 text

© 2014 68 Maven Central

Slide 69

Slide 69 text

© 2014 apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/ master/gradle-mvn-push.gradle' apply plugin: ‘android-library' android { compileSdkVersion 19 buildToolsVersion "19.1.0" defaultConfig { minSdkVersion 14 targetSdkVersion 19 } } Maven Push https://github.com/chrisbanes/gradle-mvn-push

Slide 70

Slide 70 text

© 2014 Gradle.properties POM_NAME=Proximity Sensor SDK POM_ARTIFACT_ID=library POM_PACKAGING=aar VERSION_NAME=1.0 VERSION_CODE=1 GROUP=com.example https://github.com/chrisbanes/gradle-mvn-push

Slide 71

Slide 71 text

© 2014 > ./gradlew uploadArchives Distribution: Upload https://github.com/chrisbanes/gradle-mvn-push

Slide 72

Slide 72 text

© 2014 Great SDK Qualities ‣ Easy to use ‣ Flexible ‣ Lightweight ‣ Performant ‣ Reliable ‣ Available 72

Slide 73

Slide 73 text

© 2014 73 Q & A Ty Smith @tsmith Twitter