04 - Android Services

  • Description: Android Services, Started Services, Bound Services, Messenger, AIDL, Foreground Services
  • Practical part:

Lecture

Practical

Resources

Files

Task 1 - Creating a started service (3p)

Create a new project with a blank main Activity. Add an EditText, a Button and a TextView to the Activity's layout. From the lab files, add the PiComputer.java class to your project. This class computes the number PI with variable precisions using the Viete series. Your first task is to create a started service that will receive a value for the precision, will create an instance of the PiComputer class giving it the received precision value and call the compute() method, retrieving the result.

Use the EditText to allow the user to input the precision and, when the user clicks the Button call startService(Intent) putting the precision value as an Extra in the Intent.

Create a new class in your project that will extend the Service Android class. Add the new service to the AndroidManifest.xml file. Within the service, override the onStartCommand() method and, if the received intent has the precision value extra, trigger the computing mechanism. We will consider that computing the value of PI is a computationally intensive operation. Therefore, the compute method should be called from a separate thread (Hint: AsyncTask).

  AsyncTask<Integer, Void, Double> task = new AsyncTask<Integer, Void, Double>() {
            @Override
            protected Double doInBackground(Integer... params) {
               // Here you will have to compute the PI number
            }


            @Override
            protected void onPostExecute(Double aDouble) {
               // Here a broadcast will be launched to notify the computed result to the MainActivity
            }
        };

In order for the service to send the answer back to the Activity, create a BroadcastReceiver instance and, within it's onReceive() method, update the TextView within the Activity using the value in the Intent parameter. Then, create a new IntentFilter with a custom action and register the receiver with the newly created IntentFilter:

BroadcastReceiver mReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent){
    // get the value for PI from the intent
    // and set it as the TextView's text
  }
};

IntentFilter filter = new IntentFilter();
filter.addAction(customActionString);

registerReceiver(mReceiver, filter);

Since we don't want to have the receiver registered after the Activity has been destroyed, override the onDestroy() method and unregister the receiver:

public class MainActivity extends Activity {
  ...
  @Override
  public void onDestroy() {
    super.onDestroy();
    
    unregisterReceiver(mReceiver);
  }
}

From the service, after the thread has finished computing the value for PI create a new Intent, put the value as an extra and call sendBroadcast():

Intent i = new Intent();
// set the action of the intent to the one you previously used in the Activity
i.setAction(customActionString);
//put the computed value of PI as an extra
i.putExtra("pi_value_extra", String.valueOf(computedPi));

sendBroadcast(i);

Task 2 - Running a foreground service (3p)

We want to have a new service notifying the user that our application is online, providing with a always-present notification which can be used to access the app at any time.

For this, create a new class that also extends Service and add it to the AndroidManifest.xml accordingly. Override the onStartCommand() method of the newly created service. If the intent action is “start_foreground” then show the notification and if the intent action is “stop_foreground” hide the notification.

The first step in creating a Notification is to construct an Intent which will be used to direct the user to the main Activity:

Intent i = new Intent(this, MainActivity.class);

Next, you need to create a PendingIntent using the PendingIntent.getActivity() method to which you will provide the previously created Intent:

PendingIntent pending = PendingIntent.getActivity(this, 0, i, 0);

Finally, you can construct the Notification instance using the Notification.Builder factory:

Notification.Builder notifBuilder = new Notification.Builder(this);

Set the icon and the content text on the notification appropriately (the icon should usually be your app's icon and the text can be any String that you find is informative of the running state of the app). Afterwards, set the content intent on the notification so that, when the user clicks it, the pending intent you created earlier is called:

notifBuilder.setContentIntent(pending);

Having set all required elements for the notification you can call the builder's build() method and get the notification:

Notification notification = notifBuilder.build();

Now, depending on the action of the intent sent as a parameter to the onStartCommand() method call startForeground() or stopForeground:

In the main Activity, add two more buttons, one used to show the notification and the other one to hide it. On each Button click create an intent, set the appropriate action and call startService(intent).

Task 3 - Moving from started service to bounded service (4p)

We now want to compute the value of PI using a bounded service instead of a started one. Therefore, create a third service and add it to the AndroidManifest.xml. Within this service's class declare an inner class that extends Binder. Add a method to the class that returns the current instance of the service class:

public class BoundService extends Service {
  ...
  public class MyBinder extends Binder {
    BoundService getService() {
      return BoundService.this;
    }
  }
}

Since the Binder class returns the service instance, create a method for computing PI within the service:

public void computePi(int precision, final Handler callback) {
  ...
}

Within this method compute the value of PI on a separate thread using the given precision. When the thread has finished and you have obtained the computed value, use the Handler object to obtain a Message, create a new Bundle object, add the value to it and send the Message:

// after the value of PI has been computed
Message msg = callback.obtainMessage();

Bundle args = new Bundle();
args.putDouble("pi_value", value);
msg.setData(args);

msg.sendToTarget();

Add a new service member variable to the Activity class and also a ServiceConnection member variable, which you must also implement:

public class MainActivity extends Activity {
  private BoundService mService;
  ...
  ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName component, IBinder service) {
      MyBinder mBinder = (MyBinder)service;
      mService = mBinder.getService();
    }
    ...
  };
}

In the Activity's onCreate() method you need to bind to the service using the ServiceConnection member variable:

Intent i = new Intent(this, BoundService.class);
bindService(i, mConnection, Context.BIND_AUTO_CREATE);

Since the service's compute method requires a Handler in order to send the result of the computation, create the Handler instance as well:

Handler mHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    Bundle data = msg.getData();
    // if the data has the "pi_value" key, retrieve it
    // and set it as the text of the TextView element
  }
};

Add a Button to the Activity, which on click will check if the service member variable is not null and call the BoundService.computePi() method:

if (mService != null) {
  mService.computePi(precision, mHandler);
}

Since we won't need the service after the Activity has been destroyed, call unbindService() in the onDestroy() method:

@Override
public void onDestroy() {
  ...
  unbindService(mConnection);
}

Bonus: Task 4 - Connecting to a bounded service from another application (4p)

We now want to make a fourth service that will expose the PiComputer mechanism to external applications. Create a IExtService.aidl file in the src/ folder of your application, within the same package as all other services. Within the .aidl file we will declare an interface which will be used by the system to allow external applications access to our internal service's functionality:

package ndk.lab4.picomputer; // this is just an example, use the package name you have within your app

interface IExtService {
  double computePi(int precision);
}

Now, create a fourth service(you can call it ExtService for consistency) in your app and add it to the AndroidManifest.xml. When declaring the service add an android:exported field in the XML element. Also, add an <intent-filter> child element:

<service
    android:name=".ExtService"
    android:exported="true">
    <intent-filter>
        <action android:name="ndk.lab4.picompute"/>
    </intent-filter>
</service>

Within the service implement the IExtService.Stub interface as a member variable and return the instance from the service's onBind() method.

Create a new Android application that will act as a client for the service exposed by the first application. In this new application's main Activity layout put an EditText, a Button and a TextView. The EditText will be used to allow the user to input the desired precision, the TextView will display the result and the Button's click will trigger the call to the remote service.

Copy the .aidl file from the first application to the new one's src/ folder. Do not change the package name in the .aidl file, but instead create a package name in the second app that corresponds to the one in the app providing the service.

In the MainActivity retain a member variable to the IExtService and create a local ServiceConnection. When implementing the ServiceConnection.onServiceConnected() method, get a reference to the remote IExtService using the IExtService.Stub.asInterface(IBinder) construct.

To connect to the remote service create an Intent variable, set the package to be the one of your original app and the action to be the one declared in that app's AndroidManifest.xml:

Intent i = new Intent();
i.setPackage(origAppPackageName);
i.setAction(remoteServiceAction);

Afterwards, you can call bindService() using the intent and the previously declared ServiceConnection instance. Now, when you want to compute the value of PI in the second application, call the mRemoteService.compute() method.

osp/lectures/lecture-services.txt · Last modified: 2016/11/06 18:55 by laura.gheorghe
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0