TMI #2: Identifying Android App Installations

In TMI, Michael Helmbrecht gives overly detailed answers to popular mobile development questions.

Ever browsed the web for help, only to find the popular answers leave you hungry for more? In this series, Realm’s very own Michael Helmbrecht takes a stab at giving a more detailed answer to some of the most popular mobile dev questions on the interwebs. For our second episode, Michael explains how to properly uniquely identify Android installations using generated UUIDs, and the downsides of other older methods.


Good morning everyone!

I am Michael Helmbrecht and I am here to help you navigate Stack Overflow issues affecting mobile development. Today we will be delving into the world of Android. We are going to look at a top Stack Overflow question about uniquely identifying app installs: Is there a unique Android device ID?, as raised by Tyler.

Identifying Android App Installations

You might want to use this for any of a million number of reasons, but it is pretty common to want to be able to uniquely identify how many installations you have and tell them apart from each other. This has been the source of a little bit of confusion in the Android community for a while. Here we will set the record straight and tell you how to uniquely identify your apps with Android.

When you are looking to uniquely identify installations of your Android app, you have a few different options, each with their own advantages and disadvantages, including hardware identifiers such as Telephony device IDs, serial numbers, MAC adresses, and Android_ID, in addition to your generated UUIDs.

Hardware Identifiers

The first group are hardware identifiers. A really common one (I have seen lots of places online recommend that you use this), is in the TelephonyManager package. TelephonyManager.getDeviceId() should get you a unique identifier for that device. But there are some issues with this approach:

  1. It is not required on non-phones. Wi-Fi only devices might just not even have this available.
  2. This ID persists across device wipes - if I have this, I wipe my device and give it to a friend, they will be uniquely identified as the same install as me, which you may not want as the developer since they are a whole new user.
  3. It requires READ_PHONE_STATE permission. For example, when someone is downloading your app off the App Store and all you are doing is uniquely identifying their device, it is weird to need a whole permissions set just to be able to identify their install.

Some other hardware identifiers people have suggested are ones where you can get serial numbers, but this actually has the opposite problem of Telephony Manager in that it might not work on phones. Some devices might not have them at all.

A third hardware identifier is the MAC address. Again, some devices might not have Wi-Fi, or might not give you the address with Wi-Fi off. All three of these have issues of reliability where under some configurations that are fairly common, these three things will not work. All these hardware identifiers have problems of reliability and availability in all of these really common configurations (phones, not phones, phones with and without certain capabilities), and the Android team released Android_ID to try to fix this.

ANDROID_ID

Android ID is unique per device and it has the benefit of being unique per profile. In some later versions of Android (i.e. have multiple users per device), it is also supposed to be unique per user. You can really differentiate between people using the same Android device, and it has the benefit of being set one time on the first boot and if you wipe the device then the Android ID is reset. If I give my device to someone else, then they will be counted as a new user for you. Nevertheless, this can have issues as well.

It does not work before Android 2.2 (which thankfully nowadays that is pretty much nobody), but there are some major issues where all Droid2s phones (and others) would return the same ID for every device (9774d56d682e549c). If you are using the Android ID, you may want to check for this particular string and have some sort of fallback when you encounter it.

The Android team themselves wrote a blog post about this a few years ago, and they talked about these issues. They outlined some of the pluses and minuses that they had seen throughout the community. In the end they recommended “do not worry about any of these because, for you and your app, it probably does not really matter if it is a unique hardware, it mostly just matters if it is a unique install.” A really easy way to do that is just to generate a UUID.

UUID

UUIDs can be generated on first run, saved to a file and then checked at any time to uniquely identify installations.

To implement this, we will be working with this little class, called installation (see code below). It has two private variables with sID that starts out as a null. We have this INSTALLATION just to have a unique identifier for the file that we will write.

public class Installation {
  private static String sID = null;
  private static final String INSTALLATION = "INSTALLATION";
}

We will then create a function that will get us back the ID (which will be awesome). If the ID already exists, do not bother creating it, just return it. If the ID does not exist (if it is not already been pulled into memory), we look for a file that uses our constant string. If that file exists, we will just read it and move on. If that file does not exist, then we will generate an ID and write to it.

public class Installation {
  private static String sID = null;
  private static final String INSTALLATION = "INSTALLATION";

  public synchronized static String id(Context context) {
    if (sID == null) { 
      File installation = new File(context.getFilesDir(), INSTALLATION);
      try {
        if (!installation.exists())
          writeInstallationFile(installation);
        sID = readInstallationFile(installation);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
    return sID;
  }
}

Finally, we add reading and writing methods that interact with the installation file. The read method will get us the previously generated UUID, and the write method creates a file output stream. We have the file handle already, all we are going to do is write to it.

...
  private static String readInstallationFile(File installation) throws IOException {
    RandomAccessFile f = new RandomAccessFile(installation, "r");
    byte[] bytes = new byte[(int) f.length()];
    f.readFully(bytes);
    f.close();
    return new String(bytes);
  }

  private static void writeInstallationFile(File installation) throws IOException {
    FileOutputStream out = new FileOutputStream(installation);
    String id = UUID.randomUUID().toString();
    out.write(id.getBytes());
    out.close();
  }
}

We will use the UUID.randomUUID method to generate a random UUID that will tell apart all of our installs. We will of course turn it into a string, and then write it off to our file.

To review, we will back up in our ID method. Since we will have just written that, we will immediately read from it. That way all of our paths use the file as the single source of truth and we do not have things in memory possibly conflicting with our file… and then we will be able to return that. Any time you use this (i.e. the first run through your app ever), it will generate the UUID. After that it will just simply read from the file and then within one session of your app, it will just use the in-memory value.

In this way you can uniquely identify an install without having to worry about all of the potential hardware issues that some of those other identifiers can have. If you do need the device identified uniquely through the hardware for some reason, the Android_ID method has become more stable since it was released. It is advised that, for those legacy problems, you do have some sort of heuristics for that and some fallbacks (i.e. installation class), but these will give you the tools to uniquely identify installs of your Android app. Good luck!

Join Michael in the next TMI to see him tackle more popular mobile development questions.

Additional Resources



This post is licensed under a Creative Commons BY-SA 3.0 license.


Michael Helmbrecht

Michael Helmbrecht

Michael designs and builds things: apps, websites, jigsaw puzzles. He's strongest where disciplines meet, and is excited to bring odd ideas to the table. But mostly he's happy to exchange knowledge and ideas with people. Find him at your local meetup or ice cream shop, and trade puns.