Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Friday, December 20, 2013

Android Compilation Error: aapt.exe has stopped working

An Android project references a third-party library. Everything worked fine untill the third-party library updated to a new version, then we got work fine, but got an "aapt.exe has stopped working" error during the compilation time:

Checked the log records and noticed the appt stops at /res files packaging, and followed by an error of "aapt error. Pre Compiler Build aborted." It turned out that /res/values/ids.xml in the third-party library caused the problem:

<resources>
  <item type="id" name="quit"/>
  ...
</resources>

Took a closer look and found those ids actually were not used by any other resources. After deleting the ids.xml file the compilation passed.

Saturday, February 16, 2013

Android Thread Handling in Configuration Change

The Activity Recreating Issue During Configuration Change

When configuration change occurs in an Android device, e.g. rotating the screen from landscape to portrait mode, the Activity will be destroyed and recreated. This could introduce some issues if some tasks inside Activity are not completed during the configuration change. For instance a worker thread may still be running in background and leaking the memory during the configuration change. We assume that the worker thread here in discussion ties to Activity and will communicate back to Activity instance when it completes its task.

Let's take a look at following Activity code:

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private static int instanceCount = 0;
    private Handler handler;
    private Thread thread;
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        instanceCount++;
        Log.d(TAG, "onCreate()");

        textView = (TextView)findViewById(R.id.textView1);
        textView.setText("Activity Instance " + String.valueOf(instanceCount));
            
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Log.d(TAG, "Handler thread - " + getThreadInfo());
            }
        };

        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "Worker thread - " + getThreadInfo());
                try {
                    int count = 10;
                    while(count-- > 0) { // pause 10 seconds
                        Thread.sleep(1000); 
                    }
                    Log.d(TAG, "Worker thread sendMmessage to handler");
                    handler.sendEmptyMessage(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }    
    
    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
    }

    private static String getThreadInfo()
    {
        Thread currentThread = Thread.currentThread();
        String info = String.format("%1$s ID: %2$d Priority: %3$s",  
                currentThread.getName(), currentThread.getId(), currentThread.getPriority());
        return info;
    }
}

A separate thread sleeps for 10 seconds to simulate a long-run task, then updates a UI view by a handler. If the the screen is rotated within 10 seconds, the activity will be recreated, so as a new thread and a new handler. However the old thread is still running in background, consuming resource and leaking the memory. The old Activity object will not be garbage collected at the time of destroy since the handler and thread are referencing it. The view switches from "Activity Instance 1" to "Activity Instance 2", and the LogCat shows:

Disabling Dangling Thread

The easiest method to resolve the issue is set a flag when the activity is destroyed to control the stale thread:

public class MainActivity extends Activity {
    private boolean stopThread = false;
    //...
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    //...
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "Worker thread - " + getThreadInfo());
                try {
                    int count = 10;
                    while(count-- > 0 && !stopThread) { // pause 10 seconds
                        Thread.sleep(1000); 
                    }
                    if (!stopThread) {
                        Log.d(TAG, "Worker thread sendMmessage to handler");
                        handler.sendEmptyMessage(0);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        }
    }
    
    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
        stopThread = true;
        handler.removeCallbacksAndMessages(null);
    }
    
    //...
 }
The LogCat logs:

Now the first worker thread is cancelled along with its partially completed task. To save the work by first thread, we can use onSaveInstanceState() callback to store the partial result, so later the second worker thread can use it as an initial start point, as described in this post.

Using Static Thread Object

The solution above is not perfect: multiple thread instances created during configuration change which is inefficient and expensive. We can use static thread variable to maintain one thread instance:

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private static int instanceCount = 0;
    private static WorkerThread thread;
    private TextView textView;
    
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "Handler thread - " + getThreadInfo());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        instanceCount++;
        Log.d(TAG, "onCreate()");

        textView = (TextView)findViewById(R.id.textView1);
        textView.setText("Activity Instance " + String.valueOf(instanceCount));
        
        if (savedInstanceState != null && thread != null && thread.isAlive()) {
            thread.setHandler(handler);
        } else {
            thread = new WorkerThread(handler);
            thread.start();
        }
    }    
    
    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
        if (thread.isAlive()) {
            thread.setHandler(null);
        }
    }

    private static String getThreadInfo()
    {
        Thread currentThread = Thread.currentThread();
        String info = String.format("%1$s ID: %2$d Priority: %3$s",  
                currentThread.getName(), currentThread.getId(), currentThread.getPriority());
        return info;
    }
    
    private static class WorkerThread extends Thread {
        private Handler handler;

        public WorkerThread(Handler handler) {
            super();
            this.handler = handler;
        }

        public void setHandler(Handler handler) {
            this.handler = handler;
        }

        @Override
        public void run() {
            Log.d(TAG, "Worker thread - " + getThreadInfo());
            try {
                int count = 10;
                while (count-- > 0) { // pause 10 seconds
                    Thread.sleep(1000);
                }
                if (handler != null) {
                    Log.d(TAG, "Worker thread sendMmessage to handler");
                    handler.sendEmptyMessage(0);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Notice the extended WorkerThread class is also static to avoid memory leak, as in Java non-static inner and anonymous classes will implicitly hold an reference to their outer class. Now the LogCat logs:

As a side note, be cautious to use static variables within Activity to avoid memory leak. If you have to use the static variables, do not forget to cleanup the resources/references in the Activity.onDestroy() callback.

Using Fragment to Retain Thread

Another option, also the recommended way from Android Developer Guild, is to use Fragment with RetainInstance set to true to retain one instance of thread. The worker thread is wrapped into the non-UI Fragment:

public class ThreadFragment extends Fragment {
      private static final String TAG = ThreadFragment.class.getSimpleName();
      private Handler handler;
      private Thread thread;
      private boolean stopThread;

      public ThreadFragment(Handler handler) {
          this.handler = handler;
      }
      
      public void setHandler(Handler handler) {
          this.handler = handler;
      }
      
      @Override
      public void onCreate(Bundle savedInstanceState) { 
        Log.d(TAG, "onCreate()");
        super.onCreate(savedInstanceState);

        setRetainInstance(true); // retain one Fragment instance in configuration change
        
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "Worker thread - " + MainActivity.getThreadInfo());
                try {
                    int count = 10;
                    while(count-- > 0 && !stopThread) { // pause 10 seconds
                        Thread.sleep(1000); 
                    }
                    if (handler != null) {
                        Log.d(TAG, "Worker thread sendMmessage to handler");
                        handler.sendEmptyMessage(0);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
      }

      @Override
      public void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
        handler = null;
        stopThread = true;
      }
 }

The Fragment feature was added from Android 3.0 Honeycomb. For older versions you need to include the Android Support package (android-support-v4.jar) to get the Fragment work. With Fragment setup, the main activity will dynamically create or activate existence of ThreadFragment:

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private static int instanceCount = 0;
    private ThreadFragment fragment;
    private TextView textView;
    
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "Handler thread - " + getThreadInfo());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        instanceCount++;
        Log.d(TAG, "onCreate()");

        textView = (TextView)findViewById(R.id.textView1);
        textView.setText("Activity Instance " + String.valueOf(instanceCount));
        
        FragmentManager fm = getFragmentManager();
        fragment = (ThreadFragment) fm.findFragmentByTag("thread");

        if (fragment == null) {
            fragment = new ThreadFragment(handler);
            fm.beginTransaction().add(fragment, "thread").commit();
        } else { // retained across configuration changes
            fragment.setHandler(handler);
        }
    }    
    
    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();

        fragment.setHandler(handler);
        handler.removeCallbacksAndMessages(null);
    }

    public static String getThreadInfo()
    {
        Thread currentThread = Thread.currentThread();
        String info = String.format("%1$s ID: %2$d Priority: %3$s",  
                currentThread.getName(), currentThread.getId(), currentThread.getPriority());
        return info;
    }
}

The LogCat result:

Thread Safety

The code snippets demoed about are not thread-safe. To make the code thread-safe, we can set the variable as volatile and wrap the setting inside a synchronized method so that only one thread updates the values at any time:

public class ThreadFragment extends Fragment {
    //...
    private volatile Handler handler;
    private volatile boolean stopThread;  
    //...
    
    public void setHandler(Handler handler) {
        synchronized( this.handler ) {
            this.handler = handler;
        }
        if (handler == null){
            requestStop();
        }
    }      

    public synchronized void requestStop() {
        stopThread = true;
    }
    
    thread = new Thread(new Runnable() {
        @Override
        public void run() {
           //...
           synchronized (handler) {
                if (handler != null) {
                    //...
                    handler.sendEmptyMessage(0);
                }
            }
        }
    }
      
    @Override
    public void onDestroy() {
        //...
        requestStop();
        handler = null;
    }
    
    //...
}

Above code avoids the scenarios like the work thread goes into the block after checking handler is not null, but right at that moment the handler is set to null by the main thread. However I am not so sure if such implementation is necessary. Unlike server side services that may be invoked by multiple callers at the same time, Android apps run locally so this kind of race conditions would rarely occur.

Saturday, December 08, 2012

Notes About Android Threading

  • Main thread, or UI thread, starts Android application, processes UI rendering and handles user interactions. Do NOT execute any long-running or heavy tasks inside main thread otherwise the UI would appear lagging and not responsive.
  • A Looper holds a message queue and implements infinite loop. It takes tasks one by one from the queue and executes them in sequence. When message queue is empty, the Looper is blocking and waiting for processing next queued task. A thread can associate with a Looper (only one to one relationship), then it becomes a Looper thread being able to process messages and tasks continuously, vs. regular thread that will die when completes its job in the run() method.
  • Handler is a bridge between Looper message queue and thread(s). Current thread or other threads can push runnable jobs to a Looper message queue by method handler.post() and its scheduled variance (postDelayed, postAtTime, etc.), or send a message to the queue by handler.sendMessage() method and its scheduled variance (sendMessageDelayed, sendMessageAtTime, etc.). Handler can also process the message by the Handler's handleMessage(Message) callback.
  • You can setup a thread with a Looper manually. Androdi also provides a handy class called HandlerThread for starting a new thread that already has a Looper attached. Not sure why it's not called LooperThread. It's a bit confusing as there's no Handler in a HandlerThread object. You always need to create Handler for a HandlerThread:
        HandlerThread  handlerThread = new HandlerThread("Thread name");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());
    
  • Main thread is a Looper thread. All UI interactions are pushed to main thread's Looper message queue and then are processed one by one. Configuration change request, such as orientation rotation, is just a special type of message sent to main thread's message queue.
  • Only main thread can update UI elements safely. Worker thread or background thread can use following means to work on UI elements:
    • Implement main thread Handler, then you can update UI inside handler.handleMessage() callback, and post UI-related task to handler.Post() method or their variance.
    • Use View.Post(Runnable). Android View objects have tied to a default Handler inside UI thread so you can post UI-related runnable jobs to it directly.
    • Use Activity.runOnUiThread(Runnable) method. Android Activity class has this runOnUiThread helper method to update the UI.
    • Use AsyncTask. The AsyncTask implementation takes the advantage of Thread pooling concept and provides a simple, understandable interface. Simply run background task inside the doInbackground() method, and run UI-related work inside the onPreExecute(), onProgressUpdate() and onPostExcute() methods. But be aware of the performance penalty of using it, see next.
  • AsyncTask thread has low priority of Process.THREAD_PRIORITY_BACKGROUND. With such low priority the total CPU consumption of all AysncTasks together is less than 10 percent of overall CPU power. This may cause some performance issue. You can change this behavior by changing its priority:
        new AsyncTaskClass().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 
        //...
        class AsyncTaskClass extends AsyncTask<...> {
            //...
            protected Void doInBackground() {
                Thread.currentThread().setPriority(Process.THREAD_PRIORITY_DEFAULT);
                //...
            }
        }
    
  • On the other hand the HandlerThread uses Process.THREAD_PRIORITY_DEFAULT priority by default, but you can specify HandlerThread's priority from its constructor, e.g. setting a low priority background:
    HandlerThread bgThread = new HandlerThread("Background thread", Process.THREAD_PRIORITY_BACKGROUND);
    
  • The ExecutorService is more powerful and can manage a pool of threads. Go for it if you want to have full control of your threads.
  • Usually worker thread is created from Activity. But it can also run in Service context (thread object created inside Service) without UI connection. Note that Service still runs in main UI thread by default if it starts from the Activity, but Service is not bound to Activity's life-cycle. IntentService simply extends Service and implements an internal HandlerThread object, so you can guarantee the job passed to IntentService's onHandleIntent callback runs in a separate worker thread.
  • There're a few ways to deal with recurring or repeating tasks. One common approach is keep posting to Activity's handler by using Handler.postDelayed() inside the runnable task:
        int recurInSeconds = 5;
        Handler handler = new Handler();
        handler.postDelayed(runnable, 0); // start the task
        Runnable runnable = new Runnable() {
           @Override
           public void run() {
              doTask(); 
              handler.postDelayed(this, recurInSeconds * 1000); // repeat the task
           }
        };
    Without Activity context we can use following methods to achieve the goal:
    • AlarmManager.setRepeating()/setInexactRepeating() to repeatedly start a Broadcast/Service in which the job is handled. For longer sleep intervals this is the preferred mechanism. It's handled by system and is only triggered at the time arrives thus consuming less power.
    • ScheduledThreadPoolExecutor.scheduleWithFixedDelay()/scheduleWithFixedDelay() to repeatedly run a task.
      ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
      executor.scheduleAtFixedRate(runnable, 0, recurInSeconds, TimeUnit.SECONDS);
    • Use Timer.scheduleAtFixedRate() to do the recurring TimerTask. This is not the recommended way described from Android development guild.
  • Anders Göransson's Efficient Android Threading slides for DroidCon are very informative. It's the best reference on the topic of Android threading I have found so far.

Sunday, November 18, 2012

Toggle Android Toast Message Manually

Android Toast shows a popup notification message for a short time then automatically fades out. Following code example will show a Toast when you touch the phone scree:

public class MainActivity extends Activity implements OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.mainLayout).setOnClickListener(this);
    }
    
    @Override
    public void onClick(View v) {
        Toast.makeText(this, "Toast Message", Toast.LENGTH_LONG).show();
    }
}

From the latest Toast implementation source code I noticed that the Toast message is controlled by INotificationManager:

    /**
     * Show the view for the specified duration.
     */
    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
We can see the message text is set to a TN object. TN is an inner class that actually shows or hides the Toast message:
     private static class TN extends ITransientNotification.Stub {
          TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
        }

        /**
         * schedule handleShow into the right thread
         */
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

        /**
         * schedule handleHide into the right thread
         */
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.post(mHide);
        }
Use reflection we can access the internal TN object, bypass the INotificationManager and show/hide the Toast message manually. Instead of fading out automatically, the result is a permanent Toast message on the screen.

Following code illustrates how to toggle Toast message manually where the Toast Message shows up when you touch the screen, and the message will stay on the screen unless you touch the screen again:

public class MainActivity extends Activity implements OnClickListener {
    private boolean isToastShowing = false;
    private Toast toast;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.mainLayout).setOnClickListener(this);
    }
    
    @Override
    public void onClick(View v) {
        //Toast.makeText(this, "Toast Message", Toast.LENGTH_LONG).show();
        if (toast == null) {
            toast = Toast.makeText(this, "Toast Message", Toast.LENGTH_LONG);
        }
        Field mTNField;
        try {
            mTNField = toast.getClass().getDeclaredField("mTN");
            mTNField.setAccessible(true);
            Object obj = mTNField.get(toast);
            Method method;
            if (isToastShowing) {
                method = obj.getClass().getDeclaredMethod("hide", null);
                isToastShowing = false;
            } else {
                method = obj.getClass().getDeclaredMethod("show", null);
                isToastShowing = true;
            }
            method.invoke(obj, null);
        } catch (Exception e) {
            e.printStackTrace();
        }        
    }
}

Monday, November 05, 2012

Android Service Life Cycle

Unlike Activity Android Service doesn't have a UI for user interaction, it runs in background to perform long-running actions. A Service can be initiated from Activity, AlarmManager, BroacastReceiver, or other Service. In addition, a Service can run in the same process of caller (local Service) or run in different process (remote Service). Started Services are started by startService(), and bound Services are started by bindSerivce() method, as described in Android Developer Guides. The Service life-cycle has two branches for the started Service and the bound Service:

In this post I will do some testing on how Android Service's created by Activities, and examine its clife-cycle. For simplicity the examples are the Local Service, and there's no logic to stop the Service specifically so we can see how a Service instance survives when the Activity that created it is killed. The tests are conducted in Geingerbread 2.3 and latest Jelly Bean 4.1 and they show the exact same results.

Test Service and UI

First we define a dummy TestService for our test where each callback invocation is logged:

public class TestService extends Service {
    private static final String TAG = TestService.class.getSimpleName();
    private final IBinder binder = new ServiceBinder();
    
    @Override
    public IBinder onBind(Intent arg0) {
        Log.d(TAG, "onBind()");
        return binder;
    }
    
    @Override
    public void onRebind(Intent intent) {
        Log.d(TAG, "onRebind()");
        super.onRebind(intent);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "onUnbind()");
        return super.onUnbind(intent);
    }
    
    public class ServiceBinder extends Binder {
        private final String TAGB = ServiceBinder.class.getSimpleName();
        public TestService getService() {
            Log.d(TAGB, "getService()");
            return TestService.this;
        }
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate()");
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand()");
        return super.onStartCommand(intent, flags, startId);
    }
}

We also have two Activities to do the test. The main Activity has two buttons that could start a TestService or navigate to SecondActivity. The SecondActivity only has one button to start the TestService.

Activity UI:

Activity XML definition:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:id="@+id/mainLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    <Button
        android:id="@+id/btnGoActivty"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Go to SecondActivity" 
        android:onClick="startSecondActivity" />  
     
    <Button
        android:id="@+id/btnStartService"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start TestService"
        android:onClick="startTestService" /> 
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <Button
        android:id="@+id/btnStartService"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start TestService" /> 
        
</LinearLayout>

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidtest"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="11" />

    <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" 
        android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity" android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SecondActivity"></activity>
        <service android:name=".TestService" ></service>
    </application>

</manifest>

Started Service

MainActivity code:

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate()");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
    }

    public void startSecondActivity(View v) {
        Log.d(TAG, "starting ServiceActivity");
        Intent activtyIntent = new Intent(MainActivity.this, SecondActivity.class);
        startActivity(activtyIntent);
    }

    public void startTestService(View v) {
        Log.d(TAG, "starting TestService");
        Intent serviceIntent = new Intent(MainActivity.this, TestService.class);
        startService(serviceIntent);
    }
}
SecondActivity:
public class SecondActivity extends Activity {
    private static final String TAG = SecondActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        Button btnStartService = (Button) findViewById(R.id.btnStartService);
        btnStartService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "starting TestService");
                Intent serviceIntent = new Intent(SecondActivity.this, TestService.class);
                startService(serviceIntent);
            }
        });
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy()");
        super.onDestroy();
    }
}

On the MainActivity I click "Go to SecondActivity" button, then click "Start TestService" to start the dummy service. The LogCat shows:

The TestService.onCreate() and TestService.onStartCommand() call backs are invoked when the service is started. Next click Android Back button to navigate back to MainActivity:

We can see the TestService instance remains when the SecondActivity is destroyed which initiated the Service. This is because the Service is running in background and is not tied to Activity's life-cycle.

Inside MainActivity click "Start TestService" button, the TestServcie.onStartCommand() is called but not the onCreate() callback:

If we click the Back button again the MainActivity is destroyed and the Android Home page shows up, as there's no other Activity instance in the Task stack:

Apparently the TestService is still running even though the MainActivity is gone. Unless the hosting process is killed the Service will remain running in background forever. The only exception is that Android runtime may terminate the service in low resources situations. Such runtime termination can happen in background Activities also.

By default the local Service is running in the same UI thread, and Service and Activity can communicate each other directly. A new thread is required to run the heavy long-running job inside Service to avoid blocking the main thread. Once the Service finishes its work, it can send the result back to Activity through main thread's handler. If Service is running in a different process, Broadcast mechanism could be used to do the cross-process communication.

To stop a started Service just call the stopService() method. It will stop the Service if it's running, otherwise nothing happens. Note that the number of calls to startService() is not counted, and stopService() will simply stop the Service no matter how many times the Service has been started.

Bound Service

Bound services are started by the bindService() method call. Activity or other component wishing to bind to a Service needs to have a ServiceConnection object containing onServiceConnected() and onServiceDisconnected() methods which will be called once the Service connection has been established or disconnected respectively:

    private TestService testService;
    ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "Service disconnected");
            testService = null;
        }
          
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "Service connected");
            testService = ((ServiceBinder)service).getService();
            // TestService methods are reachable via testService instance
        }
    };

With the ServiceConnection created, MainActivity and SecondActivity can bind the TestService directly:

    public void startTestService(View v) {
        Log.d(TAG, "starting TestService");
        Intent serviceIntent = new Intent(MainActivity.this, TestService.class);
        bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
    }

Then we redo the experiment again: MainActivity => SecondActivity => Start Service (bindService) => Start Service (binService) => Back to MainActivity => Start Service (bindService) => click Back button to destroy MainActivity. The LogCat records:

We can see that the TestService.onCreate() is called, then the onBind() callback when TestService is bound, but subsequent bind requests do not trigger TestService.onBind(). Another significant difference comparing with started Service is that the TestService is terminated, and TestService.unbind() and TestService.onDestroy() are invoked when the bound Activity object is destroyed.

We change the action a little bit: MainActivity => Start Service (bindService) => SecondActivity => Start Service (bindService) => Start Service (bindService) => Back to MainActivity => Start Service (bindService) => Back button to destroy MainActivity:

This time the TestService is not stopped when SecondActivity is destroyed because the TestService object still has bound reference in MainActivity. Once the MainActivity is destroyed the TestService is also terminated as there's no bound reference for TestService anymore. However, if Service is already started using startService() before the bindService() call, then the Service will remain when all Activity instances are destroyed as we discussed above in started Service section. We change the MainActivity code to use startService and the result becomes:

Once the Service is bound, an IBinder object is returned to the requester, so the Activity would be able to access the Service data directly in local Service secnaros. In remote Service situations where the bound service is not running in the same process as the Activity, the recommended interaction would be the Messenger and Handler implementation.

To stop bound Services, we can invoke the unbindService() method call. As stopService(), the bound Service will be stopped on request of unbindService() invocation if started, no matter how many times bindService() are called, otherwise nothing will happen.

IntentService

IntentService is a special implementation of started Service. Internally IntentService implements a HandlerThread to process the long-running task. A simple IntentService test code:

public class TestIntentService extends IntentService {
    private static final String TAG = TestIntentService.class.getSimpleName();
    
    public TestIntentService() {
        super(TAG);
        Log.d(TAG, "Constructor()");
    }

    @Override
    protected void onHandleIntent(Intent arg0) {
        Log.d(TAG, "onHandleIntent() start...");
        try {
            Thread.sleep(5000); // pause 5 seconds
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d(TAG, "onHandleIntent() completed");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate()");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy()");
    }
}

We change the Service initialization code in MainActivity and SecondActivity:

    public void startTestService(View v) {
        Log.d(TAG, "starting TestIntentService");
        Intent serviceIntent = new Intent(MainActivity.this, TestIntentService.class);
        startService(serviceIntent);
    }

Run the application with following actions: MainActivity => Go to SecondActivity => Start TestIntentService (wait for more than 5 seconds) => Start TestIntentService (less than 5 seconds) => Back to MainActivity => Back button to destroy MainActivity. The logs:

Unlike started Service IntentService terminates when completes its work. Also the task in IntentService.onHandleIntent() remains running even the Activity started it is gone. This is because the task inside onHandleIntent() callback is running in a separate thread.

If we click "Start TestIntentService" a few times quickly in MainActivity:

It clearly shows that the tasks are processed in sequence, not running in parallel. This is because the tasks are pushed to IntentService's message queue, and its internal handler will execute the task one by one.

Conclusion.

  • The started Service is not bound to Activity life-cycle except the above case. Service could run in background and consuming resources even all Activity instances are closed
  • Service.onStartCommand() is guaranteed to be called for each startService() call, and Service.onCreate() is only called once for the first time the Service object is created.
  • Local bound Service could be related to Activity's life-cycle if it's not started when the bindService() is invoked, i.e. the bound Service instance will be destroyed when the Activity instance is gone.
  • Local bound Service will not be tied to Activity's life-cycle if the Service has started before the bindService() is invoked, i.e. the Service will continue to run in this case.
  • Both Service.onCreate() and Service.onBind() are invoked once for the first bindService() call, all subsequent bindService() calls won't result in any Service callback.
  • IntentService.onHandleIntent() runs in a separate thread, and processes tasks one by one in sequence. Once all tasks are completed the IntentService instance is destroyed. It's not tied to Activity life-cycle either.

Saturday, October 13, 2012

Android Activity Life Cycle

Android apps with UI interaction are composed of one or more Activity. It's crucial to understand Activity life cycle just as ASP.NET developers have to know how the Page object is constructed. Following image from Android developer guide shows the overall flow:

A few notes on events for different user actions:

  1. Hit home/power button: onPause() => onStop(). Stop state means in Activity in background, and it's no longer visible on the screen. From documentation Android OS could kill the activity process (onDestroy() will be called) when memory reaches a low point, but I can't effectively reproduce that in a physical device.
  2. From 1 back to activity: onRestart() => onStart() => onResume(). This is quite straightforward. If the low-memory-kill-background-process happened, a new Activity instance will be recreated from scratch.
  3. Hit back button: onPause() => onStop() => onDestroy(). The previous Activity will show up and current activity is destroyed and gone. The app will close if no previous activity exists in navigation history.
  4. Rotate the screen from landscape to portrait or vise verse: onPause() => onStop() => onDestroy() => onCreate() => onStart() => onResume(). The whole activity is destroyed and recreated. That's very different from Windows 8/Windows phone 8 implementation where that only triggers an event and the instance of the page or class remains. Android considers screen rotation, language change, storage plug/unplug, etc. as "runtime configuration change". The Activity will be reloaded by default when configuration is changed, so a new layout xml file as well as other related resources can be reconstructed properly. You can skip this behavior by overriding onConfigurationChanged method to handle the situation by your own, but such approach is not recommended from Android developer guide.

Another interesting thing is the state management, two scenarios in particular: 1. background Activity is destroyed due to low memory and then is back to active Activty; 2. foreground Activity is destroyed and rebuilt because of the screen rotation. Android provides onSaveInstanceState method for you to save your data, which is executed after the onPause(0 and before the onStop() callback handlers, then you can restore the data in onCreate() method:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        // check if saved state data exists 
        if (savedInstanceState != null) {
            int prevSelectedNumber = savedInstanceState.getInt("SELECTEDNUMBER");
            // do UI logic based on prevSelectedNumber
        }
    }
 
    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
 
        // save instance state data
        savedInstanceState.putInt("SELECTEDNUMBER", 123);
    }

Note that onSaveInstanceState() method is only called when the Activity is destroyed by Android OS internally like the orientation change discussed above, but it will not be called if the Activity is killed by the user action such as turning off the device or pressing the back button to go to previous Activity. In some cases, for example the address/contact input form, it would be great to keep unsaved and uncompleted data to avoid user retyping when the user is going back and forth to different screens, then the data should be saved during onPause() callback.

On top of Activities there're a set of rules defining how an Activity instance is created and behaves in one or multiple applications, e.g. a browser Activity declared as singleTask launch mode will result in only one instance running inside one Task cross the whole Android environment. The details are well described in this Android Developer Guide.

Monday, September 24, 2012

Android Experience

Nowadays electronic gadgets are so pervasive. I have iPhone, iPad, multiple laptops and desktops at home. Recently I got a Nexus 7 tablet, my first Android device. What really impressed me is its easiness of development process.

Apple products are cool, no doubt about it. But you have to have a physical Mac machine to work on iOS/Mac apps. In addition you need to register the developer license which cost $99/year. Apple defines quite a bit of restriction and rules to mark sure they have full control of the Apple nation. Apple's main focus is user experience, not much for developers. Many people claim Object C and XCode the worst development environment comparing with other popular languages and IDE in the market. However, due to Apple's huge crowd of so called high-end and loyal customers, more and more developers are joining this not so friendly platform.

On the other hand, Windows phone as a late-catch-up follower, surprisingly, still requires the same $99/year fee too. With registration done, you need to install the commercial Visual Studio IDE, Windows phone SDK and that notorious Zune package to develop a simple app to run on a Windows phone handset. Should they have made all free on the first day when realizing they were way behind Apple and Google? I love C# and .NET, but I feel sad about those dump decisions made by Microsoft.

In the Android world things are very different. You only need to pay one-time $25 registration fee to publish your app to the public. It's totally free for you to do any coding experience on your own Android devices. Unlike proprietary object C and C#, you will use Java and eclipse to program the Android apps. Java is the most used object-oriented programming language (source from Tiobe) in the past few years, and open-source eclipse is a popular IDE for many programming languages for years. All Java developers would easily switch to Android development.

Yesterday I downloaded the free Android SDK, unzipped it (no need to install anything), and I was able to open eclipse IDE to create a new Android project. After installed the USB driver for Nexus 7 I could connect the tablet to my development machine using USB cable, within 30 minutes, my first "Hello World" Android app was running in the Nexus 7. The only problem I had was the Android emulator which is super slow and basically useless. But running and debugging the code via a physical device is easy and fast.

Android devices become so popular in just a few years. Will Android rules the mobile world as Microsoft dominates PC world in the past 30 years? No one knows, but people like Android for reasons. From a developer point of view, it's free, open and easy to work with. All that is a nice invitation to developers to join the Android world.