Fork me on GitHub

Tuesday, February 12, 2013

Better resource releasing on Android

In Android programming, suppose you have some resources to release, then you probably override onDestroy() method. For example, unregistering BroadcastReceiver can be done as the following Java code:
class MyActivity extends ListActivity {
    protected List<BroadcastReceiver> receivers = 
                         new ArrayList<BroadcastReceiver>()

    public IntentFilter registerReceiver(BroadcastReceiver receiver,
                                         IntentFilter filter) {
        receivers.add(receiver);
        super.registerReceiver();
    }

    public Void onDestroy() {
        for(receiver : receivers) {
            unregisterReceiver(receiver);
        }
        super.onDestroy();
    }
}
Looks not bad, although the code is wordy. However, a really bad thing is revealed when you have to release various resources in multiple classes. You might have a TCP connection, opened file to be closed, phone state listener to be unregistered, or asynchronous job to be properly cancelled. Therefore, the compact representation of resource releasing is important. Moreover, the resource-releasing code should be reused between different classes when it is needed in both Services and Activities.

However, current Android resource management has two reusability issues:

Problem 1: No common parent of Activity and Service

For the example above, there is no way to reuse the code between Activities and Services, because Activity and Service has no common parent interface sharing onDestroy() method!


Problem 2: Maintaining list of resources

The code shown above is consisted with three parts:
  1. Allocates a collection that will contain opened resources (in line 2)
  2. Stores a resource that is acquired (in line 6)
  3. Release the resources (in lines 11-13)
If you manage different kinds of resources (e.g. TCP connection and files), the code is not reusable between the other kinds of resources. That is, you have to allocate two collections for two different resources, and release it individually in onDestroy() method. For example, suppose your service opens files and starts threads that should be closed onDestroy():

class MyService extends Serivce {
    protected List<WorkerThread> threads = new ArrayList<WorkerThread>()
    protected List<File> files = new ArrayList<File>()

    public void openFile(...) {
        //...
        receivers.add(openedFile);
    }
    public void startNewJob(...) {
        //...
        threads.add(workerThread);
    }

    public Void onDestroy() {
        for(file : files) {
            file.close();
        }
        for(thread: threads) {
            thread.sendCancelMessage();
        }
        super.onDestroy();
    }
}

What if other kinds of resources to be managed? What if the same thing is needed in your Activity? This is a specimen of reusability hell.

A solution

In Scala, the example above could be reduced as follows:
class MyService extends SSerivce {
  def openFile(...) {
    //...
    onDestroy(openedFile.close())
  }
  def startNewJob(...) {
    //... brace can also be used
    onDestroy {
      workerThread.sendCancelMessage()
    }
  }
}

Because Scala supports anonymous function as a parameter, the code passed through onDestroy(...) method will be executed when the service is destroyed.

If you have to use the same code in your activity, you can extract it as a trait:
trait OpenFile extends Destroyable {
  def openFile(...) {
    //...
    onDestroy(openedFile.close())
  }
}
Then, just inherit it in your activities or services:
class MyActivity extends SActivity with OpenFile {
  // Done! just use openFile() method and forget about releasing it.
}

This solved the problems by no explicit access of collection object, and introducing common triats that inherits both SActivity and SService.
The trait Destroyable is defined for onDestroy(), and Creatable is defined for onCreate(), and other lifecycle related methods including onResume(), onStart(), onPause(), and onStop() are implemented as well.


Destroyable in Action: UnregisterReceiver

For the case of BroadcaseReceiver, it is done by just inherit a trait in your class.
class MyService extends SService with UnregisterReceiver {
  // Done. Every registered receivers unregistered automatically.
}
This is the full source code of UnregisterReceiver implimentation:
trait UnregisterReceiver extends ContextWrapper with Destroyable {
  override def registerReceiver(receiver: BroadcastReceiver, 
                                filter  : IntentFilter) = {
    onDestroy {
      unregisterReceiver(receiver)
    }
    super.registerReceiver(receiver, filter)
  }
}

No comments:

Post a Comment