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

It's Not a Bug, It's a Feature!

It's Not a Bug, It's a Feature!

By Erik Hellman
Video here: HakkaLabs.co/articles/not-a-bug-its-a-feature

Hakka Labs

March 24, 2015
Tweet

More Decks by Hakka Labs

Other Decks in Programming

Transcript

  1. It’s not a bug, it’s a feature! Erik Hellman, Spotify

    Twitter: @ErikHellman Google+: google.com/+ErikHellman Facebook: facebook.com/ErikHellman Github: github.com/ErikHellman
  2. Activity.onSaveInstanceState() • Pressing Home • Calling finish() • Pressing back

    • Rotating device • Starting new Activity ✓ ✓ ✗ ✓ ✗
  3. App Widgets public RemoteViews getViewAt(int position) { RemoteViews rv =

    new RemoteViews(mContext.getPackageName(), R.layout.photo_item); rv.setImageViewBitmap(R.id.widget_item, mAlbums[position]); ... }
  4. App Widgets + ContentProvider public RemoteViews getViewAt(int position) { RemoteViews

    rv = new RemoteViews(mContext.getPackageName(), R.layout.photo_item); Uri albumUri = Uri.parse(ALBUM_BASE_URI + position); ContentResolver contentResolver = mContext.getContentResolver(); InputStream inputStream = contentResolver.openInputStream(albumUri); Bitmap albumPhoto = BitmapFactory.decodeStream(inputStream); rv.setImageViewBitmap(R.id.widget_item, albumPhoto); ... }
  5. App Widgets + ContentProvider public class MyImageProvider extends ContentProvider {

    ... @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { File albumPhoto = new File(mAlbumDir, "album-" + uri.getLastPathSegment() + ".jpg"); return ParcelFileDescriptor.open(albumPhoto, ParcelFileDescriptor.MODE_READ_ONLY); } }
  6. App Widgets + ContentProvider 10-29 10:41:48.688 1235-1247/se.hellsoft. appwidgetwithcollections E/AndroidRuntime﹕ FATAL

    EXCEPTION: Binder_2 Process: se.hellsoft.appwidgetwithcollections, PID: 1235 java.lang.SecurityException: Permission Denial: reading com. example.android.stackwidget.MyImageProvider uri content://com. example.android.stackwidget.photo/albumPhoto/0 from pid=677, uid=10008 requires the provider be exported, or grantUriPermission()
  7. App Widgets + ContentProvider public RemoteViews getViewAt(int position) { RemoteViews

    rv = new RemoteViews(mContext.getPackageName(), R.layout.photo_item); Uri albumUri = Uri.parse(ALBUM_BASE_URI + position); long oldIdentity = Binder.clearCallingIdentity(); ContentResolver contentResolver = mContext.getContentResolver(); InputStream inputStream = contentResolver.openInputStream(albumUri); albumPhoto = BitmapFactory.decodeStream(inputStream); Binder.restoreCallingIdentity(oldIdentity); rv.setImageViewBitmap(R.id.widget_item, albumPhoto); ... }
  8. Cheap auto-start without permission? ;) @Override public void onReceive(Context context,

    Intent intent) { Toast.makeText(context, "Received broadcast: " + intent.getAction(), Toast.LENGTH_SHORT).show(); Intent activity = new Intent(context, MyActivity.class); activity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(activity); }
  9. Lesson learned: App must have been started once before a

    registered receiver will be triggered (for certain Intents)!
  10. Service.onBind() “Multiple clients can connect to the service at once.

    However, the system calls your service's onBind() method to retrieve the IBinder only when the first client binds. The system then delivers the same IBinder to any additional clients that bind, without calling onBind() again.”
  11. Service.onBind() Lesson learned 1: The Binder object is cached by

    the system, based on the Intent used for binding. Always create the Binder object at Service initialisation or onCreate()!
  12. Service.onBind() Lesson learned 2: An Intent for binding is unique

    based on action string and data URI. You can pass parameter to onBind() using the data URI of the Intent! :)
  13. ServiceConnection @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { //

    Called when binding is complete mService = IMyAidlInterface.Stub.asInterface(iBinder); } @Override public void onServiceDisconnected(ComponentName componentName) { // Called when? mService = null; }
  14. ServiceConnection @Override protected void onStart() { super.onStart(); bindService(new Intent(this, MyService.class),

    this, BIND_AUTO_CREATE); } @Override protected void onStop() { unbindService(this); super.onStop(); }
  15. ServiceConnection @Override protected void onStart() { super.onStart(); bindService(new Intent(this, MyService.class),

    this, BIND_AUTO_CREATE); } @Override protected void onStop() { mService = null; unbindService(this); super.onStop(); }
  16. The Application Termination Bug - Start Activity - Start Service

    in foreground - Leave Activity - Remove task - App receives a broadcast - Process killed!
  17. The Application Termination Bug Lesson learned: Shut down your services

    in onTaskRemoved() @Override public void onTaskRemoved(Intent rootIntent) { super.onTaskRemoved(rootIntent); stopForegroundJob(); } Read more at: http://www.doubleencore.com/2014/06/effects- android-application-termination/
  18. UnsatisfiedLinkError Why isn’t my .so file loaded?!? E/AndroidRuntime( 999): Caused

    by: java.lang. UnsatisfiedLinkError: Couldn't load my-native- lib from loader dalvik.system.PathClassLoader [DexPathList[dexElements=[zip file "/data/app/se.hellsoft.appwithnativelib-2.apk"], nativeLibraryDirectories=[/vendor/lib, /system/lib]]]: findLibrary returned null
  19. The x86 JIT bug E/AndroidRuntime(26909): FATAL EXCEPTION: BlurThread E/AndroidRuntime(26909): Process:

    se.hellsoft.x86jitbug, PID: 26909 E/AndroidRuntime(26909): java.lang.ArrayIndexOutOfBoundsException: length=173056; index=176636 E/AndroidRuntime(26909): at se.hellsoft.x86jitbug.StackBlur.transform (StackBlur.java:109) E/AndroidRuntime(26909): at se.hellsoft.x86jitbug.MainActivity. handleMessage(MainActivity.java:57) E/AndroidRuntime(26909): at android.os.Handler.dispatchMessage (Handler.java:98) E/AndroidRuntime(26909): at android.os.Looper.loop(Looper.java:136) E/AndroidRuntime(26909): at android.os.HandlerThread.run (HandlerThread.java:61) W/ActivityManager(25678): Force finishing activity se.hellsoft. x86jitbug/.MainActivity
  20. 64k method limit $ ~/android-sdk-macosx/build-tools/21.0.1/dx --help usage: dx --dex [--debug]

    [--verbose] [--positions=<style>] [--no-locals] [--no-optimize] [--statistics] [--[no-]optimize-list=<file>] [--no-strict] [--keep-classes] [--output=<file>] [--dump-to=<file>] [--dump-width=<n>] [--dump-method=<name>[*]] [--verbose-dump] [--no-files] [--core-library] [--num-threads=<n>] [--incremental] [--force-jumbo] [--multi-dex [--main-dex-list=<file> [--minimal-main-dex]] ...
  21. 64k method limit android { compileSdkVersion 21 buildToolsVersion "21.1.1" defaultConfig

    { applicationId "se.hellsoft.hugeapp" minSdkVersion 15 targetSdkVersion 21 versionCode 1 versionName "1.0" multiDexEnabled = true } ... }
  22. 64k method limit Lesson learned: - Use ProGuard! - Avoid

    really huge libraries (Hello Guava!) - Only use multi-dex as a last resort… - We are eagerly awaiting the Jack & Jill toolchain...
  23. Conclusions... • Android documentation can be vague • Google engineers

    are just like you and me! • Check the (AOSP) source to understand what the platform does (http://androidxref. com) • Submit bug reports and patches! :-) • Code samples available at github. com/ErikHellman/FeatureNotABug