Android的存储系统—Vold与MountService分析

xiaoxiao2021-02-27  342

Android的存储系统—Vold与MountService分析(一)

Android的存储系统(一)

看了很长时间Vold存储模块的相关知识,也死扣了一段时间的Android源码,发现Android存储系统所涉及的函数调用,以及Kernel与上层之间的Socket传输真的是让人头疼,除了需要整理整个架构的原理以外,还要反复看源码,真真的郁闷。

郁闷之余,还是打算把自己看过的经验之贴和参考资料进行整理,以帖子的形式发出来,供码神们参考,有不对的地方请指正,我们互相交流,下面就进入主题。

Android的存储系统主要由:SystemServer进程中的MountService和Vold进程中的VolumeManager组成。

它们管理着系统的存储设备,执行各种操作,如:mount、unmount、format等。

图1 Android存储系统架构图

图2 Android存储系统原理图

【重要组成分析】

1、NetlinkManager

     全称是NetlinkManager.cpp位于Android 4.x 源码位置/system/vold/NetlinkManager.cpp。

     该类的主要通过引用NetlinkHandler类中的onEvent()方法来接收来自内核的事件消息,NetlinkHandler位于/system/vold/NetlinkHandler.cpp。

2、VolumeManager

     全称是VolumeManager.cpp位于Android 4.x源码位置/system/vold/VolumeManager.cpp。该类的主要作用是接收经过NetlinkManager处理过后的事件消息。

     因为我们这里是SD的挂载,因此经过NetlinkManager处理过后的消息会分为五种,分别是:block、switch、usb_composite、battery、power_supply。

     这里SD卡挂载的事件是block。

3、DirectVolume

     位于/system/vold/DirectVolume.cpp。该类的是一个工具类,主要负责对传入的事件进行进一步的处理。

     block事件又可以分为:Add、Removed、Change、Noaction这四种。

4、Volume

     位于/system/vold/Volume.cpp,该类是负责SD卡挂载的主要类。Volume.cpp主要负责检查SD卡格式,以及对复合要求的SD卡进行挂载,并通过Socket将消息SD卡挂载的消息传递给NativeDaemonConnector。

5、CommandListener

     该类位于位于/system/vold/CommandListener.cpp,通过vold socket与NativeDaemonConnector通信。

6、NativeDaemonConnector

     该类位于frameworks/base/services/java/com.android.server/NativeDaemonConnector.java。该类用于接收来自Volume.cpp 发来的SD卡挂载消息并向上传递。

7、MountService

     位于frameworks/base/services/java/com.android.server/MountService.java。

     MountService是一个服务类,该服务是系统服务,提供对外部存储设备的管理、查询等。在外部存储设备状态发生变化的时候,该类会发出相应的通知给上层应用。在Android系统中这是一个非常重要的类。

8、StorageManaer

     位于frameworks/base/core/java/andriod/os/storage/StorageManager.java。

     在该类的说明中有提到,该类是系统存储服务的接口。在系统设置中,有Storage相关项,同时Setting也注册了该类的监听器。

     而StorageManager又将自己的监听器注册到了MountService中,因此该类主要用于上层应用获取SD卡状态。

 

【SD卡挂载流程】

1、Kernel发出SD卡插入uevent消息。

2、NetlinkHandler::onEvent()接收内核发出的uevent并进行解析。

3、VolumeManager::handleBlockEvent()处理经过第二步处理后的事件。

4、接下来调用DirectVolume::handleBlockEvent()。

在该方法中需要注意亮点:

(1)程序首先会遍历mPath容器,寻找与event对应的sysfs_path是否存在于mPath容器中;

(2)针对event中的action有4种处理方式:Add、Removed、Change、Noaction。

 

 

 

 

5、经过上一步之后会调用DirectVolume::handleDiskAdded()方法,该方法中会广播disk insert消息。

6、SocketListener::runListener()会接收DirectVolume::handleDiskAdded()广播的消息。该方法主要完成对event中数据的获取,通过Socket。

7、调用FrameworkListener::onDataAvailable()方法处理接收到的消息内容。

8、FrameworkListener::dispatchCommand()该方法用于分发指令。

9、在FrameworkListener::dispatchCommand()方法中,通过runCommand()方法去调用相应的指令。

10、在/system/vold/CommandListener.cpp中有runCommand()的具体实现。在该类中可以找到这个方法:CommandListener::VolumeCmd::runCommand(),从字面意思上来看这个方法就是对Volume分发指令的解析。该方法中会执行“mount”函数:vm>mountVolume(arg[2])。

11、mountVolume(arg[2])在VolumeManager::mountVolume()中实现,在该方法中调用v>mountVol()。

12、mountVol()方法在Volume::mountVol()中实现,该函数是真正的挂载函数。(在该方法中,后续的处理都在该方法中,在Mount过程中会广播相应的消息给上层,通过setState()函数)。

13、setState(Volume::Checking);广播给上层,正在检查SD卡,为挂载做准备。

14、Fat::check();SD卡检查方法,检查SD卡是否是FAT格式。

15、Fat::doMount()挂载SD卡。

至此,SD的挂载已算初步完成,接下来应该将SD卡挂载后的消息发送给上层,在13中也提到过,在挂载以及检查的过程中其实也有发送消息给上层的。

 

16、MountService的构造函数中会开启监听线程,用于监听来自vold的socket信息。

     Thread thread = new Thread(mConnector,VOLD_TAG); thread.start();

17、mConnector是NativeDaemonConnector的对象,NativeDaemonConnector继承了Runnable并Override了run方法。在run方法中通过一个while(true)调用ListenToSocket()方法来实现实时监听。

18、在ListenToSocket()中,首先建立与Vold通信的Socket Server端,然后调用MountService中的onDaemonConnected()方法。

19、onDaemonConnected()方法是在接口INativeDaemonConnectorCallbacks中定义的,MountService实现了该接口并Override了onDaemonConnected()方法。该方法开启一个线程用于更新外置存储设备的状态,主要更新状态的方法也在其中实现。

20、然后回到ListenToSocket中,通过inputStream来获取Vold传递来的event,并存放在队列中。

21、然后这些event会在onDaemonConnected()通过队列的”队列.take()”方法取出。并根据不同的event调用updatePublicVolumeState()方法,在该方法中调用packageManagerService中的updateExteralState()方法来更新存储设备的状态。

22、更新是通过packageHelper.getMountService().finishMediaUpdate()方法来实现的。

23、在updatePublicVolumeState()方法中,更新后会执行如下代码:

       bl.mListener.onStorageStateChanged();

在Android源码/packages/apps/Settings/src/com.android.settings.deviceinfo/Memory.java代码中,实现了StorageEventListener 的匿名内部类,并Override了onStorageStateChanged()方法。因此在updatePublicVolumeState()中调用onStorageStateChanged()方法后,Memory.java中也会收到。在Memory.java中收到以后会在Setting界面进行更新,系统设置—存储中会更新SD卡的状态。从而SD卡的挂载从底层到达了上层。

 

在下一个帖子中我会对Vold模块的源码以及MountService服务进行分析,包括main函数、NetlinkManager、NetlinkHandler、处理block类型的uevent、处理MountService命令、VolumeManager、NativeDaemonConnector等源码,很快就会与大家见面,感谢支持,欢迎交流与指正!

 

Android的存储系统—Vold与MountService分析(二)

Android的存储系统(二)

回顾:前贴主要分析了Android存储系统的架构和原理图,简要的介绍了整个从Kernel-->Vold-->上层MountService之间的数据传输流程,在这样的基础上,我们开始今天的源码分析!

【源码分析】

1. Vold的main函数

  Vold也是通过init进程启动,它在init.rc中的定义如下:

1 service vold /system/bin/vold 2 class core 3 socket vold stream 0660 root mount 4 ioprio be 2

  Vold服务放到了core分组,这就意味着系统启动时,它就会被init进程启动。这里定义的一个socket,主要用语Vold和Java层的MountService通信

   Vold模块的源代码位于system/vold,我们看看入口函数main(),代码如下:

1 int main() { 2 VolumeManager *vm; 3 CommandListener *cl; 4 NetlinkManager *nm; 5 6 SLOGI("Vold 2.1 (the revenge) firing up"); 7 8 mkdir("/dev/block/vold", 0755); // 创建vold目录 9 10 klog_set_level(6); 11 12 if (!(vm = VolumeManager::Instance())) { // 创建VolumeManager对象 13 exit(1); 14 }; 15 16 if (!(nm = NetlinkManager::Instance())) { // 创建NetlinkManager对象 17 exit(1); 18 }; 19 22 cl = new CommandListener(); // 创建CommandListener对象 23 vm->setBroadcaster((SocketListener *) cl); // 建立vm和cl的联系 24 nm->setBroadcaster((SocketListener *) cl); // 建立nm和cl的联系 25 26 if (vm->start()) { // 启动VolumeManager 27 exit(1); 28 } 29 30 if (process_config(vm)) { // 创建文件/fstab.xxx中定义的Volume对象 31 SLOGE("Error reading configuration (%s)... continuing anyways", strerror(errno)); 32 } 33 34 cryptfs_pfe_boot(); 35 36 if (nm->start()) { // 启动NetlinkManager,会调用NetlinkManager的start()方法,它创建PF_NETLINK socket,并开启线程从此socket中读取数据 37 exit(1); 38 } 39 40 coldboot("/sys/block"); // 冷启动,创建/sys/block下的节点文件 41 42 if (cl->startListener()) { // 开始监听Framework的socket 43 exit(1); 44 } 45 46 while(1) { // 进入循环 47 sleep(1000); // 主线程进入休眠 48 } 4950 SLOGI("Vold exiting"); 51 exit(0); 52 }

   main函数的主要工作是创建3个对象:VolumeManager、NetlinkManager和CommandListener,同时将CommandListener对象分别设置到了VolumeManager对象和NetlinkManager对象中。

  从前贴的架构图中可以发现,CommandListener对象用于和Java层的NativeDaemonConnector对象进行socket通信,因此,无论是VolumeManager对象还是NetlinkManager对象都需要拥有CommandListener对象的引用。

2. 监听驱动发出的消息—Vold的NetlinkManager对象

  NetlinkManager对象的主要工作是监听驱动发出的uevent消息。

  main()函数中调用NetlinkManager类的静态函数Instance()来创建NetlinkManager对象,代码如下:

1 NetlinkManager *NetlinkManager::Instance() { 2 if (!sInstance) 3 sInstance = new NetlinkManager(); // NetlinkManager对象通过静态变量sInstance来引用,这意味着vold进程中只有一个NetlinkManager对象。 4 return sInstance; 5

  看下NetlinkManager的构造函数,代码如下:

1 NetlinkManager::NetlinkManager() { 2 mBroadcaster = NULL; 3

  NetlinkManager的构造函数只是对mBroadcaster进行了初始化。我们可以发现main()函数中通过调用NetlinkManager的setBroadcaster()函数来给变量mBroadcaster重新赋值。

nm->setBroadcaster((SocketListener *) cl);

  main()函数还调用了NetlinkManager的start()函数,我们观察一下NetlinkManager中的start()方法,代码如下:

1 int NetlinkManager::start() { 2 struct sockaddr_nl nladdr; 3 int sz = 64 * 1024; 4 int on = 1; 5 6 memset(&nladdr, 0, sizeof(nladdr)); 7 nladdr.nl_family = AF_NETLINK; 8 nladdr.nl_pid = getpid(); 9 nladdr.nl_groups = 0xffffffff; 10 /*创建一个socket用于内核空间和用户空间的异步通信,监控系统的hotplug事件*/ 11 if ((mSock = socket(PF_NETLINK,SOCK_DGRAM,NETLINK_KOBJECT_UEVENT)) < 0) { 12 SLOGE("Unable to create uevent socket: %s", strerror(errno)); 13 return -1; 14 } 15 /*设置缓冲区大小为64KB*/ 16 if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) { 17 SLOGE("Unable to set uevent socket SO_RCVBUFFORCE option: %s", strerror(errno)); 18 goto out; 19 } 20 /*设置允许 SCM_CREDENTIALS 控制消息的接收*/ 21 if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) { 22 SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno)); 23 goto out; 24 } 25 /*绑定 socket 地址*/ 26 if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) { 27 SLOGE("Unable to bind uevent socket: %s", strerror(errno)); 28 goto out; 29 } 30 /*利用新创建的socket实例化一个NetlinkHandler类对象用于监听socket,NetlinkHandler继承了类NetlinkListener,NetlinkListener又继承了类SocketListener*/ 31 mHandler = new NetlinkHandler(mSock); 32 if (mHandler->start()) { // 启动NetlinkHandler,调用NetlinkHandler的start()函数 33 SLOGE("Unable to start NetlinkHandler: %s", strerror(errno)); 34 goto out; 35 } 36 37 return 0; 38 39 out: 40 close(mSock); 41 return -1; 42 }

   我们看一下NetlinkManager的家族关系,如下图:

  上面的虚线为启动时的调用流程:

  (1) class NetlinkManager(在其start函数中创建了NetlinkHandler对象,并把创建的socket作为参数)

  (2)class NetlinkHandler: public NetlinkListener(实现了onEvent)

  (3) class NetlinkListener : public SocketListener(实现了onDataAvailable)

  (4) class SocketListener(实现了runListener,在一个线程中通过select查看哪些socket有数据,通过调用onDataAvailable来读取数据)。

总结:此贴主要分析了Vold的main()函数和NetlinkManager对象的源码,通过源码了解对象的创建时机和函数调用流程,下一贴会继续从NetlinkHandler的start()方法深入分析,继续源码的学习,很快会与大家见面,欢迎大家批评指正,我们互相学习。

Android的存储系统—Vold与MountService分析(三)

Android的存储系统(三)

回顾:前帖分析了Vold的main()函数和NetlinkManager的函数调用流程,截止到NetlinkHandler的创建和start()调用,本帖继续分析源码

 

 

1、处理block类型的uevent

  main()函数创建了CommandListener对象,NetlinkManager的start()函数又创建了NetlinkHandler对象,如果将CommandListener类和NetlinkHandler类的继承关系图画出来,会发现它们都是从SocketListener类派生出来的,如下图所示:

 

图1 NetlinkHandler和CommandListener的继承关系

  原理:处于最底层的SocketListener类的作用是监听socket的数据,接收到数据后分别交给FrameworkListener类和NetlinkListener类的函数,并分别对来自Framework和驱动的数据进行分析,分析后根据命令再分别调用CommandListener和NetlinkHandler中的函数。

  观察NetlinkHandler类的构造方法,代码如下:

NetlinkHandler::NetlinkHandler(int listenerSocket) : NetlinkListener(listenerSocket) { }

  这个构造方法很简单,再看看它的start()方法,代码如下:

int NetlinkHandler::start() { return this->startListener(); }

  可以发现,start()方法调用了SocketListener的startListener()函数,代码如下:

1 int SocketListener::startListener(int backlog) { 2 if (!mSocketName && mSock == -1) { 3 SLOGE("Failed to start unbound listener"); 4 errno = EINVAL; 5 return -1; 6 } else if (mSocketName) { // 只有CommandListener中会设置mSocketName 7 if ((mSock = android_get_control_socket(mSocketName)) < 0) { 8 SLOGE("Obtaining file descriptor socket '%s' failed: %s",mSocketName, strerror(errno)); 9 return -1; 10 } 11 SLOGV("got mSock = %d for %s", mSock, mSocketName); 12 } 13 14 if (mListen && listen(mSock, backlog) < 0) { 15 SLOGE("Unable to listen on socket (%s)", strerror(errno)); 16 return -1; 17 } else if (!mListen) 18 mClients->push_back(new SocketClient(mSock, false, mUseCmdNum)); 19 20 if (pipe(mCtrlPipe)) { // 创建管道,用于退出监听线程 21 SLOGE("pipe failed (%s)", strerror(errno)); 22 return -1; 23 } 24 25 if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) { // 创建一个监听线程 26 SLOGE("pthread_create (%s)", strerror(errno)); 27 return -1; 28 } 29 30 return 0; 31 }

  startListener()函数开始监听socket,这个函数在NetlinkHandler中会被调用,在CommandListener也会被调用。

  startListener()函数首先判断变量mSocketName是否有值,只有CommandListener对象会对这个变量赋值,它的值就是在init.rc中定义的socket字符串。

      调用函数 android_get_control_socket()的目的是从环境变量中取得socket的值,这样CommandListener对象得到了它需要监听的socket,

  而对于NetlinkHandler对象而言,它的mSocket不为NULL,前面已经创建了socket。

 

  startListener()函数接下来会根据成员变量mListener的值来判断是否需要调用Listen()函数来监听socket。这个mListen的值在对象构造时根据参数来初始化。

  对于CommandListener对象,mListener的值为ture,对于NetlinkHandler对象,mListener的值为false,这是因为CommandListener对象和SystemServer通信,需要监听socket连接,而NetlinkHandler对象则不用。

  

  接下来startListener()函数会创建一个管道,这个管道的作用是通知线程停止监听,这个线程就是startListener()函数最后创建的监听线程,它的运行函数是threadStart(),在前贴的NetlinkManager家族图系中我们可以清晰的发现,其代码如下:

void *SocketListener::threadStart(void *obj) { SocketListener *me = reinterpret_cast<SocketListener *>(obj); me->runListener(); // 调用runListener()方法 pthread_exit(NULL); return NULL; }

  threadStart()中又调用了runListener()函数,代码如下:

1 void SocketListener::runListener() { 2 3 SocketClientCollection pendingList; 4 5 while(1) { // 无限循环,一直监听 6 SocketClientCollection::iterator it; 7 fd_set read_fds; 8 int rc = 0; 9 int max = -1; 10 11 FD_ZERO(&read_fds); // 清空文件描述符集read_fds 12 13 if (mListen) { // 如果需要监听 14 max = mSock; 15 FD_SET(mSock, &read_fds); // 把mSock加入到read_fds 16 } 17 18 FD_SET(mCtrlPipe[0], &read_fds); // 把管道mCtrlPipe[0]也加入到read_fds 19 if (mCtrlPipe[0] > max) 20 max = mCtrlPipe[0]; 21 22 pthread_mutex_lock(&mClientsLock); // 对容器mClients的操作需要加锁 23 for (it = mClients->begin(); it != mClients->end(); ++it) { // mClient中保存的是NetlinkHandler对象的socket,或者CommandListener接入的socket 24 int fd = (*it)->getSocket(); 25 FD_SET(fd, &read_fds); // 遍历容器mClients的所有成员,调用内联函数getSocket()获取文件描述符,并添加到文件描述符集read_fds 26 if (fd > max) { // 也加入到read_fds 27 max = fd; 28 } 29 } 30 pthread_mutex_unlock(&mClientsLock); 31 SLOGV("mListen=%d, max=%d, mSocketName=%s", mListen, max, mSocketName); 32 if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) { // 执行select调用,开始等待socket上的数据到来 33 if (errno == EINTR) // 因为中断退出select,继续 34 continue; 35 SLOGE("select failed (%s) mListen=%d, max=%d", strerror(errno), mListen, max); 36 sleep(1); // select出错,休眠1秒后继续 37 continue; 38 } else if (!rc) 39 continue; // 如果fd上没有数据到达,继续 40 41 if (FD_ISSET(mCtrlPipe[0], &read_fds)) { 42 char c = CtrlPipe_Shutdown; 43 TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1)); 44 if (c == CtrlPipe_Shutdown) { 45 break; 46 } 47 continue; 48 } 49 if (mListen && FD_ISSET(mSock, &read_fds)) { // 如果是CommandListener对象上有连接请求 50 struct sockaddr addr; 51 socklen_t alen; 52 int c; 53 54 do { 55 alen = sizeof(addr); 56 c = accept(mSock, &addr, &alen); // 接入连接请求 57 SLOGV("%s got %d from accept", mSocketName, c); 58 } while (c < 0 && errno == EINTR); // 如果是中断导致失败,重新接入 59 if (c < 0) { 60 SLOGE("accept failed (%s)", strerror(errno)); 61 sleep(1); 62 continue; // 接入发生错误,继续循环 63 } 64 pthread_mutex_lock(&mClientsLock); 65 mClients->push_back(new SocketClient(c, true, mUseCmdNum)); // 把接入的socket连接加入到mClients,这样再循环时就会监听到它的数据到达 66 pthread_mutex_unlock(&mClientsLock); 67 } 68 69 /* Add all active clients to the pending list first */ 70 pendingList.clear(); 71 pthread_mutex_lock(&mClientsLock); 72 for (it = mClients->begin(); it != mClients->end(); ++it) { 73 SocketClient* c = *it; 74 int fd = c->getSocket(); 75 if (FD_ISSET(fd, &read_fds)) { 76 pendingList.push_back(c); // 如果mClients中的某个socket上有数据了,把它加入到pendingList列表中 77 c->incRef(); 78 } 79 } 80 pthread_mutex_unlock(&mClientsLock); 81 82 /* Process the pending list, since it is owned by the thread,* there is no need to lock it */ 83 while (!pendingList.empty()) { // 处理pendingList列表 84 /* Pop the first item from the list */ 85 it = pendingList.begin(); 86 SocketClient* c = *it; 87 pendingList.erase(it); // 把处理了的socket从pendingList列表中删除 88 /* Process it, if false is returned, remove from list */ 89 if (!onDataAvailable(c)) { 90 release(c, false); // 调用release()函数-->调用onDataAvailable()方法 91 } 92 c->decRef(); 93 } 94 } 95 }

  SocketListener::runListener是线程真正执行的函数。

  以上runListener()函数虽然比较长,但这是一段标准的处理混合socket连接的代码,对于我们编写socket的程序大有帮助,这里先做简单了解。

 

  <--------接下来,我们继续分析......-------->

 

  runListener()函数收到从驱动传递的数据或者MountService传递的数据后,调用onDataAvailable()函数来处理,FrameworkListener类和NetlinkListener类都会重载这个函数。

  首先来分析一下NetlinkListener类的onDataAvailable()函数是如何实现的!

  直接上代码:

1 bool NetlinkListener::onDataAvailable(SocketClient *cli) 2 { 3 int socket = cli->getSocket(); 4 ssize_t count; 5 uid_t uid = -1; 6 /*从socket中读取kernel发送来的uevent消息*/ 7 count = TEMP_FAILURE_RETRY(uevent_kernel_multicast_uid_recv(socket, mBuffer, sizeof(mBuffer), &uid)); 8 if (count < 0) { // 如果count<0,进行错误处理 9 if (uid > 0) 10 LOG_EVENT_INT(65537, uid); 11 return false; 12 } 13 14 NetlinkEvent *evt = new NetlinkEvent(); // 创建NetlinkEvent对象 15 if (evt->decode(mBuffer, count, mFormat)) { // 调用decode()函数 16 onEvent(evt); // 在NetlinkHandler中实现17 } else if (mFormat != NETLINK_FORMAT_BINARY) { 18 SLOGE("Error decoding NetlinkEvent"); 19 } 20 delete evt; 21 return true; 22 }

  NetlinkListener类的onDataAvailable()函数首先调用uevent_kernel_multicast_uid_recv()函数来接收uevent消息。

  接收到消息后,会创建NetlinkEvent对象,然后调用它的decode()函数对消息进行解码,然后用得到的消息数据给NetlinkEvent对象的成员变量赋值。

  最后onDataAvailable()函数调用了onEvent()函数继续处理消息,onEvent()函数的代码如下:

void NetlinkHandler::onEvent(NetlinkEvent *evt) { VolumeManager *vm = VolumeManager::Instance(); const char *subsys = evt->getSubsystem(); if (!subsys) { SLOGW("No subsystem found in netlink event"); return; } if (!strcmp(subsys, "block")) { vm->handleBlockEvent(evt); // 调用VolumeManager的handleBlockEvent()函数来处理 } }

  NetlinkHandler的onEvent()函数中会判断event属于哪个子系统的,如果属于“block”(SD热插拔),则调用VolumeManager的handleBlockEvent()函数来处理,代码如下:

void VolumeManager::handleBlockEvent(NetlinkEvent *evt) { const char *devpath = evt->findParam("DEVPATH"); VolumeCollection::iterator it; bool hit = false; for (it = mVolumes->begin(); it != mVolumes->end(); ++it) { if (!(*it)->handleBlockEvent(evt)) { // 对每个DirectVolume对象,调用它handleBlockEvent来处理这个event hit = true; // 如果某个Volume对象处理了Event,则返回 break; } } ..... }

总结:本帖的源码分析先到这里为止,下一贴再分析DirectVolume对象的handleBlockEvent()函数以及CommandListener对象如何处理从MountService发送的命令数据,即我们之前还没有讨论的关于FrameworkListener的onDataAvailable()函数的代码!

PS:希望对Android手机开发、IOS、以及游戏(纯兴趣,白菜)和Java EE感兴趣的码友们互粉,这样我也能及时的看到你们的大神之作和经验之贴,感谢感谢!

原文地址 http://www.cnblogs.com/pepsimaxin/p/5195842.html

转载请注明原文地址: https://www.6miu.com/read-2998.html

最新回复(0)