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
wiley.com/go/ptl/androidprogramming
http://xkcd.com/979/
WARM UP!
Activity.onSaveInstanceState()
Activity.onSaveInstanceState()
Activity.onSaveInstanceState()
Activity.onSaveInstanceState()
✓
✓
✗
✓
✗
Activity.onSaveInstanceState()
Lesson learned:
Don’t use onSaveInstanceState() for anything but View related stuff!
App Widgets
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]);
...
}
App Widgets + ContentProvider
ContentProvider
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);
...
}
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);
}
}
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()
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);
...
}
Lesson learned:
Understand what calling identity is and how the Binder calls work.
When is the first time your BroadcastReceiver can be called?
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>
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);
}
Lesson learned:
App must have been started once before a registered receiver will be triggered (for certain Intents)!
Bound Service
How does Service.onBind() work?
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.”
Service.onBind()
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()!
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! :)
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;
}
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();
}
ServiceConnection
ServiceConnection
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();
}
ServiceConnection
Lesson learned:
Read the JavaDoc properly.
Again. :)
The Application Termination Bug
I/ActivityManager﹕ Killing 1433:se.hellsoft.demo/u0a61 (adj 0): remove task
The Application Termination Bug
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/
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
UnsatisfiedLinkError
UnsatisfiedLinkError
Lesson learned:
Steal Copy the fallback loader (LibraryLoaderHelper.java) from the Chromium project!
Source: http://goo.gl/9XRCSK
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
The x86 JIT bug
Lesson learned:
64k method limit
“64k should be enough for anyone!”
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]]
...
64k method limit
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
}
...
}
64k method limit
Lesson learned:
Conclusions...
Thank you for listening!
Any questions? :)
www.spotify.com/us/jobs/
@erikhellman
google.com/+ErikHellman
Join the band.