Introduction
This code shows how to integrate Activities and Services using messaging - not RPC (binding).
The code simulates a long running process - for example a data synchronization with a WebServer.
The example consists of an Activity (MainActivity) containing:
- A ProgressBar (shoving the service progress)
- A TextView (showing the final service result)
- A Button (to start the service)
And a Service (ServerSyncService). When ServerSyncService receives an Intent it performes the following logic:
- Loop 1 to 100
- Update ProgressBar (via callback)
- Sleep 200 ms
- Set TextView to "Data from server..." (via callback)
Callback from the Service is done by sending an Intent to MainActivity via LocalBroadcastManager.
The Intent contains a command and a data part (embedded as putExtras). The commands are
The command structure is:
Command | Data |
UPDATE_PROGRESS | Percent completed |
RESULT | Data from server |
To avoid "magic numbers" a helper class "Constant" is used to hold global constants - for example putExtra key names.
One important feature to notice is that the Activity keeps being updated correctly even if it is rotated (restarted).
Background
I teach Android programming, and have been searching for a simple example showing how to make callback from a Service using messaging. I probably did not search enough since I did not find examples I liked "
Anyway this my self constructed example. The example is based on LocalBroadcastManager
Using the code
The helper class "Constant"
package dk.eal.kbr.android.example.asyncservicecallbacktoactivity.communication;
public class Constant
{
public static final int UPDATE_PROGRESS = 1;
public static final int RESULT = 2;
public static final String FILTER =
"dk.eal.kbr.android.example.asyncservicecallbacktoactivity.communication.REQUEST_PROCESSED";
public static final String COMMAND = "COMMAND";
public static final String DATA = "DATA";
}
MainActivity layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".MainActivity">
<ProgressBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_gravity="left|center_vertical" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/result"
android:id="@+id/textView"
android:layout_gravity="left|center_vertical" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Service"
android:id="@+id/button"
android:onClick="onStartService_Clicked"
android:layout_gravity="left|center_vertical" />
</LinearLayout>
MainActivity java code
package dk.eal.kbr.android.example.asyncservicecallbacktoactivity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.app.Activity;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import dk.eal.kbr.android.example.asyncservicecallbacktoactivity.communication.Constant;
public class MainActivity extends Activity {
private ProgressBar mProgress;
private TextView result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mProgress = (ProgressBar)findViewById(R.id.progressBar);
mProgress.setMax(100);
result = (TextView)findViewById(R.id.textView);
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter(Constant.FILTER));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void onStartService_Clicked(View v)
{
mProgress.setProgress(0);
result.setText("");
Intent intent = new Intent(this, ServerSyncService.class);
startService(intent);
}
@Override
protected void onStart() {
super.onStart();
LocalBroadcastManager.getInstance(this).registerReceiver((mMessageReceiver), new IntentFilter(Constant.FILTER));
}
@Override
protected void onStop() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onStop();
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("KBR", "Got message");
handleMessage(intent);
}
};
private void handleMessage(Intent msg)
{
Bundle data = msg.getExtras();
switch (data.getInt(Constant.COMMAND, 0))
{
case Constant.UPDATE_PROGRESS:
int progress = data.getInt(Constant.DATA, 0);
mProgress.setProgress(progress);
break;
case Constant.RESULT:
String res = data.getString(Constant.DATA);
result.setText(res);
break;
default:
break;
}
}
}
Important code:
In the methods
it is very important to use the LocalBroadcastManager to start/stop listening for incoming Intents from ServerSyncService.
When an Intent arrives the method "handleMessage" is called. "handleMessage" inspects the command part of the message and perform the requested action on the main thread.
ServerSyncService java code
package dk.eal.kbr.android.example.asyncservicecallbacktoactivity;
import android.app.IntentService;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import dk.eal.kbr.android.example.asyncservicecallbacktoactivity.communication.Constant;
public class ServerSyncService extends IntentService {
public ServerSyncService() {
super("ServerSyncService");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
protected void onHandleIntent(Intent intent) {
for(int i = 1 ; i <= 100 ; i++)
{
sendUpdateMessage(i);
try
{
Thread.sleep(200);
}
catch(Exception e)
{
Log.d("KBR", "SendError: " + e.getMessage());
}
}
sendResultMessage("Data fra serveren...");
stopSelf();
}
private void sendUpdateMessage(int pct) {
Log.d("KBR", "Broadcasting update message: " + pct);
Intent intent = new Intent(Constant.FILTER);
intent.putExtra(Constant.COMMAND, Constant.UPDATE_PROGRESS);
intent.putExtra(Constant.DATA, pct);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
private void sendResultMessage(String data) {
Log.d("sender", "Broadcasting result message: " + data);
Intent intent = new Intent(Constant.FILTER);
intent.putExtra(Constant.COMMAND, Constant.RESULT);
intent.putExtra(Constant.DATA, data);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
The interesting part of the code is the methods sendUpdateMessage and sendResultMessage.
They build the messages for progressbar update and service result respectively.
Manifest
="1.0"="utf-8"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dk.eal.kbr.android.example.asyncservicecallbacktoactivity"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="16" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="dk.eal.kbr.android.example.asyncservicecallbacktoactivity.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>
<service android:name=".ServerSyncService">
</service>
</application>
</manifest>
Remember to include the service in the manifest
Points of Interest
To me it has been great to dive into a pure messaging way of communication
History
Initial version.