Hey Guys,
Finally I got my official blog at www.onlydevtwice.com
From now on, I will publish my blog post both in Thai and English in my new site.
See you there.
:-)
Johnny Dev : Johnny Dew's Dev Blog
Thursday, March 20, 2014
Wednesday, November 13, 2013
Unit testing Android Handler using Robolectric
I was looking for ways to do unit testing my Android code. Since Android JUnit need to be executed in emulator or real device, I want another way to run my tests outside these environments.
Then I use actual Java JUnit and Robolectric to test my code, but the problem of this approach is some code that is need to be ran inside Android environment cannot actually running while testing.
In my case, I use Handler from background Thread to sends Message back to caller Thread. I want to test that the callback method is actually executed on caller Thread but the Message is never be sent in testing because of the test environment.
Here is snippet of my TargetClass code.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Then I use actual Java JUnit and Robolectric to test my code, but the problem of this approach is some code that is need to be ran inside Android environment cannot actually running while testing.
In my case, I use Handler from background Thread to sends Message back to caller Thread. I want to test that the callback method is actually executed on caller Thread but the Message is never be sent in testing because of the test environment.
Here is snippet of my TargetClass code.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void process(TargetClassListener listener){
//1 grab caller thread callback.
mListener = listener;
mCallbackHandler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
if(mListener != null){
mListener.onSuccess();
}
}
};
//2 start processing in worker thread.
HandlerThread ht = new HandlerThread("",android.os.Process.THREAD_PRIORITY_BACKGROUND){
@Override
public void run(){
//1 do something.
//2 notify onSuccess().
Message msg = mCallbackHandler.obtainMessage();
msg.what = TargetClassListener.CALLBACK_ON_SUCCESS;
msg.sendToTarget();
}
};
ht.start();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
From the code above, the background Thread (HandlerThread ht) will send Message to mCallbackHandler which will force caller Thread to execute handleMessage() and execute mListener.onSuccess().
While test is running, the callback method onSuccess() won't be executed because the Message cannot be sent in testing environment.
So, instead of validate test result in onSuccess() (which cannot be reached here), I change to validate Handler object.
Here is snippet from my test code.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
At the third step, I get the Handler object from target class and wrap it inside Robolectric's ShadowHandler. Then validate the Message that the handler contains and the Thread id that associated with its Looper.
If these test cases are passed, then I can guarantee that the TargetClass will execute the right callback on the right Thread.
It's something, isn't it? :-)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
From the code above, the background Thread (HandlerThread ht) will send Message to mCallbackHandler which will force caller Thread to execute handleMessage() and execute mListener.onSuccess().
While test is running, the callback method onSuccess() won't be executed because the Message cannot be sent in testing environment.
So, instead of validate test result in onSuccess() (which cannot be reached here), I change to validate Handler object.
Here is snippet from my test code.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Test
public void process_normalCase_willExecuteCallbackOnCallerThread(){
//1 execute class under test.
TargetClass tc = new TargetClass();
tc.process(this);
//2 wait for background thread to be done.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail(e.toString());
}
//3 validate handler object.
assertNotNull(tc.getCallbackHandler());
ShadowHandler handler = (ShadowHandler) Robolectric.shadowOf(tc.getCallbackHandler());
assertTrue(handler.hasMessages(CALLBACK_ON_SUCCESS));
assertEquals(Thread.currentThread().getId(), handler.getLooper().getThread().getId());
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
At the third step, I get the Handler object from target class and wrap it inside Robolectric's ShadowHandler. Then validate the Message that the handler contains and the Thread id that associated with its Looper.
If these test cases are passed, then I can guarantee that the TargetClass will execute the right callback on the right Thread.
It's something, isn't it? :-)
Sunday, June 9, 2013
Take a screenshot in Raspbian Wheezy
To take a screenshot in LXDE, you will need the application ImageMagick. You can download ImageMagick using apt tool.
sudo apt-get install imagemagick
Once ImageMagic is installed, you can take a screenshot using import command.
import -window root screen.png
You can also combine this command with the delay command to make a delay time before take a screenshot.
sleep 5; import -window root screen.png
This will be delayed for 5 seconds then take a screenshot. Combine sleep command is very handy since you might need to hide the terminal window before take a shot.
Reference: http://stackoverflow.com/questions/10581953/how-do-i-take-a-screenshot-on-a-raspberry-pi-running-debian-squeeze-and-lxde
sudo apt-get install imagemagick
Once ImageMagic is installed, you can take a screenshot using import command.
import -window root screen.png
You can also combine this command with the delay command to make a delay time before take a screenshot.
sleep 5; import -window root screen.png
This will be delayed for 5 seconds then take a screenshot. Combine sleep command is very handy since you might need to hide the terminal window before take a shot.
Reference: http://stackoverflow.com/questions/10581953/how-do-i-take-a-screenshot-on-a-raspberry-pi-running-debian-squeeze-and-lxde
Friday, June 7, 2013
Raspbian Wheezy keyboard mapping problem
I found my Raspbian Wheezy got a wrong keyboard mapping. When I type '@' and '#' symbol, it turns out to be other characters.
After searching in the internet (using DuckDuckGo in Raspbian :-D) I found this thread http://raspberrypi.stackexchange.com/questions/1042/why-is-my-symbol-not-working
So the problem is default keyboard layout is UK and I have to change it to US by change its configuration file using nano.
sudo nano /etc/default/keyboard
change XKBLAYOUT value from "gb" to "us"
then restart the PI
sudo /sbin/reboot
after my PI is wake up again, keyboard layout is changed to US and now problem solved.
After searching in the internet (using DuckDuckGo in Raspbian :-D) I found this thread http://raspberrypi.stackexchange.com/questions/1042/why-is-my-symbol-not-working
So the problem is default keyboard layout is UK and I have to change it to US by change its configuration file using nano.
sudo nano /etc/default/keyboard
change XKBLAYOUT value from "gb" to "us"
then restart the PI
sudo /sbin/reboot
after my PI is wake up again, keyboard layout is changed to US and now problem solved.
Thursday, August 23, 2012
[Android] Which alternative image resource will be chosen by the system?
This is a note from my experiments.
Let's say I've supplied images only in
drawable-xhdpi and drawable-hdpi folders and now my app is running on device
with mdpi screen density. My question is which folder will be chosen by Android
system to scale an image to display in mdpi device. Below is a note from my
experiments about how Android find the best matching image resource.
Drawable folders structure
drawable-xhdpi (density = 320)
drawable-hdpi (density = 240)
drawable-mdpi (density = 160)
drawable-ldpi (density = 120)
CAUTION: Screen density qualifiers are not supported in
Android 1.5 and lower.
Code snippet
BitmapFactory.Options options = new
BitmapFactory.Options();
options.inJustDecodeBounds = true;
Log.v(TAG, "Options value
before decode Bitmap");
Log.v(TAG,
String.format("inDensity %d, inScaled %b, inScreenDensity %d,
inTargetDensity %d", options.inDensity, options.inScaled,
options.inScreenDensity, options.inTargetDensity));
BitmapFactory.decodeResource(getResources(),
R.drawable.my_image, options);
Log.v(TAG, "Options value after
decode Bitmap");
Log.v(TAG,
String.format("inDensity %d, inScaled %b, inScreenDensity %d,
inTargetDensity %d", options.inDensity, options.inScaled,
options.inScreenDensity, options.inTargetDensity));
Experiment #1 Does the system
scales image to matches screen density?
This experiment try to answers the question
"Does the system chooses image from another folder if there is no image for
current device screen density?"
A.
Supply
only image in xhdpi folder.
Log result
o On
xhdpi device
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 320
Meaning
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 320
Meaning
System
chooses image from xhdpi folder (inDensity = 320) for xhdpi density
(inTargetDensity = 320)
o On
hdpi device
inDensity
320, inScaled true, inScreenDensity 0, inTargetDensity 240
Meaning
System
chooses image from xhdpi folder (inDensity = 320) and scales it to current hdpi
density (inTargetDensity = 240)
Conclusion from experiment #1
System scales image to matches current screen
density.
Experiment #2 Does the system
chooses bitmap from the most closely matches drawable folder?
This experiment try to answers the question
"Does the system choose image from drawable folder that most closely
matches device screen density?"
A.
Supply
image in xhdpi and hdpi
Log result
o On
mdpi device
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 160
Meaning
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 160
Meaning
System
chooses image from hdpi folder (inDensity = 240) which is the most
closely matches current mdpi density (inTargetDensity = 160)
o On
ldpi device
inDensity
240, inScaled true, inScreenDensity 0, inTargetDensity 120
Meaning
System
chooses image from hdpi folder (inDensity = 240) which is the most
closely matches current ldpi density (inTargetDensity = 120) than xhdpi.
B.
Supply
image in hdpi and mdpi
Log result
Log result
o
On xhdpi device
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 320
Meaning
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 320
Meaning
System
chooses image from hdpi folder (inDensity = 240) which is the most
closely matches current xhdpi density (inTargetDensity = 320) than mdpi.
o On
ldpi device
inDensity 160, inScaled true, inScreenDensity 0, inTargetDensity 120
Meaning
System chooses image from mdpi folder (inDensity = 160) which is the most closely matches current ldpi density (inTargetDensity = 120) than hdpi.
inDensity 160, inScaled true, inScreenDensity 0, inTargetDensity 120
Meaning
System chooses image from mdpi folder (inDensity = 160) which is the most closely matches current ldpi density (inTargetDensity = 120) than hdpi.
C.
Supply
image in mdpi and ldpi
Log result
o On
xhdpi device
inDensity
160, inScaled true, inScreenDensity 0, inTargetDensity 320
Meaning
Meaning
System
chooses image from mdpi folder (inDensity = 160) which is the most
closely matches current xhdpi density (inTargetDensity = 320) than ldpi.
o On
hdpi device
inDensity
160, inScaled true, inScreenDensity 0, inTargetDensity 240
Meaning
System
chooses image from mdpi folder (inDensity = 160) which is the most
closely matches current hdpi density (inTargetDensity = 240) than ldpi.
D.
Supply
image in xhdpi and ldpi
Log result
o On
hdpi device
inDensity
320, inScaled true, inScreenDensity 0, inTargetDensity 240
Meaning
System
chooses image from xhdpi folder (inDensity = 320) which is the most
closely matches current hdpi density (inTargetDensity = 240) than ldpi
o On
mdpi device
inDensity
320, inScaled true, inScreenDensity 0, inTargetDensity 160
Meaning
System
chooses image from xhdpi folder (inDensity = 320) which is not the most
closely matches current mdpi density (inTargetDensity = 160).
In this case, the chosen image should be the image in ldpi folder (density = 120) which is the most closely matches mdpi but system picks an image from xhdpi folder. This could be the effect from scaling factor which describes in Android document that system prefer to scales down the image.
In this case, the chosen image should be the image in ldpi folder (density = 120) which is the most closely matches mdpi but system picks an image from xhdpi folder. This could be the effect from scaling factor which describes in Android document that system prefer to scales down the image.
Conclusion from experiment #2
·
System is not
always choose the image from the most closely matches folder. In the experiment
#2-D mdpi device case, system chooses to scale down the image from xhdpi folder
instead of scale up the image from ldpi folder which is the most closely
matches. This could be the effect from the scaling factor which scale down the
image can produces better image quality than scale it up. We will try
experiment more about scaling factor in the experiment #3.
·
Another interesting
case is experiment #2-B ldpi device case. The system chooses image from mdpi
folder and scale it down. This should not be a problem since mdpi is the most
closely matches but, according to this Android
document which describes this case that system would
prefer to scale down the hdpi image instead of mdpi one because of scaling hdpi
to ldpi by a factor of 0.5 has fewer artifacts compared to scaling mdpi to ldpi
by a factor of 0.75. I'm not sure why my experiment result is conflicted with
the one in Android document perhaps I'm running my test case on Android
emulator (Froyo) or something else?
Experiment #3 Which one to be
chosen? Scale up or scale down?
This experiment try to find the answer in the
situation that the system cannot find an image for the current screen density
but the images exist in drawable folders which are next to the current device
screen density folder e.g. current
screen density is hdpi but the images are stored in drawable-xhdpi and drawable-mdpi,
system will scale down an image from xhdpi folder or scale up an image from
mdpi folder?
A.
Supply
image in xhdpi and ldpi (experiment #2-D)
Log result
o On
hdpi device
inDensity
320, inScaled true, inScreenDensity 0, inTargetDensity 240
Meaning
System
scales down the image from xhdpi folder (inDensity = 320) to current
hdpi density (inTargetDensity = 240).
o On
mdpi device
inDensity
320, inScaled true, inScreenDensity 0, inTargetDensity 160
Meaning
System
scales down the image from xhdpi folder (inDensity = 320) to current
mdpi density (inTargetDensity = 160).
B.
Supply
image in xhdpi and mdpi
Log result
o On
hdpi device
inDensity
320, inScaled true, inScreenDensity 0, inTargetDensity 240
Meaning
System
scales down the image from xhdpi folder (inDensity = 320) to current
hdpi density (inTargetDensity = 240).
o On
ldpi device
inDensity
160, inScaled true, inScreenDensity 0, inTargetDensity 120
Meaning
Meaning
System
scales down the image from mdpi folder (inDensity = 160) to current ldpi
density (inTargetDensity = 160) which is also the most closely matches.
C.
Supply
image in hdpi and ldpi
Log result
o On
mdpi device
inDensity
240, inScaled true, inScreenDensity 0, inTargetDensity 160
Meaning
Meaning
System
scales down image from hdpi folder (inDensity = 240) to current mdpi
density (inTargetDensity = 160).
o On
xhdpi device
inDensity
240, inScaled true, inScreenDensity 0, inTargetDensity 320
Meaning
System scales up image from hdpi folder (inDensity = 240) to current xhdpi density (inTargetDensity = 320) which is also the most closely matches.
Meaning
System scales up image from hdpi folder (inDensity = 240) to current xhdpi density (inTargetDensity = 320) which is also the most closely matches.
Conclusion from experiment #3
System prefer to scales down the image.
Experiment #4 Three image
candidates.
The goal of this experiment is to determine which
image will be chosen by the system if all folders has an image but the current
device density folder.
A.
Supply
image in xhdpi, hdpi and mdpi
Log result on ldpi device
inDensity
160, inScaled true, inScreenDensity 0, inTargetDensity 120
Meaning
System
scales down image from mdpi folder (inDensity = 160) which is most
closely matches current ldpi density (inTargetDensity = 120).
B.
Supply
image in xhdpi, hdpi and ldpi
Log result
inDensity
240, inScaled true, inScreenDensity 0, inTargetDensity 160
Meaning
System
scales down image from hdpi folder (inDensity = 240) to current mdpi
density (inTargetDensity = 160).
C.
Supply
image in xhdpi, mdpi and ldpi.
Log result
inDensity
320, inScaled true, inScreenDensity 0, inTargetDensity 240
Meaning
System
scales down image from xhdpi folder (inDensity = 320) to current hdpi
density (inTargetDensity = 240).
D.
Supply
image in hdpi, mdpi and ldpi.
Log result
inDensity
240, inScaled true, inScreenDensity 0, inTargetDensity 320
Meaning
System
scales up image from hdpi folder (inDensity = 240) which is most closely
matches current xhdpi density (inTargetDensity = 320).
Conclusion from experiment #3
System prefer to scales down from the larger
image in the most closely matches folder or scales up from the smaller image in
the most closely matches folder if there are no image in higher resolution.
Conclusion
From all experiment results, below is the behavior
of the Android system when finding best matching image.
Precondition
The desired image does not exist in the drawable
folder of the current device screen density.
1.
System chooses the
higher resolution image which is most closely matches the current device screen
density.
2.
If no higher
resolution image exist then chooses the lower resolution image which is most
closely matches the current device screen density.
Subscribe to:
Posts (Atom)