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.