Вы находитесь на странице: 1из 5

An Introduction to Using Binder Framework on Android Operating System

----------------------------------------------------------------------
We intend this write-up to serve two purposes: (1) be a document describing
the Binder framework as implemented on Android operating system, and (2) be a
"how-to" guide for beginners who want to quickly develop Android applications
in C++ using Binder API.
This document takes a "learning-while-programming" approach. It develops a
simple service on Android, and in the process, introduces and defines terms,
presents application code, reproduces source code snippets from the binder
implementation, and discusses interesting aspects, all at once. We have found
such an approach quite useful because it presents systemic perspectives right at
once, when it makes more sense. Also, it clears up questions when certain
aspects appear non-trivial or vague.
Our approach is not without its drawbacks. For one, it assumes that the reader
is confident and competent enough to simultaneously think at different levels.
One should be prepared to switch between application and framework levels
quickly. Otherwise, our approach has the potential to confuse people to an
extent that they may feel intimidated and reject learning the whole thing. That,
unfortunately, is not an enjoyable experience at all.
The explanation that follows assumes that you are already familiar with at
least one component technology such as CORBA, COM, or Java RMI. In order to
understand what is discussed here, it is essential to have a reasonably good
grasp of the following concepts: process boundaries (that is, address-space
separation in modern operating systems), IPC, RPC, method invocation on remote
objects, marshaling across process boundaries, and proxy objects that help in
achieving location transparency.
Binder framework on Android is implemented in C++. The implementation makes use
of template classes, template functions, multiple inheritance, virtual
inheritance, macros that glue fields and member functions to template classes
that use multiple inheritance, and base classes with methods that return
pointers to derived class instances! We also assume a familiarity with smart
pointers. If you have used STL or Boost smart pointers, then you will not have
any difficulty following the Binder implementation details.
All in all, it is an intellectually rewarding exercise to put these pieces
together in the context of binders. So, without further ado, let's approach
Binder from its most interesting starting point.
Defining a Custom Interface
---------------------------
An interface defines a collection of functions, and informally specifies the
semantics of the intended behavior. In Android, the Binder framework has a class
named 'IInterface' that is the base of all user-defined interfaces.
We can define a custom interface named 'ISampleStack' as shown below:
class ISampleStack : public Iinterface {
public:
virtual status_t push(int data) = 0;
virtual status_t peek(int *data) = 0;
virtual void pop() = 0;
};
ISampleStack defines three operations on stacks - push, pop, and peek.
Each member function returns NO_ERROR when it successfully executes
its intended operation. The member function 'push' returns ERROR_STK_FULL when
the stack has no room for the new element. The function 'peek' returns
ERROR_STK_EMPTY when an attempt is made to retrieve value from an empty stack.
The operation 'pop' does not return any value, nor does it indicate errors
in its execution.
The definition of ISimpleStack elides a few details for brevity and clarity. In
practice, we need to use a macro within the scope of ISimpleStack declaration
in order to declare infrastructural data and code required to support binder
objects. But more on it later - once we gather more information about the
different pieces that make up a binder object.
Since we are looking for under-the-hood details, it is tempting to take a quick
look at the declaration of IInterface, which we reproduce here:
class IInterface : public virtual RefBase
{
public:
sp<IBinder> asBinder();
sp<const IBinder> asBinder() const;
protected:
virtual IBinder* onAsBinder() = 0;
};
At this point, it is adequate to know that the RefBase class implements basic
reference counting facility. Also, the template class 'sp' refers to strong
pointer smart-pointer implementation. Both RefBase and sp types, which are still
incomplete from the perspective of our current understanding, indicate that
binder objects are reference counted, and that perhaps the use of smart pointers
mitigates the difficulties of reference counting.
Note that IInterface provides access to an object that implements
IBinder. This shows that there is a much deeper connection between IInterface
and IBinder, something that will be become more and more clear as we progress.
The asBinder() member function's implementation is starightforward:
sp<IBinder> IInterface::asBinder()
{
return this ? onAsBinder() : NULL;
}
The framework does not provide an implementation for onAsBinder. It is a pure
virtual function that derived interfaces must override.
There are at least four key points to note in the code above.First, asBinder()
function returns a strong pointer to an object that implements
IBinder interface. Therefore, this function yields a reference counted binder
object. The life of the binder object obtained using asBinder() is
automatically tracked.
Second, the name 'asBinder' gives us enough hints that in most cases a single
class implements both IInterface and IBinder. Perhaps, a concrete class employs
multiple inheritance to bring together implementations derived from IInterface
and IBinder.
Third, by delegating to onAsBinder() and requiring it to be overridden by
derived classes, the framework is enabling varieties of implementation. One can
imagine lazy object creation, virtual proxies for real binder objects,
decorators or interceptors to modify the behavior of the underlying binder
object, and so on.
Fourth, asBinder() is overloaded. There is a const version and a non-const
version of the function. This is useful when programs use const instances of
classes derived from IIinterface. Although not completely shown here, both
functions are implemented using the same 'asBinder' definition presented above.
The const version of 'asBinder' merely const-casts 'this' pointer before calling
'onBinder'.
Equipped with the basic understanding of IInterface and ISimpleStack, we can
turn our attention to another fundamental interface: IBinder.
IBinder and Binder Objects
--------------------------
In the Binder API, there is a one-to-one relation between IInterface and
IBinder. For each interface that the application wants to publish, there
should be a corresponding IBinder that abstracts location as well as
the implementationvdetails of the object that actually realizes the interface.
A binder object is an instance of a class that implements the IBinder interface.
We use the term 'binder class' to refer to a concrete class whose instances are
binder objects. In other words, a binder class directly or indirectly implements
IBinder, and its instances are binder objects.
It is important to know that in Android, a binder class should implement one
and only one interface derived from IInterface. Unlike COM and other component
technologies, Android's Binder implementation does not automatically enable
implementing multiple custom interfaces (that derive from IInterface).
For quick reference, we present a portion of IBinder declaration below.
class IBinder : public virtual RefBase {
public:
inline IBinder() { }
virtual sp<IInterface> queryLocalInterface(const String16&
descriptor);
virtual String16 getInterfaceDescriptor() const = 0;
virtual bool isBinderAlive() const = 0;
virtual status_t pingBinder() = 0;
virtual status_t dump(int fd,
const Vector<String16>& args) = 0;
virtual status_t transact(uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0) = 0;
virtual BBinder* localBinder();
virtual BpBinder* remoteBinder();
// remaining portion is elided
...
protected:
inline virtual ~IBinder() {
};
Apart from noticing that binder objects are reference counted, there are
a few interesting aspects waiting to be understood before moving on. Notice the
'queryLocalInterface' function. It takes a string
descriptor representing an interface, and it returns a (strong) pointer to
an object that implements IInterface. Also, notice the 'getInterfaceDescriptor'
function that returns the string descriptor of the interface associated with
this binder.
There is something interesting going on here. Recall that 'asBinder'
function of IInterface gives out a pointer to a binder object. Using that binder
object one can 'getInterfaceDescriptor' followed by 'queryLocalInterface' to
get back the original IInterface reference. This demonstrates the one-to-one
relation that exists between the implementation of IInterface and IBinder in a
program.
The 'isBinderAlive' and 'pingBinder' functions are useful when the program wants
to find out if a binder object can receive calls. If a local or remote binder
object is dead, then these functions will indicate that fact.
Some services may automatically take away binder objects that are not
periodically 'pinged' by their active clients. This is a potential optimization
strategy where a service may pool and reuse binder objects to reduce memory
overhead and to cater to a large number of clients.
The 'dump' function requests a binder object to persist its state to a stream
represented by the first argument - a file descriptor. The second argument - an
array of strings - represents an arbitrary collection of strings that the caller
wants the binder object to persist in the stream. One may imagine other uses of
this parameter also. For example, some values may alter the behavior of this
function, and some may define the amount of state information persisted.
In our example, the ISimpleStack implementation will store stack's size and
contents in the given stream.
The 'transact' function is arguably the most important function. It is from here
a binder object invokes functions that actually implement the behavior
published in the custom interface (for example, ISimpleStack in our case). Let
us take a closer look at its complete declaration:
virtual status_t transact(uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0) = 0;
As evident from this declaration, 'transact' is a pure virtual function that
local and remote binders must implement. The first argument - 'code' - denotes
the function that should be carried out. Typically, the binder object uses a
switch statement over the value of this argument to dispatch the actual
function. The second argument - 'data' - of type Parcel, represents the
arguments that should be unmarshaled (at the callee site) before the binder
object invokes the function. The third argument - 'reply' - again of type
Parcel, represents the result of executing the function. It is the return value
that should be unmarshaled at the caller site. The last argument - 'flags' -
specifies the nature of IPC call and the contents of parcel objects. One of the
values that we will be interested in is FLAG_ONEWAY (or TF_ONE_WAY depending on
the source files you look at), which is defined as an enumeration value:
FLAG_ONEWAY = 0x00000001
This value indicates that the caller is not interested in the return value of
the call, and that the caller does not block on the result of the remote method
invocation.

Вам также может понравиться