During the session, I'll explore lot of useful tricks I use during my everyday life as Android developer. Testing, background tasks, smartphone and tablet optimization, avoid memory leaks, UI view management, good external libraries to take into account, mobile design patterns and some best practices I found.
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Android Survival Guide - Two years of software development
1. Who I am
Born in the past millenium and still
an happy software developer
Mobile addicted
Android enthusiastic
Free software fan
Proud rider
… and Tiramisu' lover
rainbowbreeze
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 1
2. Overview
Online learnig resources
Paintless rotation with threads
Easy IoC/DI framework
Testing, from unit to automation
Honeycomb and backward compatibility
Libraries and frameworks
Q&A session
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 2
3. Chocolate Interaction model
Don't be scare: interact, collaborate, share your fun!
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 3
5. Useful online material - ListView
The world of ListView
http://www.google.com/events/io/2010/sessions/world-of-listview-android.html
Lazy-loading of remote thumbnails
http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html
Drag and Drop capable ListView
https://github.com/commonsguy/cwac-touchlist
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 5
6. Background thread, progress bar and rotation
Your application have to be smooth, don't use UI
thread for long operation (> 200ms)
Yuo cannot update UI Thread from external threads
Use Activity.runOnUiThread(Runnable),
View.post(Runnable), View.postDelayed(Runnable,
long), Handler to bypass the problem
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 6
7. Background thread, progress bar and rotation
public void onClick(View v) {
//background thread
new Thread(new Runnable() {
public void run() {
final Bitmap b = loadImageFromNetwork();
//update ui thread
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(b);
}
});
}
}).start();
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 7
8. Background thread, progress bar and rotation
The simplest way
AsyncTask
Can access to UI Thread from separate thread
http://android-developers.blogspot.com/2009/05/painless-threading.html
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 8
10. Background thread, progress bar and rotation
But if the screen is rotated while the backgroud
thread is still in execution?
UI not updated, dialogs disappear,
NullPointerException, memory leaks...
OMG!
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 10
11. Background thread, progress bar and rotation
The clean way
- Define an Handler inside the Activity that process thread
tasks (update/result)
- Create background thread passing the handler
- Call the handler from the thread when a progress must be
published or the thread finish
- Update activity reference inside thread using OnPause()
and OnResume()
- Pay attention to OnStart and other Activity lifecycle events
http://code.google.com/p/rainbowlibs/source/browse/android/trunk/rainbowlibs/src/it/rainb
owbreeze/libs/logic/RainbowBaseBackgroundThread.java
http://code.google.com/p/rainbowlibs/source/browse/android/trunk/rainbowlibs/src/it/rainb
owbreeze/libs/ui/RainbowSplashScreenActivity.java
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 11
12. Background thread, progress bar and rotation
public class WebcamActivity extends Activity() {
private Handler mActivityHandler = new Handler() {
public void handleMessage(Message msg) {
Log.debug("Message from external thread" + msg.what);
switch (msg.what) {
case RotationThread.OPERATION_STARTS:
//create progress dialog + other UI operations
break;
case RotationThread.OPERATION_COMPLETED:
//remove the dialog + additional logic
break;
case RotationThread.OPERATION_ERROR:
//remove the dialog + additional error logic
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 12
13. Background thread, progress bar and rotation
public class WebcamActivity extends Activity() {
private RotatingThread mRotatingThread;
private void showWebcam() {
//show a progress dialog
showDialog(DIALOG_PREPARE_FOR_FULLSCREEN);
mRotatingThread = new RotatingThread(
mActivityHandler,
webcamToShowId);
mRotatingThread.start();
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 13
14. Background thread, progress bar and rotation
public class RotatingThread() extends Thread {
private WeakReference<Handler> mCallerHandler;
public RotatingThread(Handler handler, int webcamId) {
registerCallerHandler(handler);
…
}
public void registerCallerHandler(Handler newHandler {
mCallerHandler =
new WeakReference<Handler>(newHandler);
}
public void unregisterCallerHandler()
{ mCallerHandler = null; }
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 14
15. Background thread, progress bar and rotation
public class RotatingThread() extends Thread {
...
public void run() {
callHandlerAndRetry(OPERATION_STARTS);
//your long operation here
callHandlerAndRetry(OPERATION_COMPLETED);
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 15
16. Background thread, progress bar and rotation
protected void callHandlerAndRetry(int messageCode) {
for (int retries = 0; retries < TOTAL_RETRIES; retries++) {
if (null != mCallerHandler && null != mCallerHandler.get()) {
Message message =
mCallerHandler.get().obtainMessage(messageCode);
message.arg1 = arg1;
mCallerHandler.get().sendMessage(message);
break;
}
try { //what some times, maybe next time activity is ready
Thread.sleep(INTERVAL_BETWEEN_RETRIES);
} catch (InterruptedException ignoreExcepition) {}
}
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 16
17. Background thread, progress bar and rotation
public class WebcamActivity extends Activity() {
@Override
protected void onPause() {
mRotatingThread.unregisterCallerHandler();
super.onPause();
}
@Override
protected void onResume() {
mRotatingThread.registerCallerHandler(mActivityHandler);
super.onResume();
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 17
18. Background thread, progress bar and rotation
public class WebcamActivity extends Activity() {
@Override
public Object onRetainNonConfigurationInstance() {
return mRotatingThread;
}
@Override
protected void onStart() {
super.onStart();
mRotatingThread =
(RotatinThread)getLastNonConfigurationInstance();
//nothing saved, first run of the activity
if (null == mRotatingThread) showWebcam();
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 18
19. Background thread, progress bar and rotation
The short (but no totally complete) way
Use a Fragment and setRetainInstance(true)
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/
app/FragmentRetainInstance.html
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 19
20. Lazy loading singleton
Singleton Pattern
Ensure that only one instance of a class is created and provide a
global point of access to the object.
Create your own singleton or extend
android.app.Application and modify its onCreate()
method.
Base class for those who need to maintain global application state.
You can provide your own implementation by specifying its name in
your AndroidManifest.xml’s <application> tag, which will cause
that class to be instantiated for you when the process for your
application/package is created.
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 20
21. Lazy loading singleton
public class App extends Application {
public static int myGlobalStaticValue;
public int myGlobalValue;
@Override
public void onCreate() {
super.onCreate();
myGlobalStaticValue = 10;
myGlobalValue = 20;
}
<application android:name="it.rainbowbreeze.singleton.App">
int value = App.myGlobalStaticValue;
int value = ((App)context.getApplication()).myGlobalValue;
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 21
22. Lazy loading singleton
Drawbacks
Singleton is a pattern or an anti-pattern?
http://stackoverflow.com/questions/3826905/singletons-vs-application-context-in-android
OS may kill your application process, including the Application
subclass instance. As a result the state is lost. When you later
return to the application, then the OS will restore its activity
stack and Application subclass instance, but its fields will be null.
If you really need singleton, at least use lazy-
loading singleton and avoid static fields to store
application lifecycle data
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 22
23. Lazy loading singleton
public class MySingleton() {
private MySingleton mInstance;
public synchronized MySingleton getInstance(Context c) {
if (null == mInstance) {
mInstance = new MySingleton(context);
}
return mInstance;
}
private MySingleton(Context appContext) {
//initializes the object
//reload SharedPreferences data
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 23
24. Inversion of Control / Dependency Injection
Dependency Injection Pattern
The object does not need to know in advance about how the
other part of the system works. Instead, the programmer
provides (injects) the relevant system component in advance
along with a contract that it will behave in a certain way.
http://en.wikipedia.org/wiki/Dependency_injection
Reduce the coupling among software components:
better code testability, useful in libraries, clean
code.
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 24
25. Inversion of Control / Dependency Injection
public class ItemDao implements ItemDao {
public ItemDao(Context appContext) {
//performs initialization
}
}
public DataManager implements IDataManager {
INetManager mNetManager;
IItemDao mItemDao;
public DataManager(INetManager netMngr, IItemDao itemDao)
{
mNetManager = netMngr;
mItemDao = itemDao;
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 25
26. Inversion of Control / Dependency Injection
Drawbacks
Complex and less readable code
Chain of dependencies
More code to write
Solutions
Ioc/DI frameworks manage all the boring things!
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 26
27. Inversion of Control / Dependency Injection
public class AppEnv() {
public static AppEnv i(Context appContext) {
…
mInstance = new AppEnv(appContext);
return mInstance;
}
private AppEnv(Context appContext) {
mItemDao = new ItemDao(appContext);
mDataManager = new DataManager(mNetMngr, mItemDao);
}
ItemsDao mItemDao;
public ItemsDao getItemDao(){
return mItemDao;
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 27
28. Inversion of Control / Dependency Injection
Drawbacks
Cannot inject mock for testiong purposes
Solutions
Separate the object factory from the singleton
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 28
29. Inversion of Control / Dependency Injection
public class ObjFactory {
public ItemDao createDao(Context appContext) {
return new ItemDao(appContext);
}
public DataManager createDataManager(
INetManager netMngr, ItemDao itemDao) {
return new DataManager(netManager, itemDao);
}
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 29
30. Inversion of Control / Dependency Injection
public class AppEnv() {
private static ObjFactory mObjFactory = new ObjFactory();
public static AppEnv i(Context c) {
…
mInstance = new AppEnv(c, mObjFactory);
return mInstance;
}
public static AppEnv i(Context c, ObjFactory customFactory) {
…
mInstance = new AppEnv(c, customFactory);
return mInstance;
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 30
31. Inversion of Control / Dependency Injection
public class AppEnv() {
…
private AppEnv(Context appContext, ObjFactory objFactory) {
mDao = objFactory.createDao(appContext);
//... other initialization
mDataManager = objFactory.createDataManager(
mNetMngr, mDao);
}
private ItemsDao mDao;
public ItemsDao getItemDao(){
return mDao;
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 31
32. Inversion of Control / Dependency Injection
--- inside your activity / component ---
AppEnv appEnv = AppEnv.i(getContext());
ItemDao dao = appEnv.getItemDao();
--- inside your test class ---
MockObjFactory mockFactory = new MockObjFactory() {
@Overrides
public ItemDao createDao(Context appContext) {
return new MockItemDao(appContext);
}
};
AppEnv appEnv = AppEnv.i(getContext(), mockFactory);
ItemDao dao = appEnv.getItemDao();
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 32
33. Inversion of Control / Dependency Injection
RoboGuice
Like Google Guice, but for Android
http://code.google.com/p/roboguice/
http://stackoverflow.com/questions/5067681/guice-performance-on-android
Other solutions
http://stackoverflow.com/questions/1029696/the-hunt-for-the-j2me-friendly-ioc-container-is-on
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 33
34. Testing - basic
Software testing = reduce risks of software
implementation
Lot of support Android side
http://developer.android.com/guide/topics/testing/testing_android.html
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 34
35. Testing - first steps
Create new “Android Test Project”
Create a class that extends TestCase
Code first test
Red, Green, Refactor!
http://developer.android.com/resources/tutorials/testing/helloandroid_test.html
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 35
36. Testing - first steps
public class MyFirstTest extends TextCase() {
Private MyStringConverter mConverter;
public void setUp() {
mConverter = new MyStringConverter();
}
public testUpperCaseConversion() {
String result = mConverter.toUppercase(“ota2011”);
assertEquals(“Wrong value”, “OTA2011”, result);
}
}
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 36
37. Testing with special Android Mock
AndroidTestCase
access to a context
http://developer.android.com/reference/android/test/AndroidTestCase.html
ApplicationTestCase
test app lifecycle, inject mock context
http://developer.android.com/reference/android/test/ApplicationTestCase.html
Activity Testing API
test activity lifecycle, inject mocks, send key or touch events, etc
http://developer.android.com/resources/tutorials/testing/activity_test.html
http://developer.android.com/guide/topics/testing/activity_testing.html
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 37
38. Testing with special Android Mock
public class SpinnerActivityTest extends
ActivityInstrumentationTestCase2<SpinnerActivity> {
protected void setUp() throws Exception {
…
mActivity = getActivity();
mSpinner = (Spinner) mActivity.findViewById(R.id.Spinner01);
…
}
public void testSpinnerUI() {
this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
mPos = mSpinner.getSelectedItemPosition();
mSelection = (String)mSpinner.getItemAtPosition(mPos);
assertEquals(resultText, mSelection);
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 38
39. Testing with special Android Mock
ProviderTestCase2
test contentProvicer with isolated context
http://developer.android.com/guide/topics/testing/contentprovider_testing.html
ServiceTestCase
test the service lifecycle, not the logic (detached)
http://developer.android.com/guide/topics/testing/service_testing.html
ViewAsserts, MoreAsserts
extended asserts
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 39
40. Test automation with monkey
adb shell monkey -p your.package.name -v 500
sends sequences of random events
http://developer.android.com/guide/developing/tools/monkey.html
monkeyrunner
A Python program that runs an application, sends keystrokes to it,
takes screenshots of its user interface, and stores them
http://developer.android.com/guide/developing/tools/monkeyrunner_concepts.html
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 40
41. Testing with external tools
Robotium
Automatic black-box test cases for Android applications, with
gesture and actions
http://code.google.com/p/robotium/
Robolectric
Fast and easy TDD, runs tests in normal JavaVM with mock
Android classes, no need to deploy on
devicehttp://pivotal.github.com/robolectric/
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 41
42. Honeycomb and Backward Compatibility
Nowadays Android has two separate branches, but
Ice Cream Sandwich will merge them
Avoid different versions of same app and prefer one adaptive
Use alternative resources
AndroidManifest.xml filters for sdk, screen, hw
Put some dirty if in code
http://code.google.com/p/iosched/
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 42
43. Honeycomb and Backward Compatibility
Resources
res/layout/main_activity.xml # For phones
res/layout-xlarge/main_activity.xml # Large screen
res/layout-sw600dp/main_activity.xml # For 7” tablets
res/layout-sw720dp/main_activity.xml # For 10” tablets
… and much more!
http://android-developers.blogspot.com/2011/07/new-tools-for-managing-screen-
sizes.html
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 43
46. Testing with special Android Mock
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = null;
if (!fragmentsSupported)
intent = new Intent(this, MainNonFragmentActivity.class);
else
intent = new Intent(this, MainFragmentActivity.class);
startActivity(intent);
finish();
}
}
http://blog.radioactiveyak.com/2011/02/strategies-for-honeycomb-and-backwards.html
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 46
47. Honeycomb and Backward Compatibility
Android Compatibility Package
Backport some 3.x APIs to 1.6 and 2.x
http://developer.android.com/sdk/compatibility-library.html
Support for Fragment, Loader, ViewPager
Native code on 3.x, library code on 2.x
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 47
48. Honeycomb and Backward Compatibility
Fragment
http://android-developers.blogspot.com/2011/03/fragments-for-all.html
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 48
49. Honeycomb and Backward Compatibility
setRetainInstance(true)
activity is rotated but fragment is not destroyed and
recreated, lifecycle events are called.
Finally!
Bug: http://code.google.com/p/android/issues/detail?id=17423
Bug: http://stackoverflow.com/questions/6250580/fragment-already-added-
illegalstateexception
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 49
50. Honeycomb Backward Compatibility
ActionBarSherlock
Library that uses native code for 3.x or its own code for 2.x
http://actionbarsherlock.com/
GreenDroid
Brand new actionbar implementation
http://android.cyrilmottier.com/?p=274
Android Survival Guide
Alfredo Morresi (http://www.rainbowbreeze.it) Slide 50