1 of 48

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 of 48

wiley.com/go/ptl/androidprogramming

3 of 48

http://xkcd.com/979/

4 of 48

WARM UP!

5 of 48

Activity.onSaveInstanceState()

6 of 48

Activity.onSaveInstanceState()

7 of 48

Activity.onSaveInstanceState()

  • Pressing Home
  • Calling finish()
  • Pressing back
  • Rotating device
  • Starting new Activity

8 of 48

Activity.onSaveInstanceState()

  • Pressing Home
  • Calling finish()
  • Pressing back
  • Rotating device
  • Starting new Activity

9 of 48

Activity.onSaveInstanceState()

Lesson learned:

Don’t use onSaveInstanceState() for anything but View related stuff!

10 of 48

App Widgets

11 of 48

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]);

...

}

12 of 48

App Widgets + ContentProvider

ContentProvider

13 of 48

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);

...

}

14 of 48

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);

}

}

15 of 48

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()

16 of 48

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);

...

}

17 of 48

Lesson learned:

Understand what calling identity is and how the Binder calls work.

18 of 48

When is the first time your BroadcastReceiver can be called?

19 of 48

Cheap auto-start without permission? ;)

<receiver android:name="se.hellsoft.badreceiver.MyReciever"

android:enabled="true"

android:exported="true">

<intent-filter>

<action android:name="android.intent.action.USER_PRESENT" />

</intent-filter>

</receiver>

20 of 48

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);

}

21 of 48

Lesson learned:

App must have been started once before a registered receiver will be triggered (for certain Intents)!

22 of 48

Bound Service

How does Service.onBind() work?

23 of 48

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.”

24 of 48

Service.onBind()

25 of 48

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()!

26 of 48

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! :)

27 of 48

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;

}

28 of 48

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();

}

29 of 48

ServiceConnection

30 of 48

ServiceConnection

31 of 48

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();

}

32 of 48

ServiceConnection

Lesson learned:

Read the JavaDoc properly.

Again. :)

33 of 48

The Application Termination Bug

I/ActivityManager﹕ Killing 1433:se.hellsoft.demo/u0a61 (adj 0): remove task

34 of 48

The Application Termination Bug

  • Start Activity
  • Start Service in foreground
  • Leave Activity
  • Remove task
  • App receives a broadcast
  • Process killed!

35 of 48

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/

36 of 48

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

37 of 48

UnsatisfiedLinkError

38 of 48

UnsatisfiedLinkError

Lesson learned:

Steal Copy the fallback loader (LibraryLoaderHelper.java) from the Chromium project!

Source: http://goo.gl/9XRCSK

39 of 48

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

40 of 48

The x86 JIT bug

Lesson learned:

  • android:vmSafeMode=”true”
  • Use RenderScript instead!

41 of 48

64k method limit

“64k should be enough for anyone!”

42 of 48

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]]

...

43 of 48

64k method limit

44 of 48

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

}

...

}

45 of 48

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...

46 of 48

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

47 of 48

Thank you for listening!

Any questions? :)

48 of 48

www.spotify.com/us/jobs/

@erikhellman

google.com/+ErikHellman

Join the band.