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

Building Overlay SDKs on Android - "The two-min...

Building Overlay SDKs on Android - "The two-minute integration challenge"

The Android app ecosystem is evolving every day and developers are seeking better tools and libraries to help them create apps that will stand out in the overcrowded marketplace. Third-party SDKs appear every day promising to aid this process, instead they bring friction and integration challenges - is it worth the effort? More often the answer is no, as many of them need a significant amount of effort to become embedded in an app. Building SDKs for seamless integration is a challenge, especially when you have to deal on the view level. So how do we attack the two-minute integration challenge?

In this session we will discuss a set of good practices on how to build a well-structured SDK focusing on the view level. We will take a deep-dive into the different options available for rendering overlays on Android - particularly through an SDK - highlighting capabilities and limitations of each approach. Over the course of the session we will explore the various technical decisions an SDK developer has to make to overcome platform limitations, focusing on how to provide a minimalistic interaction with the SDK users, while still keeping the full functionality of the SDK under the hood.

Andreas Vourkos

April 26, 2016
Tweet

More Decks by Andreas Vourkos

Other Decks in Technology

Transcript

  1. 8k Android & iOS apps 230m mobile devices An SDK

    that overlays over everything runs on more than
  2. •  What is the purpose of this SDK? •  Who

    is it for? •  What tools/procedures are currently being used? •  How easy will it be to adopt and scale? •  How will you expose/distribute it? •  What are the plaBorm’s limitaDons? IniBal consideraBons when designing an SDK
  3. An SDK to overlay quesBonnaires over any app •  Show

    overlay as full screen •  Usage as a black box for developers •  1 line of code •  Should work on Android 10 and above
  4. An overlay is a view that is placed over other

    views. Such views can be created on Android in several different ways and for several different purposes, i.e.: •  FloaDng buNons/menus •  Onboarding tutorials •  AdverDsing •  Augmented Reality •  Fancy animaDons •  Other Overlays on Android
  5. •  WindowToken is a special type of Binder object. • 

    Used on Android extensively for security reasons. •  A Window Token uniquely idenDfies a window, and is used by WindowManager to decide whether or not a window is allowed to be placed at a requested posiDon. Two types of WindowTokens: •  AppWindowToken •  WindowToken WindowTokens
  6. There are 3 main classes of windows: •  ApplicaBon windows

    - are normal top level windows •  Sub-windows - are windows that are associated with another top-level window •  System windows - are special types of windows for use by the system for specific purposes Windows
  7. Adding an overlay view with WindowManager WindowManager windowManager = (WindowManager)

    context.getSystemService(Context.WINDOW_SERVICE); WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.width = WindowManager.LayoutParams.MATCH_PARENT; params.height = WindowManager.LayoutParams.MATCH_PARENT; params.type = WindowManager.LayoutParams.TYPE_APPLICATION; params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; params.format = PixelFormat.TRANSLUCENT; params.gravity = Gravity.CENTER | Gravity.START; windowManager.addView(overlayView, params);
  8. An implementaDon of Window’s abstract class When an acDvity starts,

    AcDvityManager along with WindowManager request the creaDon of a PhoneWindow. PhoneWindow is responsible for holding the acDvity’s view hierarchy. Phone Window
  9. PhoneWindow has two important members: •  DecorView mDecor – which

    is the top-level view of the window, containing the window decor (like the acDvity's window background) •  ViewGroup mContentParent – which is the view in which the window contents are placed (for example the user's view layout) and is either the DecorView itself, or a child of DecorView where the contents go DecorView is an inner class of PhoneWindow: private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { ... } Phone Window
  10. There are 3 opDons on how to setContentView: 1.  setContentView

    (View view) - Set the acDvity content to an explicit view. 2.  setContentView (int layoutResID) - Set the acDvity content from a layout resource. 3.  setContentView (View view, ViewGroup.LayoutParams params) - Set the acDvity content to an explicit view with specified layout params. SeOng acBvity's content view @Override public void setContentView(View view, ViewGroup.LayoutParams params) { if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mContentParent.addView(view, params); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
  11. setContentView Vs addContentView addContentView main difference with setContentView is that

    it does not remove any previously added views to mContentParent Adding another content view to the AcBvity addContentView(overlayView, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); @Override public void addContentView(View view, ViewGroup.LayoutParams params) { if (mContentParent == null) { installDecor(); } mContentParent.addView(view, params); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } ((ViewGroup) overlayView.getParent()).removeView(overlayView); removing is easy
  12. •  FrameLayout - is designed to hold a stack of

    child views with the most recent placed on top. •  RelaBveLayout - can be used to posiDon child views either relaDve to each other, or to the screen boundaries. Overlay using a FrameLayout or RelaBveLayout
  13. 1.  Check if OverlayView already exists in view hierarchy. 2. 

    Remove OverlayView (if found) and restore to AcDvity's iniDal view hierarchy. 3.  Get reference to the child of DecorView which holds the AcDvity’s view hierarchy. 4.  Create parent RelaDveLayout. 5.  Remove child of DecorView's which holds the AcDvity’s view hierarchy from parent. 6.  Add the original DecorView child view to our parent RelaDveLayout. 7.  Add our parent RelaDveLayout to DecorView as a new child. 8.  Add our OverlayView to parent RelaDveLayout. Overlay with a new DecorView child – re-arrangement flow
  14. public staDc void init(AcDvity act, String apiKey, PosiDon p, int

    indPadding) ; public staDc void init(AcDvity act, String apiKey, PosiDon p, int indPadding, boolean devMode); public staDc void init(AcDvity act, String apiKey, PosiDon p, int indPadding, ViewGroup userLayout) ; public staDc void init( AcDvity act, String apiKey, PosiDon p, int indPadding, PollfishSurveyReceivedListener pollfishSurveyReceivedListener, PollfishSurveyNotAvailableListener pollfishSurveyNotAvailableListener, PollfishSurveyCompletedListener pollfishSurveyCompletedListener, PollfishOpenedListener pollfishOpenedListener, PollfishClosedListener pollfishClosedListener, PollfishUserNotEligibleListener pollfishUserNotEligibleListener) ; … public staDc void customInit (AcDvity act, String apiKey, PosiDon p, int indPadding) ; public staDc void customInit(AcDvity act, String apiKey, PosiDon p, int indPadding, boolean devMode) ; public staDc void customInit (AcDvity act, String apiKey, PosiDon p, int indPadding, ViewGroup userLayout) ; public staDc void customInit ( AcDvity act, String apiKey, PosiDon p, int indPadding, PollfishSurveyReceivedListener pollfishSurveyReceivedListener, PollfishSurveyNotAvailableListener pollfishSurveyNotAvailableListener, PollfishSurveyCompletedListener pollfishSurveyCompletedListener, PollfishOpenedListener pollfishOpenedListener, PollfishClosedListener pollfishClosedListener, PollfishUserNotEligibleListener pollfishUserNotEligibleListener) ; … Expose an interface for your library Think twice - choose the right design paAern!
  15. ParamsBuilder paramsBuilder = new ParamsBuilder("YOUR_API_KEY") .indicatorPadding(50) .indicatorPosiDon(PosiDon.BOTTOM_RIGHT) .customMode(true) .pollfishSurveyCompletedListener(new PollfishSurveyCompletedListener()

    { @Override public void onPollfishSurveyCompleted(boolean playfulSurvey, int surveyPrice) { Log.d(TAG, "onPollfishSurveyCompleted"); } }) .build(); PollFish.initWith(this, paramsBuilder); Expose an interface for your library Easy to use, clean and extensible
  16. In meta data of AndroidManifest.xml Provision and authenBcate - API

    key <manifest … > <applicaDon> <!-- ... --> <meta-data android:name="com.yourlib.ApiKey" android:value="YOUR_API_KEY"/> </applicaDon> </manifest> PollFish.initWith(.this, new ParamsBuilder("YOUR_API_KEY") .build()); or during iniDalizaDon
  17. Give developers a way to test the behavior of your

    library transparently or give them back the power Developer Vs Release Mode boolean isDebuggable = (0 != (acDvity.getApplicaDonInfo().flags & ApplicaDonInfo.FLAG_DEBUGGABLE)); ParamsBuilder paramsBuilder = new ParamsBuilder("YOUR_API_KEY") .releaseMode(true) .build(); PollFish.initWith(this, paramsBuilder);
  18. Throw an excepDon if needed/expected, fail fast Handle failure –

    Inform Inform developer – save the support team! Never fail in release mode if(paramsBuilder==null) { throw new IllegalArgumentExcepDon("ParamsBuilder should never be null"); } Log.w(TAG, "You re using Pollfish SDK for Google Play in developer mode"); log info to the developer
  19. Request minimum permissions Check if a permission is granted during

    runDme Deal silently with permissions PackageManager pm = ctx.getPackageManager(); if ((pm.checkPermission(permission.BLUETOOTH, ctx.getPackageName()) == PackageManager.PERMISSION_GRANTED) && ((pm.checkPermission(permission.BLUETOOTH_ADMIN, ctx.getPackageName()) == PackageManager.PERMISSION_GRANTED))) { //Do some beacon staff }
  20. BluetoothAdapter mBluetoothAdapter=null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { BluetoothManager bluetoothManager =

    (BluetoothManager) acDvity.getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); } else { mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } Keep backwards compaBbility – be agnosBc check Android version
  21. Classpath DetecDon Keep backwards compaBbility – be agnosBc try {

    Class.forName("class.name" ); } catch( ClassNotFoundExcepDon e ) { //this class isn't there! } Thrown when the VM no1ces that a program tries to reference, * on a class or object, a method that does not exist. try{ setLayerType(View.LAYER_TYPE_SOFTWARE, null); }catch (NoSuchMethodError e) { //this method isn't there! }
  22. You can allow AcDvity to implement listeners of SDK events

    Provide listeners/noBficaBons for SDK events ParamsBuilder paramsBuilder = new ParamsBuilder("YOUR_API_KEY"] .pollfishSurveyCompletedListener(new PollfishSurveyCompletedListener() { @Override public void onPollfishSurveyCompleted(boolean playfulSurvey, int surveyPrice) { Log.d(TAG, "onPollfishSurveyCompleted"); } }) .build(); public class MainAcDvity extends AppCompatAcDvity implements PollfishSurveyCompletedListener{…} if(acDvity instanceof PollfishSurveyCompletedListener){…} or allow developer to pass relevant listeners during iniDalizaDon and then internally check
  23. ApplicaDon.AcDvityLifecycleCallbacks acDvityCallbackWrapper = new ApplicaDon.AcDvityLifecycleCallbacks() { public void onAcDvityCreated(AcDvity acDvity,

    Bundle bundle) { } public void onAcDvityStarted(AcDvity acDvity) { } public void onAcDvityResumed(AcDvity acDvity){ } public void onAcDvityPaused(AcDvity acDvity) { } public void onAcDvityStopped(AcDvity acDvity) { } public void onAcDvitySaveInstanceState(AcDvity acDvity, Bundle bundle) { } public void onAcDvityDestroyed(AcDvity acDvity) { } }; applicaDon.registerAcDvityLifecycleCallbacks(acDvityCallbackWrapper ); Listen/register to AcBvity lifecycle events if needed
  24. @Override public boolean onKeyDown(int keyCode, KeyEvent event) { … If

    (keyCode == KeyEvent.KEYCODE_BACK) { if (pollfishViewStatus == PollfishViewStatus.PANEL_OPEN) { hidePanel(); return true; } } …. return false; } Capture and consume key events if needed
  25. •  Handle offline •  Set threads priority •  Batch network

    requests, minimize impact •  Cache files •  Handle orientaDons •  Check keyboard and window flags •  Avoid memory leaks Design for every case
  26. Make it testable/mockable •  Avoid staDc methods •  Avoid final

    classes Obfuscate and opBmize •  Keep public API Release •  Distribute to public repos Make it testable, obfuscate, release
  27. •  An SDK easy to integrate – easy to use

    •  Javadocs and documentaDon •  Sample projects •  Demo apps Deliverables