Android 2.3 SD卡挂载流程浅析(四)

前面的三篇博文《Android 2.3 SD卡挂载流程浅析(一)》Android 2.3 SD卡挂载流程浅析(二)Android 2.3 SD卡挂载流程浅析(三)的分析,知道了SD卡挂载的消息是如何从底层传递到上层的,在《Android 2.3 SD卡挂载流程浅析(三)》中,我们已经知道了最后是在updatePublicVolumeState()中调用onStorageStateChanged(),从而达到更新SD卡挂载信息的。在本文《Android 2.3 SD卡挂载流程浅析(四)》中,我会将前文提到的程序调用流程图画出来,并对代码进行简单的分析。

      首先,还是挂出这张老图(因为每次都用这张图0_0...)。

       就权当复习吧,这是SD卡的整个挂载流程,而程序的调用也是根据这个流程图来的。

       1.接收并处理uevent

       首先是接收因为插入SD卡被内核检测到而发出的Event;

       NetlinkHandler::onEvent(NetlinkEvent *evt)

//代码路径:AndroidSourcecode2.3/system/vold/NetlinkHandler.cpp

//该方法主要通过evt->getSubsystem();方法来获取系统的event

  1. void NetlinkHandler::onEvent(NetlinkEvent *evt) {  
  2.     VolumeManager *vm = VolumeManager::Instance();  
  3.     const char *subsys = evt->getSubsystem();  
  4.   
  5.     if (!subsys) {  
  6.         SLOGW("No subsystem found in netlink event");  
  7.         return;  
  8.     }  
  9.   
  10.     if (!strcmp(subsys, "block")) {  
  11.         vm->handleBlockEvent(evt);  
  12.     } else if (!strcmp(subsys, "switch")) {  
  13.         vm->handleSwitchEvent(evt);  
  14.     } else if (!strcmp(subsys, "usb_composite")) {  
  15.         vm->handleUsbCompositeEvent(evt);  
  16.     } else if (!strcmp(subsys, "battery")) {  
  17.     } else if (!strcmp(subsys, "power_supply")) {  
  18.     }  
  19. }  
      2.对SD卡挂载事件开始处理

      void VolumeManager::handleBlockEvent(NetlinkEvent *evt)

//代码路径:AndroidSourcecode2.3/system/vold/VolumeManager.cpp

//该方法的主要作用是:
//第一,    遍历mPath容器,寻找与event对应的sysfs_path是否存在与mPath容器中。
//第二,    针对Event中的action有4种处理方式:Add,Removed,Change,Noaction。

  1. void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {  
  2.     const char *devpath = evt->findParam("DEVPATH");  
  3.   
  4.     /* Lookup a volume to handle this device */  
  5.     VolumeCollection::iterator it;  
  6.     bool hit = false;  
  7.     for (it = mVolumes->begin(); it != mVolumes->end(); ++it) {  
  8.         if (!(*it)->handleBlockEvent(evt)) {  
  9. #ifdef NETLINK_DEBUG  
  10.             SLOGD("Device '%s' event handled by volume %s\n", devpath, (*it)->getLabel());  
  11. #endif  
  12.             hit = true;  
  13.             break;  
  14.         }  
  15.     }  
  16.   
  17.     if (!hit) {  
  18. #ifdef NETLINK_DEBUG  
  19.         SLOGW("No volumes handled block event for '%s'", devpath);  
  20. #endif  
  21.     }  
  22. }  
     3.对Block挂载事件进行处理

     DirectVolume::handleBlockEvent(NetlinkEvent *evt)

//代码路径:AndroidSourcecode2.3/system/vold/DirectVolume.cpp

//在Add action中首先会创建设备节点,然后对disk和partion两种格式的设备分别进行处理。这里是disk格式。

  1. int DirectVolume::handleBlockEvent(NetlinkEvent *evt) {  
  2.     const char *dp = evt->findParam("DEVPATH");  
  3.   
  4.     PathCollection::iterator  it;  
  5.     for (it = mPaths->begin(); it != mPaths->end(); ++it) {  
  6.         if (!strncmp(dp, *it, strlen(*it))) {  
  7.             /* We can handle this disk */  
  8.             int action = evt->getAction();  
  9.             const char *devtype = evt->findParam("DEVTYPE");  
  10.   
  11.             if (action == NetlinkEvent::NlActionAdd) {  
  12.                 int major = atoi(evt->findParam("MAJOR"));  
  13.                 int minor = atoi(evt->findParam("MINOR"));  
  14.                 char nodepath[255];  
  15.   
  16.                 snprintf(nodepath,  
  17.                          sizeof(nodepath), "/dev/block/vold/%d:%d",  
  18.                          major, minor);  
  19.                 if (createDeviceNode(nodepath, major, minor)) {  
  20.                     SLOGE("Error making device node '%s' (%s)", nodepath,  
  21.                                                                strerror(errno));  
  22.                 }  
  23.                 if (!strcmp(devtype, "disk")) {  
  24.                     <span style="color:#ff0000;">handleDiskAdded(dp, evt);<span style="color:#33cc00;">//SD卡插入是Add事件</span></span>  
  25.                 } else {  
  26.                     handlePartitionAdded(dp, evt);  
  27.                 }  
  28.             } else if (action == NetlinkEvent::NlActionRemove) {  
  29.                 if (!strcmp(devtype, "disk")) {  
  30.                     handleDiskRemoved(dp, evt);  
  31.                 } else {  
  32.                     handlePartitionRemoved(dp, evt);  
  33.                 }  
  34.             } else if (action == NetlinkEvent::NlActionChange) {  
  35.                 if (!strcmp(devtype, "disk")) {  
  36.                     handleDiskChanged(dp, evt);  
  37.                 } else {  
  38.                     handlePartitionChanged(dp, evt);  
  39.                 }  
  40.             } else {  
  41.                     SLOGW("Ignoring non add/remove/change event");  
  42.             }  
  43.   
  44.             return 0;  
  45.         }  
  46.     }  
  47.     errno = ENODEV;  
  48.     return -1;  
  49. }  
      4.处理DiskAdd事件

DirectVolume::handleDiskAdded(const char *devpath, NetlinkEvent *evt)

//代码路径:AndroidSourcecode2.3/system/vold/DirectVolume.cpp

//在该方法中广播disk insert的广播消息(这里的广播不同于Java中的广播,这里实际上是Socket)。

  1. void DirectVolume::handleDiskAdded(const char *devpath, NetlinkEvent *evt) {  
  2.     mDiskMajor = atoi(evt->findParam("MAJOR"));  
  3.     mDiskMinor = atoi(evt->findParam("MINOR"));  
  4.   
  5.     const char *tmp = evt->findParam("NPARTS");  
  6.     if (tmp) {  
  7.         mDiskNumParts = atoi(tmp);  
  8.     } else {  
  9.         SLOGW("Kernel block uevent missing 'NPARTS'");  
  10.         mDiskNumParts = 1;  
  11.     }  
  12.   
  13.     char msg[255];  
  14.   
  15.     int partmask = 0;  
  16.     int i;  
  17.     for (i = 1; i <= mDiskNumParts; i++) {  
  18.         partmask |= (1 << i);  
  19.     }  
  20.     mPendingPartMap = partmask;  
  21.   
  22.     if (mDiskNumParts == 0) {  
  23. #ifdef PARTITION_DEBUG  
  24.         SLOGD("Dv::diskIns - No partitions - good to go son!");  
  25. #endif  
  26.         setState(Volume::State_Idle);  
  27.     } else {  
  28. #ifdef PARTITION_DEBUG  
  29.         SLOGD("Dv::diskIns - waiting for %d partitions (mask 0x%x)",  
  30.              mDiskNumParts, mPendingPartMap);  
  31. #endif  
  32.         setState(Volume::State_Pending);  
  33.     }  
  34.   
  35.     snprintf(msg, sizeof(msg), "Volume %s %s disk inserted (%d:%d)",  
  36.              getLabel(), getMountpoint(), mDiskMajor, mDiskMinor);  
  37.     <span style="color:#ff0000;">mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeDiskInserted,  
  38.                                              msg, false);  
  39. </span>}  
      5.处理广播消息
       SocketListener::runListener()

//代码路径:AndroidSourcecode2.3/system/core/libsysutils/src/SocketListener.cpp

//完成对Socket的监听以及对数据的处理onDataAvailable(* it );

  1. void SocketListener::runListener() {  
  2.   
  3.     while(1) {  
  4.         SocketClientCollection::iterator it;  
  5.         fd_set read_fds;  
  6.         int rc = 0;  
  7.         int max = 0;  
  8.   
  9.         FD_ZERO(&read_fds);  
  10.   
  11.         if (mListen) {  
  12.             max = mSock;  
  13.             FD_SET(mSock, &read_fds);  
  14.         }  
  15.   
  16.         FD_SET(mCtrlPipe[0], &read_fds);  
  17.         if (mCtrlPipe[0] > max)  
  18.             max = mCtrlPipe[0];  
  19.   
  20.         pthread_mutex_lock(&mClientsLock);  
  21.         for (it = mClients->begin(); it != mClients->end(); ++it) {  
  22.             FD_SET((*it)->getSocket(), &read_fds);  
  23.             if ((*it)->getSocket() > max)  
  24.                 max = (*it)->getSocket();  
  25.         }  
  26.         pthread_mutex_unlock(&mClientsLock);  
  27.   
  28.         if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {  
  29.             SLOGE("select failed (%s)", strerror(errno));  
  30.             sleep(1);  
  31.             continue;  
  32.         } else if (!rc)  
  33.             continue;  
  34.   
  35.         if (FD_ISSET(mCtrlPipe[0], &read_fds))  
  36.             break;  
  37.         if (mListen && FD_ISSET(mSock, &read_fds)) {  
  38.             struct sockaddr addr;  
  39.             socklen_t alen = sizeof(addr);  
  40.             int c;  
  41.   
  42.             if ((c = accept(mSock, &addr, &alen)) < 0) {  
  43.                 SLOGE("accept failed (%s)", strerror(errno));  
  44.                 sleep(1);  
  45.                 continue;  
  46.             }  
  47.             pthread_mutex_lock(&mClientsLock);  
  48.             mClients->push_back(new SocketClient(c));  
  49.             pthread_mutex_unlock(&mClientsLock);  
  50.         }  
  51.   
  52.         do {  
  53.             pthread_mutex_lock(&mClientsLock);  
  54.             for (it = mClients->begin(); it != mClients->end(); ++it) {  
  55.                 int fd = (*it)->getSocket();  
  56.                 if (FD_ISSET(fd, &read_fds)) {  
  57.                     pthread_mutex_unlock(&mClientsLock);  
  58.                     if (!<span style="color:#ff0000;">onDataAvailable(*it)</span>) {  
  59.                         close(fd);  
  60.                         pthread_mutex_lock(&mClientsLock);  
  61.                         delete *it;  
  62.                         it = mClients->erase(it);  
  63.                         pthread_mutex_unlock(&mClientsLock);  
  64.                     }  
  65.                     FD_CLR(fd, &read_fds);  
  66.                     pthread_mutex_lock(&mClientsLock);  
  67.                     continue;  
  68.                 }  
  69.             }  
  70.             pthread_mutex_unlock(&mClientsLock);  
  71.         } while (0);  
  72.     }  
  73. }  
      6.处理消息内容

      FrameworkListener::onDataAvailable(SocketClient *c)

//代码路径:AndroidSourcecode2.3/system/core/libsysutils/src/FrameworkListener.cpp

//对接收到的广播消息进行处理

  1. bool FrameworkListener::onDataAvailable(SocketClient *c) {  
  2.     char buffer[255];  
  3.     int len;  
  4.   
  5.     if ((len = read(c->getSocket(), buffer, sizeof(buffer) -1)) < 0) {  
  6.         SLOGE("read() failed (%s)", strerror(errno));  
  7.         return false;  
  8.     } else if (!len)  
  9.         return false;  
  10.   
  11.     int offset = 0;  
  12.     int i;  
  13.   
  14.     for (i = 0; i < len; i++) {  
  15.         if (buffer[i] == '\0') {  
  16.             <span style="color:#ff0000;">dispatchCommand</span>(c, buffer + offset);  
  17.             offset = i + 1;  
  18.         }  
  19.     }  
  20.     return true;  
  21. }  
      7.分发指令

      FrameworkListener::dispatchCommand(SocketClient *cli, char *data)

//代码路径:AndroidSourcecode2.3/system/core/libsysutils/src/FrameworkListener.cpp

//分配指令:DumpCmd、VolumeCmd、AsecCmd、ShareCmd、StorageCmd、XwarpCmd

  1. void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {  
  2.     FrameworkCommandCollection::iterator i;  
  3.     int argc = 0;  
  4.     char *argv[FrameworkListener::CMD_ARGS_MAX];  
  5.     char tmp[255];  
  6.     char *p = data;  
  7.     char *q = tmp;  
  8.     bool esc = false;  
  9.     bool quote = false;  
  10.     int k;  
  11.   
  12.     memset(argv, 0, sizeof(argv));  
  13.     memset(tmp, 0, sizeof(tmp));  
  14.     while(*p) {  
  15.         if (*p == '\\') {  
  16.             if (esc) {  
  17.                 *q++ = '\\';  
  18.                 esc = false;  
  19.             } else  
  20.                 esc = true;  
  21.             p++;  
  22.             continue;  
  23.         } else if (esc) {  
  24.             if (*p == '"')  
  25.                 *q++ = '"';  
  26.             else if (*p == '\\')  
  27.                 *q++ = '\\';  
  28.             else {  
  29.                 cli->sendMsg(500, "Unsupported escape sequence"false);  
  30.                 goto out;  
  31.             }  
  32.             p++;  
  33.             esc = false;  
  34.             continue;  
  35.         }  
  36.   
  37.         if (*p == '"') {  
  38.             if (quote)  
  39.                 quote = false;  
  40.             else  
  41.                 quote = true;  
  42.             p++;  
  43.             continue;  
  44.         }  
  45.   
  46.         *q = *p++;  
  47.         if (!quote && *q == ' ') {  
  48.             *q = '\0';  
  49.             argv[argc++] = strdup(tmp);  
  50.             memset(tmp, 0, sizeof(tmp));  
  51.             q = tmp;  
  52.             continue;  
  53.         }  
  54.         q++;  
  55.     }  
  56.   
  57.     argv[argc++] = strdup(tmp);  
  58. #if 0  
  59.     for (k = 0; k < argc; k++) {  
  60.         SLOGD("arg[%d] = '%s'", k, argv[k]);  
  61.     }  
  62. #endif  
  63.   
  64.     if (quote) {  
  65.         cli->sendMsg(500, "Unclosed quotes error"false);  
  66.         goto out;  
  67.     }  
  68.       
  69.     for (i = mCommands->begin(); i != mCommands->end(); ++i) {  
  70.         FrameworkCommand *c = *i;  
  71.   
  72.         if (!strcmp(argv[0], c->getCommand())) {  
  73.             if (c-><span style="color:#ff0000;">runCommand</span>(cli, argc, argv)) {  
  74.                 SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));  
  75.             }  
  76.             goto out;  
  77.         }  
  78.     }  
  79.   
  80.     cli->sendMsg(500, "Command not recognized"false);  
  81. out:  
  82.     int j;  
  83.     for (j = 0; j < argc; j++)  
  84.         free(argv[j]);  
  85.     return;  
  86. }  
     8.开始执行挂载

     CommandListener::VolumeCmd::runCommand(SocketClient *cli,int argc, char **argv)

//代码路径:AndroidSourcecode2.3/system/vold/CommandListener.cpp

//rc = vm->mountVolume(argv[2]);

  1. int CommandListener::VolumeCmd::runCommand(SocketClient *cli,int argc, char **argv) {  
  2.     dumpArgs(argc, argv, -1);  
  3.   
  4.     if (argc < 2) {  
  5.         cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing Argument"false);  
  6.         return 0;  
  7.     }  
  8.   
  9.     VolumeManager *vm = VolumeManager::Instance();  
  10.     int rc = 0;  
  11.   
  12.     if (!strcmp(argv[1], "list")) {  
  13.         return vm->listVolumes(cli);  
  14.     } else if (!strcmp(argv[1], "debug")) {  
  15.         if (argc != 3 || (argc == 3 && (strcmp(argv[2], "off") && strcmp(argv[2], "on")))) {  
  16.             cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume debug <off/on>"false);  
  17.             return 0;  
  18.         }  
  19.         vm->setDebug(!strcmp(argv[2], "on") ? true : false);  
  20.     } else if (!strcmp(argv[1], "mount")) {  
  21.         if (argc != 3) {  
  22.             cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume mount <path>"false);  
  23.             return 0;  
  24.         }  
  25.         <span style="color:#ff0000;">rc = vm->mountVolume(argv[2]);</span>  
  26.     } else if (!strcmp(argv[1], "unmount")) {  
  27.         if (argc < 3 || argc > 4 || (argc == 4 && strcmp(argv[3], "force"))) {  
  28.             cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume unmount <path> [force]"false);  
  29.             return 0;  
  30.         }  
  31.   
  32.         bool force = false;  
  33.         if (argc >= 4 && !strcmp(argv[3], "force")) {  
  34.             force = true;  
  35.         }  
  36.         rc = vm->unmountVolume(argv[2], force);  
  37.     } else if (!strcmp(argv[1], "format")) {  
  38.         if (argc != 3) {  
  39.             cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume format <path>"false);  
  40.             return 0;  
  41.         }  
  42.         rc = vm->formatVolume(argv[2]);  
  43.     } else if (!strcmp(argv[1], "share")) {  
  44.         if (argc != 4) {  
  45.             cli->sendMsg(ResponseCode::CommandSyntaxError,  
  46.                     "Usage: volume share <path> <method>"false);  
  47.             return 0;  
  48.         }  
  49.         rc = vm->shareVolume(argv[2], argv[3]);  
  50.     } else if (!strcmp(argv[1], "unshare")) {  
  51.         if (argc != 4) {  
  52.             cli->sendMsg(ResponseCode::CommandSyntaxError,  
  53.                     "Usage: volume unshare <path> <method>"false);  
  54.             return 0;  
  55.         }  
  56.         rc = vm->unshareVolume(argv[2], argv[3]);  
  57.     } else if (!strcmp(argv[1], "shared")) {  
  58.         bool enabled = false;  
  59.         if (argc != 4) {  
  60.             cli->sendMsg(ResponseCode::CommandSyntaxError,  
  61.                     "Usage: volume shared <path> <method>"false);  
  62.             return 0;  
  63.         }  
  64.   
  65.         if (vm->shareEnabled(argv[2], argv[3], &enabled)) {  
  66.             cli->sendMsg(  
  67.                     ResponseCode::OperationFailed, "Failed to determine share enable state"true);  
  68.         } else {  
  69.             cli->sendMsg(ResponseCode::ShareEnabledResult,  
  70.                     (enabled ? "Share enabled" : "Share disabled"), false);  
  71.         }  
  72.         return 0;  
  73.     } else {  
  74.         cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume cmd"false);  
  75.     }  
  76.   
  77.     if (!rc) {  
  78.         cli->sendMsg(ResponseCode::CommandOkay, "volume operation succeeded"false);  
  79.     } else {  
  80.         int erno = errno;  
  81.         rc = ResponseCode::convertFromErrno();  
  82.         cli->sendMsg(rc, "volume operation failed"true);  
  83.     }  
  84.   
  85.     return 0;  
  86. }  
      9.调用方法执行挂载动作

      VolumeManager::mountVolume(const char *label)

//代码路径:AndroidSourcecode2.3/system/vold/VolumeManager.cpp
//v->mountVol();

  1. int VolumeManager::mountVolume(const char *label) {  
  2.     Volume *v = lookupVolume(label);  
  3.   
  4.     if (!v) {  
  5.         errno = ENOENT;  
  6.         return -1;  
  7.     }  
  8.   
  9.     return <span style="color:#ff0000;">v->mountVol();  
  10. </span>}  
    10.真正的挂载者

    Volume::mountVol()

//代码路径:AndroidSourcecode2.3/system/vold/Volume.cpp

//SD卡的挂载最终是在该方法中实现的,如果挂载有异常,也会在该方法的执行过程通过setState方法发出广播。

  1. int Volume::mountVol() {  
  2.     dev_t deviceNodes[4];  
  3.     int n, i, rc = 0;  
  4.     char errmsg[255];  
  5.   
  6.     if (getState() == Volume::State_NoMedia) {  
  7.         snprintf(errmsg, sizeof(errmsg),  
  8.                  "Volume %s %s mount failed - no media",  
  9.                  getLabel(), getMountpoint());  
  10.         mVm->getBroadcaster()->sendBroadcast(  
  11.                                          ResponseCode::VolumeMountFailedNoMedia,  
  12.                                          errmsg, false);  
  13.         errno = ENODEV;  
  14.         return -1;  
  15.     } else if (getState() != Volume::State_Idle) {  
  16.         errno = EBUSY;  
  17.         return -1;  
  18.     }  
  19.   
  20.     if (isMountpointMounted(getMountpoint())) {  
  21.         SLOGW("Volume is idle but appears to be mounted - fixing");  
  22.         setState(Volume::State_Mounted);  
  23.         // mCurrentlyMountedKdev = XXX  
  24.         return 0;  
  25.     }  
  26.   
  27.     n = getDeviceNodes((dev_t *) &deviceNodes, 4);  
  28.     if (!n) {  
  29.         SLOGE("Failed to get device nodes (%s)\n", strerror(errno));  
  30.         return -1;  
  31.     }  
  32.   
  33.     for (i = 0; i < n; i++) {  
  34.         char devicePath[255];  
  35.   
  36.         sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(deviceNodes[i]),  
  37.                 MINOR(deviceNodes[i]));  
  38.   
  39.         SLOGI("%s being considered for volume %s\n", devicePath, getLabel());  
  40.   
  41.         errno = 0;  
  42.         setState(Volume::State_Checking);  
  43.   
  44.         if (Fat::check(devicePath)) {//如果SD卡的格式不对是通不过这里的检测的  
  45.             if (errno == ENODATA) {  
  46.                 SLOGW("%s does not contain a FAT filesystem\n", devicePath);  
  47.                 continue;  
  48.             }  
  49.             errno = EIO;  
  50.             /* Badness - abort the mount */  
  51.             SLOGE("%s failed FS checks (%s)", devicePath, strerror(errno));  
  52.             setState(Volume::State_Idle);  
  53.             return -1;  
  54.         }  
  55.   
  56.         /* 
  57.          * Mount the device on our internal staging mountpoint so we can 
  58.          * muck with it before exposing it to non priviledged users. 
  59.          */  
  60.         errno = 0;  
  61.         if (Fat::doMount(devicePath, "/mnt/secure/staging"falsefalsefalse,  
  62.                 1000, 1015, 0702, true)) {//真正的挂载方法doMount  
  63.             SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));  
  64.             continue;  
  65.         }  
  66.   
  67.         SLOGI("Device %s, target %s mounted @ /mnt/secure/staging", devicePath, getMountpoint());  
  68.   
  69.         protectFromAutorunStupidity();  
  70.   
  71.         if (createBindMounts()) {  
  72.             SLOGE("Failed to create bindmounts (%s)", strerror(errno));  
  73.             umount("/mnt/secure/staging");  
  74.             setState(Volume::State_Idle);  
  75.             return -1;  
  76.         }  
  77.   
  78.         /* 
  79.          * Now that the bindmount trickery is done, atomically move the 
  80.          * whole subtree to expose it to non priviledged users. 
  81.          */  
  82.         if (doMoveMount("/mnt/secure/staging", getMountpoint(), false)) {  
  83.             SLOGE("Failed to move mount (%s)", strerror(errno));  
  84.             umount("/mnt/secure/staging");  
  85.             setState(Volume::State_Idle);  
  86.             return -1;  
  87.         }  
  88.         setState(Volume::State_Mounted);  
  89.         mCurrentlyMountedKdev = deviceNodes[i];  
  90.         return 0;  
  91.     }  
  92.   
  93.     SLOGE("Volume %s found no suitable devices for mounting :(\n", getLabel());  
  94.     setState(Volume::State_Idle);  
  95.   
  96.     return -1;  
  97. }  


      到这里SD卡的挂载基本上已经完成,但是我们的目的是理清一条从底向上的路线,因此我会继续向上分析。我会将这一点在下一篇博文《Android 2.3 SD卡挂载流程浅析(五)》中继续分析。

     因为这些都是我个人的一些见解,因此不见得都正确,希望读者抱着怀疑的态度阅读。不过不是有句名言叫“大胆假设,小心求证”吗!如文中有错误,还恳请各位看官指正。



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值