Skip to content

Tutorial: Background operation

Dariusz Seweryn edited this page Apr 2, 2021 · 3 revisions

Overview

Running BLE connections in background is usually an important feature of applications meant for connected devices. To run RxAndroidBle in the background you need nothing different than for having an ordinary Android application running in the background.

There are plenty of tutorials on how to run Android application in the background and copying them here has little sense — you can simply google the topic.

Usually everything you need to keep an app working in the background is to have a Foreground Service running so the OS will not suspend your app. The service does not even need to host the connection logic at all. One of the RxAndroidBle users created a gist of a Service he uses for running a background connection — you can see it below.

Click to see the code
package net.grlewis.wifithermocouple;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;

/*
 * Motivation:
 * An Android Service is similar to an Activity, but has no user interface.
 * As the name implies, it provides services to one or more Activities in your App.
 * A Started Service is launched by the App and can run independently in the background.
 * A Bound Service is "bound" when an Activity wants to use it, and "unbound" when the Activity is finished with it
 * A Service can be Bound, Started, or both.
 * (Android destroys a pure Bound Service when the last Activity unbinds from it.)
 *
 * The process of binding to a Service is a bit complicated, and needlessly so when your Activity
 * simply wants to call methods that the Service provides (you can read about it in the Android documentation).
 *
 * This class was designed to simplify the process and make it "reactive" by creating an Observable
 * that your Activity can subscribe to when it wants to connect to the Service, and dispose when it's finished.
 * The Observable emits a single item: a reference to the connected Service that you can use to call its methods:
 *     serviceRef.someServiceMethod( );
 * (Note by the way that it is legal to call static methods in the Service using this instance reference,
 * though the compiler will warn you; use the annotation '@SuppressWarnings( "static-access" )' to prevent this.)
 *
 *
 * Use:
 *     Create YourService by extending Service (or some subclass; remote services under a different process aren't supported)
 *     YourService (typically in onCreate()) calls:
 *         Binder mBinder = new ReactiveServiceConnection.RSCBinder( this );  // Service passes instance of itself
 *     YourService overrides onBind( Intent ) and returns mBinder [Intent can pass additional Extra info if desired]
 *     YourService overrides onUnbind( Intent ) [Return true if you would like to have the Service's onRebind( Intent ) method
 *         later called when new clients bind to it. Mine just calls super. Could probably skip this unless want to return true.]
 *     The client Activity creates an instance of this ReactiveServiceConnection class and subscribes to it, for example:
 *         Disposable mServiceConnectionDisposable = new ReactiveServiceConnection( getApplicationContext(), YourService.class )
 *             .subscribe( serviceRef -> { <save the reference to YourService and use it to call its methods> };
 *         Before subscribing, the client can modify the Intent to be used via getBindIntent() and setBindIntent()
 *         YourService is bound on subscribing and the client Subscriber's onSubscribe() receives a Disposable to close the connection
 *         When YourService is connected a reference to it is emitted via the Subscriber's onNext(); the reference can be used to call Service methods
 *         When the client disposes, the reference is nulled, Service unbound, and disposed set true (Subscriber's onComplete() is not called)
 *         The following RuntimeExceptions may be passed to the Subscriber's onError() method:
 *             If there is a problem binding to the Service (it doesn't exist or binding isn't allowed): a ReactiveServiceBindingException
 *             If your Activity tries to bind using a different IBinder: a ReactiveServiceConnectionException
 *             If the connection is lost: a ReactiveServiceDisconnectionException (should never happen)
 *     Several methods are provided to retrieve information about the connection.
 *     Failing to dispose the connection (typically in your Activity's onStop()) may cause memory leaks.
 *     Don't forget to declare YourService in the App's manifest.
 *
 */


class ReactiveServiceConnection extends Observable<Service> implements ServiceConnection {
    
    private static final String TAG = ReactiveServiceConnection.class.getSimpleName();  // for logging debug messages
    
    private Observer<? super Service> subscriber;      // Subscriber passed by the .subscribe() call (which gets forwarded to our .subscribeActual())
    private RSCDisposable disposable;                  // returned to the Subscriber's .onSubscribe() method
    private boolean disposed;                          // tracks state for reporting by .isDisposed()
    private boolean okToBind;                          // set by .bindService(), indicating Service exists and binding is allowed
    
    private String serviceClassName;                   // name of the Service class
    private Service serviceRef;                        // reference used to call Service methods (emitted to Subscriber's onNext())
    private final Class serviceClass;                  // passed to constructor
    private ComponentName serviceComponentName;        // returned by .onServiceConnected()
    private Intent bindIntent;                         // created by constructor; can be modified via getter/setter
    private final Context context;                     // passed to constructor
    
    
    // Disposable that is returned to the Subscriber's onSubscribe( Disposable )
    // RxJava docs are unclear whether Subscriber's onComplete() should be called on disposal,
    // but when a Subscriber disposes, it should know that no more emissions are to be expected
    private class RSCDisposable implements Disposable {
        @Override // if called, unbind Service
        public synchronized void dispose( ) {
            disposed = true;
            context.unbindService( ReactiveServiceConnection.this );  // return the parent instance (ServiceConnection implementation)
            serviceRef = null;
            //subscriber.onComplete();  // RxJava docs are unclear whether this should be called (doesn't seem to be any need to)
        }
        @Override
        public synchronized boolean isDisposed( ) {
            return disposed;
        }
    }
    
    
    // Binder that is instantiated and used by the Service we're connecting to
    // constructor is called by the Service instance, passing 'this'
    // The Service overrides onBind() and returns this instance of RSCBinder
    public static class RSCBinder extends Binder {
        private Service boundService;
        // constructor
        RSCBinder( Service service ) { boundService = service; }  // normally 'this' is passed for Service
        // extension method added to Binder to return reference to Service
        public Service getService() { return boundService; }
    }
    
    
    public static class ReactiveServiceBindingException extends RuntimeException {
        public ReactiveServiceBindingException( String message ) { super( message ); }
    }
    
    public static class ReactiveServiceConnectionException extends RuntimeException {
        public ReactiveServiceConnectionException( String message ) { super( message ); }
    }
    
    public static class ReactiveServiceDisconnectionException extends RuntimeException {
        public ReactiveServiceDisconnectionException( String message ) { super( message ); }
    }
    
    
    // constructor
    // Client (Activity) calls this and subscribes to the created instance
    public ReactiveServiceConnection( Context callingContext, Class serviceClass ) {
        super();  // call the main Observable constructor
        serviceClassName = serviceClass.getName();
        this.serviceClass = serviceClass;
        context = callingContext;
    
        disposable = new RSCDisposable( );
        bindIntent = new Intent( callingContext, this.serviceClass );
    }
    
    
    
    @Override  // this is implemented to create the Observable (it's called by the parent Observable's subscribe())
               // it will be called when the Activity subscribes to receive the connection
    protected void subscribeActual( Observer<? super Service> observer ) {
        subscriber = observer;
        disposed = false;
        subscriber.onSubscribe( disposable );  // send back the Disposable
        // establish the connection (rest happens in onServiceConnected() callback)
        okToBind = context.bindService( bindIntent, this, Context.BIND_AUTO_CREATE );  // 'this' is ServiceConnection implementation
        if( !okToBind ) {
            disposed = true;
            subscriber.onError( new ReactiveServiceBindingException( "Requested " + serviceClassName + " does not exist or binding not allowed" ) );
        }
    }
    
    
    // ServiceConnection implementation
    @Override  // receives the RSCBinder
    public void onServiceConnected( ComponentName name, IBinder service ) {
        if( service instanceof RSCBinder ) {  // make sure the IBinder is compatible with this scheme
            serviceComponentName = name;
            serviceRef = ((RSCBinder) service).getService( );  // cast the IBinder to our special Binder and get the Service reference
            disposed = false;
            subscriber.onNext( serviceRef );                   // emit the service reference to subscribing Activity
        } else {
            disposed = true;
            subscriber.onError( new ReactiveServiceConnectionException( "IBinder in onServiceConnected callback from "
                    + name.toShortString() + " is not an instance of RSCBinder" ) );
        }
    }
    
    @Override  // this is only supposed to be called when a remote Service crashes (not our situation)
    public void onServiceDisconnected( ComponentName name ) {
        context.unbindService( this );
        disposed = true;
        serviceRef = null;
        subscriber.onError( new ReactiveServiceDisconnectionException( "The Service " + name.toShortString() + " has gone away." ) );
    }
    
//    @Override
//    Called when the binding to this connection is dead. This means the interface will never receive another connection.
//    The application will need to unbind and rebind the connection to activate it again.
//    public void onBindingDied( ComponentName name ) { }

//    @Override
//    Called when YourService returns null from its onBind() method (should not normally happen).
//    This indicates that the attempting service binding represented by this ServiceConnection will never become usable.
//    public void onNullBinding( ComponentName name ) { }

    
    public ComponentName getServiceComponentName() { return serviceComponentName; }
    public Class getServiceClass() { return serviceClass; }
    public String getServiceClassName() { return serviceClassName; }
    public boolean serviceIsOkToBind( ) { return okToBind; }   // not set until we actually try to bind (by subscribing)
    public Service getServiceRef( ) { return serviceRef; }     // the same Service reference emitted to Subscriber's onNext()
    
    // getter/setter that can be used to add EXTRA information to the Intent
    public Intent getBindIntent( ) { return  bindIntent; }
    public void setBindIntent( Intent bindIntent ) { this.bindIntent = bindIntent; }
    
}

Keep in mind that your milage may vary as different phone vendors take different measures to keep app power consumption low. See www.dontkillmyapp.com.

Scanning

Since API 26 (Oreo) Android OS exposes a special BLE scan function that is intended for scanning in the background. It promises to wake up the application via a PendingIntent. It is exposed in RxAndroidBle via BackgroundScanner:

// Getting the BackgroundScanner
BackgroundScanner backgroundScanner = rxBleClient.getBackgroundScanner();

// To start the scan
backgroundScanner.scanBleDeviceInBackground(/* PendingIntent */ pendingIntent, /* ScanSettings */ scanSettings, /* ScanFilter... */ scanFilter)

// To stop the scan
backgroundScanner.stopBackgroundBleScan(/* PendingIntent */ pendingIntent);

// To convert the raw OS ScanResults to corresponding RxAndroidBle ScanResults
List<ScanResult> scanResults = backgroundScanner.onScanResultReceived(/* Intent */ intent);

If you want to use foreground scan functions via a service on API 29 and above you should declare it in the AndroidManifest.xml with property android:foregroundServiceType="location" otherwise it will not get scan results — see readme for "Permissions".