Laurel Laurel - 6 months ago 9
Android Question

Why is the emulator less random than my device?

I created a small app (as a project, with no plans to release it). Part of the code generates a grid of random numbers and and displays a grid using the sprite from a list with said number. It behaved as expected, at least on my Kindle Fire (and another device I tried it on).

The emulator, however, is a different story. I think the emulated device was a generic Samsung (4, maybe). The same code, when run on the emulator, fills about half the grid with one sprite, and half with a different sprite.

The grid might look like this on the emulator:

11177
11177
11777
11777
11777


instead of this on real devices:

64251
26253
87635
38415
28167


The relevant part of my code (yes, I should move
new Random()
elsewhere):

import java.util.Random;

// ... ... ...

for (int i = 0; i < GRID; i++) {
for (int j = 0; j < GRID; j++) {
paint.setColor(Color.parseColor("#FBB117"));
Random rand=new Random();
int num=rand.nextInt(8);
canvas.drawBitmap(bmp,frames[num],calcGridSpot(i,j),
paint);

//... Eventually closing braces:
}
}


I have had issues in the past, in Java, with
Random
deciding to be less random (I believe it might be due to optimizations).

Why is the emulator behaving less randomly? (And how do I fix it?)

Answer

I suspect your "yes, I should move new Random() elsewhere" remark is more accurate than you think.

You can find the source for Android's java.util.Random class; note the kitkat and lollipop/marshmallow versions are different.

kitkat:

public Random() {
    // Note: Using identityHashCode() to be hermetic wrt subclasses.
    setSeed(System.currentTimeMillis() + System.identityHashCode(this));
}

marshmallow:

public Random() {
    // Note: Don't use identityHashCode(this) since that causes the monitor to
    // get inflated when we synchronize.
    setSeed(System.nanoTime() + seedBase);
    ++seedBase;
}

Note the change from millisecond-granularity time to nanosecond-granularity time. However, this change is only relevant if the emulated device actually has a clock that granular. Some emulators may have very coarse clocks, which means that System.nanoTime() could be returning the same value on every iteration of a reasonably fast loop.

You're creating new random number generators each loop, seeding them with N, N+1, N+2, and so on. Because the seeds are nearly identical, most of the bits in the first number output are identical.

If you pull the "new Random" out of the loop, not only will it be more efficient, it will allow the pseudo-random number generator to progress through its full sequence and give you better results.