Three Educative Examples on Using Binders

 

 

Three Educative Examples on Using Binders
--------------------------------------------
Let us study three simple programs to familiarize ourselves with a few key
ideas about Binders. These examples are intended to present a non-superficial 
view of the binder framework. Therefore, the emphasis is on introducing a few 
important ideas, appealing to the common sense and intuition of the reader, 
without being too precise and pedantic in our explanation.

Our first example lists names of services that use binders. Only those services 
that are executing are listed. Each service publishes one or more interfaces. 
However, in the following code, we are interested in listing only the names of 
the services, not in their interfaces or functionality. 

1.  void listServices()
2.  {
3.      sp<IBinder> ctx = ProcessState::self()->getContextObject(NULL);
4.      sp<IServiceManager> sm = IServiceManager::asInterface(ctx);
5.
6.      Vector<String16>  services = sm->listServices();
7.      for (size_t i = 0; i < services.size(); ++i)
8.          aout << services[i] << endl;
9.  }

This code (along with 'main' function) can be compiled as an executable. 
(Useful tip: You can use the 'readelf' command to inspect the dynamic libraires 
and symbols used in this program.

The first thing that attracts attention is our use of a type named 'sp', that 
stands for 'strong pointer'. The binder objects are reference counted, and 
therefore, are 'strongly' tracked. When the function 'listServices' completes 
execution, two strong pointers, 'ctx' and 'sm' in the code above, automatically 
adjust the reference counts on objects they hold. This, in turn, ensures that 
objects do not leak memory across address spaces.    

The line 3, in the code above, shows that our process is already enabled with 
a per-process portion of the binder framework. The 'getContextObject' call
gets hold of a system-wide object that manages the 'context' or infrastructure
necessary for publishing interfaces implemented by different components. Notice 
that the object is parceled as a binder!

It is important to observe that components that publish interfaces usually live 
in different processes. Therefore, it is essential to have a  mechanism to
export interface references (which are, after all, pointers) across process 
boundaries. A 'context object' takes up this responsibility and transparently
handles marshaling and unmarshaling values and references across processes. 

In the code above, the line 4 turns the binder object into an IServiceManager
pointer. In later portions of this primer, we will take a closer look at the 
'asInterface' function; it is enough to know, at the moment, that this function 
either provides a raw pointer to the interface counterpart of the binder or
gives out a pointer to a proxy object. In the first case, the object 
implementing the requested interface lives in the caller's process, and in the
second case, proxy takes care of marshaling and unmarshaling remote calls.

IServiceManager has a function named 'listServices', that returns a bunch of 
names of services that publish one or more interfaces. In lines 7 and 8, our 
code simply prints these names.

The definitions of IBinder, ProcessState, IServiceManager, Vector, and String16
can be found in various files under 'frameworks/base/libs/utils' folder. The 
dynamic library that hosts this code is 'libutils.so'. 

The Second Example: Get a State Dump of a Service
---------------------------------------------------
The next example uses the debugging support built into binders. A binder object
may be asked to produce a dump of its state. The following code communicates
with the binder associated with Android's activity manager service, and produces
a really detailed report in a stream.

The code shown below looks slightly different from the first example. In line 6, 
we use the helper function, 'defaultServiceManager', to get to IServiceManager. 
This helper function is defined as a part of the binder API (which is packaged 
in 'libutils.so'). 

Refer back to lines 3 and 4 in 'listServices' example for comparison. Keep in 
mind that we have elided error handling code in our examples for brevity and 
simplicity.

1.  void dumpActivityState()
2.  {
3. 	    const char *ACTIVITY_MANAGER_SERVICE = "activity";
4.      const char *DUMP_FILE_PATH = "/data/app/dump.txt";
5.
6.      sp<IServiceManager> sm = defaultServiceManager();
7.      sp<IBinder> amBinder = sm->getService(String16(
8.                                                ACTIVITY_MANAGER_SERVICE));
9.      int fd = ::open(DUMP_FILE_PATH, O_CREAT | O_WRONLY | O_TRUNC, 0777);
10.     if (fd > -1) {
11.        Vector<String16>  args;
12.        amBinder->dump(fd, args);
13.        ::close(fd);
14.    }
15. }

In line 7 we attempt to reach the binder interface of the activity manager 
service. The 'IServiceManager::getService' function searches for a registered 
service with the given name and retrieves its IBinder interface.

In line 9 we create a file for writing, and in line 11 we ask the binder
to dump its state. This will produce a detailed report of the running activities
in the system, exactly same as the 'dumpsys' command might produce! 

The Third Example: Exercising a Binder Object
--------------------------------------------------------------------
Our third example uses some of the basic member functions of IBinder. It has a
great educational value since it draws our attention to some of the fundamental
aspects of binders. 

Take a look at the 'testBinders' function: 

1.  void testBinders()
2.  {
3.      sp<IServiceManager> sm = defaultServiceManager();
4.      sp<IBinder> amBinder = sm->getService(String16("activity"));
5.
6.      String16 amItfDescr = amBinder->getInterfaceDescriptor();
7.      if (amItfDescr == String16("android.app.IActivityManager"))
8.      {
9.  		aout << "got 'android.app.IActivityManager'" << endl;
10. 	}
11.
12. 	if (amBinder->isBinderAlive() &&
14. 		amBinder->pingBinder() == NO_ERROR &&
            amBinder->queryLocalInterface(amItfDescr) == NULL)
15. 	{
16. 		aout << "The non-local interface binder is alive" << endl;
17. 	}
18.
19. 	if ((amBinder->localBinder() == NULL)  && 
20.         (amBinder->remoteBinder() != NULL)) 
21.     {
22. 		aout << "we really have a proxy for the remote interface!" << endl;
23. 	}
24.
25.     if ((am->remoteBinder()->remoteBinder() != NULL) &&
26.		    (am->remoteBinder() == am->remoteBinder()->remoteBinder()) &&
27.		     am->remoteBinder()->remoteBinder() ==
28.			                am->remoteBinder()->remoteBinder()->remoteBinder())
29.	    {
30.			aout << "same remote binder" << endl;
31.	    }
32. }

By now, the lines 3 and 4 are quite familiar to us! On line 6, we retrieve the
interface descriptor associated with the activity manager's binder object. In the
Binder framework, each interface should be uniquely identifiable, and usually a 
human readable name is used as a descriptor.

Since we know, as of Android 1.5, that activity manager's interface descriptor 
is 'android.app.IActivityManager' we directly compare it with the name that
'getInterfaceDescriptor' returns.  

The lines 12 through 17 exercise three additional member functions of binders.
Both 'isBinderAlive' and 'pingBinder' are used to check if the binder object is
still running. 

The function 'queryLocalInterface' is more interesting: it is intended to 
retrieve a reference to the requested interface. The reference is 'local', which
means that the object that implements the requested interface lives in the same
process as the caller.   

In our example, we pass the descriptor 'android.app.IActivityManager' which was
obtained in line 6. Note that we expect 'queryLocalInterface' to retuen a NULL
pointer! This is because we intuitively suppose that our code executes in a
process other than that of the activity manager. Our hypothesis proves to be 
correct in this context!

The code in lines 19 to 23 use additional tests to confirm our intuitive ideas.
We use 'locaBinder' and 'remoteBinder' functions to check if activity manger's 
binder that we have been using is actually a binder object that operates from
within our process. Here again, commonsense prevails and the local binder 
pointer will be actually NULL, while the remote binder reference is non-NULL. In
other words, this implies that the binder object which we obtain in line 4 is 
actually a proxy to the remote binder that lives in the activity manager's 
process!

Finally, lines 25 to 31 demonstrate that a program would receive the same 
remote binder given an arbitrary binder reference. This implies that we will
never encounter a situation where a long chain of remote binders have to be 
traversed to reach the final binder. This fact intuitively makes sense because
creating a proxy for another proxy is neither cheap nor practical. For example, 
If such a scheme were allowed, proxies would be required to propagate events or
notifications such as the death of the actual object that implements an 
interface. With a single, unique remote binder identity, it also becomes easier 
to check the remote object identity. 

A Quick Summary:
--------
The abstractions in Binder framework are not too complex to understand. At the
same time, binders are powerful and efficient. Their implementation in Android
is lightweight, keeping in mind the resource constraints in mobile devices.    

It is evident from these three examples that binder objects that live in 
different processes use IPC to communicate with one another. The Binder runtime 
transparently handles various details of object-oriented IPC.

With this brief introduction to some of the important ideas in Binder framework,
we are ready to learn the internals of binders. This is the topic of the next 
section. 

 

發佈了23 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章