An Introduction to Using Binder Framework on Android Operating System

    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.





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值