Android 16

Danylo Volokh
Android Development by Danylo :)
11 min readOct 3, 2016

--

In this article I will cover 16 things that I’ve learned during last few years of developing Android applications. I’d like to cover all 100500 things I’ve learned but I want to start from 16.

BTW: If you know what the name of the guy on the picture below it means we share a common interest about Android and anime.

  1. Care about Turkish locale.
  2. Add enum values to the end of enum.
  3. Max size of Bitmap that can be set to ImageView is 4096*4096 px.
  4. Typeface.createFromAsset is leaking memory.
  5. MAT is perfect for finding memory leaks.
  6. Wrong usage of event bus can really bring a lot of problems.
  7. Multiprocess sucks in so many ways.
  8. Doze mode and MediaCodec.
  9. Use a wrapper for the Log class.
  10. RxJava can be great replacement for AsyncTask.
  11. Test your code with “Do not keep activities”.
  12. About code convention.
  13. Care about creating objects.
  14. Use Fabric for analytics.
  15. Hungarian notation is useful for reading patches.
  16. QA team are your friends.

Care about Turkish Locale.

Turkish language has Dotted and dotless I. What does it mean for us?

We know that #perfmatters and in Android we shouldn’t use enums but we still have enums in our applications. And one of the most not obvious bugs happened to us when we were mapping fields from server response to enum values, for example:

enum Surname {    MORNINGSTAR, UNDERWOOD}

When the we are trying to map String to appropriate enum value we should care about locale.

In English locale:

String strSurname = "Morningstar";
Surname surname = Surname.valueOf(strSurname.toUpperCase());

Result — Works fine.

In Turkish Turkish locale:

String strSurname = "Morningstar";
Surname surname = Surname.valueOf(strSurname.toUpperCase());

Result — Crash.

Caused by: java.lang.IllegalArgumentException: MORNİNGSTAR is not a constant in com.myapp.MainActivity$Surname

The solution:

String strSurname = "Morningstar";
Surname surname = Surname.valueOf(
strSurname.toUpperCase(Locale.ENGLISH)
);

When we faced this problem for the second time we enabled lint option in build.gradle which will stop the build if we didn’t handle the case.

lintOptions {
checkReleaseBuilds true
abortOnError true
enable 'DefaultLocale'
check 'DefaultLocale'
error 'DefaultLocale'
}

More about ‘DefaultLocale’ can be read here.

Add enum values to the end of enum.

Very often enum values are stored in the application by it’s ordinal number.

If surnameNumber will be stored somewhere in persistent memory and you will add a new enum value and put it into position before stored ordinal then after update of your application you will fail to restore the correct enum.

Use case:

  1. Assume you have this peace of code somewhere in your app.
enum Surname {MORNINGSTAR, UNDERWOOD}Surname surname = Surname.MORNINGSTAR;
int surnameNumber = surname.ordinal(); // value = 0
// store surname into persistent storage.

2. Add new enum value to the front of your values.

enum Surname {TARGARYEN, MORNINGSTAR, UNDERWOOD}

3. Release this version of the enum to your users. Let them update your application.

4. Check the place where you retrieve stored surnameNumber.

// get a value from storage. value = 0 - > should be "MORNINGSTAR"
int storedSurnameNumber = //...
// create actual enum.
Surname surname = Surname.values()[storedSurnameNumber];
/**
* Actual : surname = TARGARYEN
* Expected : surname = MORNINGSTAR
*/

We’ve almost introduced a critical bug with this mistake. QA team saved us before releasing it.

Max size of Bitmap that can be set to ImageView is 4096*4096 px.

For the last three years I have been working with bitmaps and there is one important peculiarity of ImageView. There is few good justifications of why do we have to scale bitmaps before setting them to ImageView. The main reason for it is that we we scale bitmaps we consume less memory. But there is also one restriction:

W/OpenGLRenderer(10630): Bitmap too large to be uploaded into a texture (4400x640, max=4096x4096)

We cannot set an image to the Image view if we exceeded max size. That’s why it is good to use tools for image processing, they are handle the scaling for you:

Typeface.createFromAsset is leaking memory.

All of us uses Typeface.createFromAsset() to set a custom font in our TextViews. But not everyone knows that it loads font to the memory and this memory is never released.

Typeface t= Typeface.createFromAsset(mContext.getAssets(), fontPath); 
textView.setTypeface(t);

If you use this code in your every TextView you will leak memory as many times as you will use it.

To see this you need to look into Android Memory info file. It can be created by pressing “Memory Usage” in your Android Studio monitor.

Asset Allocations
zip:/data/app/com.myappp.apk:/assets/fonts/Value-Medium.otf: 127K
zip:/data/app/com.myappp.apk:/assets/fonts/Value-Regular.otf: 127K
zip:/data/app/com.myappp.apk:/assets/fonts/Value-Bold.otf: 128K

….

There is also a bug reported here.

The solution:

You need to create a simple cache that loads font only once and then shares this object with all the TextViews which font has to be changed.

class FontCache { // it needs to be Singletonprivate final Hashtable<String, Typeface> mCache = new Hashtable<>();public Typeface get(String fontPath) {
if (!mCache.containsKey(fontPath)) {
Typeface t = Typeface
.createFromAsset(mContext.getAssets(), fontPath);
mCache.put(fontPath, t);
}
return mCache.get(fontPath);
}
}

MAT is perfect for finding memory leaks.

Eclipse memory analyzer (MAT) — one of my favourite tools. Every time I have to deal with memory leaks — it’s the tool I use. Usage is very simple. The best guide for it was published back in 2011.

Google I/O 2011: Memory management for Android Apps

In simple terms:

  1. You reproduce a memory leak.
  2. Manually turn on Garbage collector to clean all the garbage.
  3. Collect heap information from Android Studio via “Dump Java heap for selected client”.
  4. Convert dump file to a file type that can be recognized by MAT via tool that is called “hprof-conv” which is located here: “android_sdk\platform-tools\”
  5. Open the converted file in MAT and use dominator three to find objects that shouldn’t be there.

Here is also a short video provided by Colt:

Android Performance Patterns: Performance Cost of Memory Leaks

Wrong usage of event bus can really bring a lot of problems.

I’ve recently fixed a bug where we were registering/unregistering a Fragment to the EventBus not only in onStart() / onStop() and also registering it in onActivityResult() because of some race conditions. And I’ve found a memory leak that after onActivityResult() we never unregister the EventBus and it holds a reference to the Fragment forever.

EventBus very often causes a lot of problems with comparison of how many problems it solves because:

  1. It’s not an OOP approach. You don’t have direct callbacks and in some cases using EventBus looks like sending a message and hoping that someone is listening to it.
  2. Using global EventBus-es may cause multiple instances of the same class to receive the events if one of the instances is not unsubscribed correctly. For example:

ActivityA has started ActivityB and both of them are using the same fragment. And this fragment is receiving messages from some handler class via EventBus.

If the fragment is unsubscribed from EventBus in onDestroy() and not in onStop() then events will be received in both fragments (one in ActivityA and one in ActivityB).*

*- only if ActivityA wasn’t killed by the OS.

3. Even if you register/unregister in onStart()/onStop() you may have race conditions while using global EventBus. For example:

onStart() of ActivityB is called before onStop() of ActivityA and if you send some events from ActivityB which are also received in ActivityA they may be received in a wrong place.

4. EventBus from Green DAO is hiding crashes if not configured correctly:

Could not dispatch event: class com.myapp.message.PageStateMessage to subscribing class class com.myapp.fragments.PagerFragment
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean com.myapp.ui.Page.getState()' on a null object reference
at com.myapp.fragments.PagerFragment.onEventMainThread(VideoFragment.java:3054)

BTW: I used MAT to find the mentioned memory leaks.

EventBus pattern may cause a lot of confusions and unexpected bugs. In general it is recommended to use local EventBus whenever is possible.

Multiprocess sucks in so many ways.

The main problems (in context of Android) with multiprocess are :

  1. You cannot use SharedPreferences in a normal way. Each process has its own SharedPreferences.
  2. You cannot use Singleton pattern.

I’ve been working a lot with multiprocess applications and trust me — it’s not something you want to deal with.

Each process is having it’s own SharedPreferences and even static members are different for different processes. This way the main problem is to have a good multiprocess storage.

Here are some libraries that DOES NOT HANDLE multiprocessing:

Realm — cool library but with many bugs.

Tray — creators of the library claim that it is multiprocess but on some devices there is an error with ContentProvider -it cannot be acquired for some reason.

The only reliable approach we’ve found is to use SQLite database for multiprocess storage and manage it by our own.

There are some advantages of using Multiprocess but it should probably covered in a different article.

Doze mode and MediaCodec

As you know Doze was introduced in Marshmallow. And we have handled entering Doze mode in our application. In our application we use MediaCoded for encoding and decoding and it turned our that this is the place that has to be handled when device enters “Idle state” (Doze mode).

When we get and index of buffer from MediaCodec we might get a negative value which means that we need to do some additional action:

switch(inputBufferIndex) {
case MediaCodec.INFO_TRY_AGAIN_LATER:
break;
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
break;
}

So, when device enters Doze mode the MediaCodec always returns MediaCodec.INFO_TRY_AGAIN_LATER and if you don’t handle that you may end in a situation with infinite retries. That is what happened with us so we have to add an additional check if we are in Doze, and if we are then break the loop.

public boolean isInIdleState() {
return Build.VERSION.SDK_INT >= 23 &&
((PowerManager) mContext.getSystemService(Context.POWER_SERVICE))

.isDeviceIdleMode();
}

Use a wrapper for the Log class.

In my projects I use class Logger that is usual wrapper of Log class, but it has at least one big advantage over the regular Log class: it adds a thread id to the beginning of the string which makes a lot easier to debug multithread applications:

class Logger {
public int v(String TAG, String message) {
return Log.v(TAG, attachThreadId(message));
}
private static String attachThreadId(String str){
return Thread.currentThread().getId() + " " + str;
}
// ...other methods here
}

Of course LogCat already gives us the ability to know if we are in “Main thread”:

09–22 16:44:00.816 18386–18386/com.myapp V/log in UI thread
09–22 16:44:01.081 18386–19297/com.myapp V/log in other thread

If first number (PID) equals to second number (TID) — UI thread.

If not — background thread.

I find more convenient to have a number in front of the log.

09–22 16:44:00.816 18386–18386/com.myapp V/ 1 log in UI thread
09–22 16:44:01.081 18386–19297/com.myapp V/ 1472 log in other thread

RxJava can be great replacement for AsyncTask.

AsyncTask became obsolete with introduction of RxJava. RxJava is everywhere, tons of libraries are based on Rx.

It’s less verbose to use it and it gives you the ability to customize async operations whenever you want with usage of different operators.

Simple example:

I need to do some heavy operation with Bitmap. I could do it with AsyncTask but I’ve simply used Rx.

class HandleBitmapObservable implements Observable.OnSubscribe<Bitmap>{    private Bitmap mBitmap;    HanldeBitmapObservable(Bitmap bitmap){
mBitmap = bitmap;
}
@Override
public void call(Subscriber<? super Bitmap> subscriber) {

// .. do some heavy operations on mBitmap here
// deliver the result of your work
subscriber.onNext(mBitmap);

// notify when you're done
subscriber.onCompleted();
// prevent potential memory leaks
mBitmap = null;
}
public static Observable<Bitmap> handleBitmap(Bitmap bitmap){ return Observable.create(new HandleBitmapObservable(bitmap))

// Here you can choose in which thread the operation has
// to be done. You're not limited to a single thread. You can use
// ThreadExecutor. I use thread provided by "Schedulers.computation()"
.subscribeOn(Schedulers.computation())// Here you can choose in which thread the result will be delivered .observeOn(AndroidSchedulers.mainThread());
}
}

Usage of this class is very simple:

Subscription s = HanldeBitmapObservable.handleBitmap(bitmap)
.subscribe(
new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
// use handled bitmap here
}
});
// do not forget to unsubscribe in onStop to prevent memory leaks
@Override
public void onStop(){
super.onStop();
s.unsubscribe();
}

Test your code with “Do not keep activities”.

If you are an Android Developer and you don’t know what is a “Do not keep activities” (DNKA) you definitely need to go over the Developer options on your devices.

This flag helped us in so many cases. It simulates the Android behaviour when there is not enough memory and the activities that are not visible are killed by the OS. This situation even changes the lifecycle of your activity during receiving result from Activity you have started previously.

Here are some lifecycle callbacks and how they are called in relation to the state of DNKA flag in Developers options while receiving “Activity result”:

With DNKA:

  • onCreate
  • onStart
  • onActivityResult

Without DNKA:

  • onActivityResult
  • onStart

You’d be surprised of how many applications from Google Play you can force to crash just by browsing their screens when DNKA is turned on.

About code convention.

On our project we have a lot of legacy code that was written with our custom code convention. During last three years we had 9 permanent members that has already left the project. Instead of explaining code convention every time new member comes to the project we have decided to use standard code code convention kindly provided by Google.

Code Style for Contributors

Care about creating objects.

As it usually is the main advantage of something can be also main disadvantage. I think that “Memory architecture” of Java is that kind of thing.

We don’t need to care about memory in most of the cases. Garbage collector will do everything for us. But in Android we need to care about memory a lot.

At some point we had this Architecture that was creating tons of objects — this caused huge problems for us.

There are libraries that demands creating a lot of objects when you use them. Realm is a good example. If something demands from us to create a lot of new objects we think twice before adding it to our project.

We also had huge performance issues with logging. We have a lot of logs and when we checked how many objects we are creating we were shocked.

Partial solution:

We added proguard rules and everyone became happy. Remove debug and verbose logs.

proguar-rules.pro file:

# Logging
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int d(...);
}

build.gradle file:

release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
debuggable false
}

proguard-android-optimize.txt — it the key for this to work. If you don’t use this proguard file the usage of Log.d() and Log.v() will not be removed. But be carfull — some of the optimizations might harm your application. You will need good regression testing after this.

Use Fabric for analytics.

Fabric is a free tool for crash reporting and analytics. In analytics you can find some cool info like:

  • Daily active users
  • Monthly active users
  • Daily new users
  • Crash-free users (one of my favourite)
  • Usage by devices
  • Usage by OS versions

It’s also easy to use for reporting unexpected situations.

For example:

Assume there is a situation that should not happen in production but it will be wrong not to check the input values in this place. So you check if values are correct and if they are not you fallback gracefully and report to Fabric that “unexpected situation” has happened.

There are also other similar tools but I find this one very convenient and easy to integrate.

Hungarian notation is useful for reading patches.

If you’re doing code review on your project you’ve noticed that reading patches might be very confusing because you don’t always know the context of where in the code the changes has been made.

The Hungarian notation is very helpful here. It is easy to tell which types (static, non-static etc..) are these fields even without an IDE:

SPEED_OF_LIGHT;
spaceShipName;
sLaunchPlatform;
mSpaceShipSpeed;

There also a lot of other advantages for using it: https://en.wikipedia.org/wiki/Hungarian_notation

QA team are your friends.

When I just started my career I didn’t have a good relationship with the QA team. I was arguing about some bugs they had logged, telling them: “It’s not a bug!”. And then I realized that the best approach is to tell: “Log it please and we will check”. In this case they feel appreciated for their job. Now, I cannot imagine a work on production without a QA. Who will cover my back if I screwed up? Having a QA is like a backup, you still have a good opportunity they will find your bugs instead of your users in production. Appreciate your QA and be grateful for the bugs they find for you to fix.

Actually, this is only 1% of the things I’d like to share. Maybe some day i will share the rest.

Cheers :)

--

--