Hi, I'm preparing a document describing Binder implementation in Android. I've already written about four pages combining "how-to-write-a-binder-program" with "how-does-it-work" style of writing. As I progressed, I thought it might be useful to listen to what others think about such an approach. So I'm attaching the document for your comments. Please take a look at it and provide your suggestions, opinions, views, and what not, to make this a useful document. FYI, English is not my mother tongue. Therefore, my style may not be smooth on ears that converse in English :-) Please feel free to suggest improvements not only on the content but also on the overall style and readability.
Thank you for your time and patience Devi Prasad PS: I've posted this on both platform and framework groups. I'm always confused about these two forums ;-) --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "android-framework" group. To post to this group, send email to android-framework@googlegroups.com To unsubscribe from this group, send email to android-framework+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/android-framework?hl=en -~----------~----~----~----~------~----~------~--~---
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.