TShopping

 找回密碼
 註冊
搜索
查看: 3593|回復: 1
打印 上一主題 下一主題

[教學] Android SDCard Mount流程分析(一)(二)(三)

  [複製鏈接]
跳轉到指定樓層
1#
發表於 2013-12-14 19:16:21 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
 
Push to Facebook
前段時間對Android 的SDCard unmount 流程進行了幾篇簡短的分析,由於當時只是紙上談兵,沒有實際上的跟進,可能會有一些誤導人或者小錯誤。今天重新梳理了頭緒,針對mount的流程再重新分析一次。
  本篇大綱
  
‧ android 系統如何開機啟動監聽mount服務
‧ 默認掛載點在Android 系統的哪個目錄
‧ vold.fstab 配置文件的分析
‧ vold 裡面啟動頁面main做了些什麼
android 系統如何開機啟動監聽mount服務
android sdcard 熱插拔監測和執行操作是由一個啟動文件vold  所統領的,系統開機會讀取初始化配置文件init.rc,該文件位於比如我的板子是:device/ti/omap3evm/init.rc,具體根據自己平台查找。裡面有一個是默認啟動vold 服務的代碼,如下:
  1.      service vold /system/bin/vold
  2.      socket vold stream 0660 root mount
  3.      ioprio be 2
複製代碼
如果要對該文件做出修改之類,要重新編一下boot.img 鏡像文件,燒錄進android 系統,之後可以在android的文件系統根目錄找到init.rc文件。上述代碼為啟動vold 啟動文件,也可以在init.rc 增加多一些我們想要的文件目錄,比如增加一個可以存放多分區掛載的目錄等,這個是後話。
  
  默認掛載點在Android 系統的哪個目錄
  usbdisk 或者 sdcard 熱插拔的時候,kernel 會發出命令執行mount或者unmount 操作,但這都是驅動級的。而mount 目錄會在android 的文件系統目錄下:/dev/block/vold 這個目錄由vold 生成,用來存放所有的usbdisk 或者 sdcard 的掛載點。代碼位於main裡面最優先執行:
  
mkdir("/dev/block/vold", 0755);
  
  可以根據這個目錄找到如下節點:
sh-4.1# ls /dev/block/vold/
179:0  179:1  8:0    8:1    8:2    8:3    8:4
節點的小介紹:
0代表當前的整個設備,1代碼當前設備的分區名稱代號。
所以你會發現,sdcard只有一個分區它卻生成了兩個如:179:0 179:1
而usbdisk 有四個分區,它會生成五個掛載點: 8:0    8:1    8:2    8:3    8:4  就是這個原因。
  
  
  vold.fstab 配置文件的分析
vold 裡面會通過指定文件來讀取預先配置好的sdcard或者多分區配置文件,該文件位於
/system/core/rootdir/etc/vold.fstab
如以下的配置文件為:
  1. dev_mount sdcard /mnt/sdcard
複製代碼
dev_mount 代表掛載格式
  sdcard 代表掛載的標籤
  /mnt/sdcard 代表掛載點
  auto 為自定義選項可以為任何,但必須在main 裡面自己判斷比如這裡的意思為自動掛載
後面兩個目錄為設備路徑,第一個如果被佔用會選擇第二個
  
配置文件可以根據自己的需要編寫,並不是固定的,但最好遵循google vold 啟動文件代碼的格式編寫,要不然會給我們修改代碼或者增加多分區功能帶來不小的麻煩,如以下我自己編寫的多分區掛載支持vold.fstab 配置文件:
  1.   dev_mount sdcard external /mnt/sdcard
複製代碼
該文件修改後經系統編譯會在android 系統目錄裡/system/etc/vold.fstab找到。
/devices/platform/ehci-omap.0/usb1/1-2/1-2.1/  代表要掛載的USB接口。
vold.fstab 只是一個單純的配置文件,具體的讀取和取數據還 是要靠main裡面的process_config函數。看代碼,裡面正有一段用來讀取配置文件:
  1. if (!(fp = fopen("/etc/vold.fstab", "r"))) {
  2.          return -1;
  3.      }
複製代碼
在這個函數里面會根據讀取到的數據存放起來,然後滿足條件時執行操作。比如代碼裡面的:
  1. if (!strcmp(type, "dev_mount")) {
  2.              DirectVolume *dv = NULL;
  3.              char *part;

  4.             if (!(part = strtok_r(NULL, delim, &save_ptr))) {
  5.                  SLOGE("Error parsing partition");
  6.                  goto out_syntax;
  7.              }
  8.              if (strcmp(part, "auto") && atoi(part) == 0) {
  9.                  SLOGE("Partition must either be 'auto' or 1 based index instead of '%s'", part);
  10.                  goto out_syntax;
  11.              }

  12.             if (!strcmp(part, "auto")) {
  13.                  dv = new DirectVolume(vm, label, mount_point, -1);
  14.              } else {
  15.                  dv = new DirectVolume(vm, label, mount_point, atoi(part));
  16.              }

  17.             while ((sysfs_path = strtok_r(NULL, delim, &save_ptr))) {
  18.                  if (*sysfs_path != '/') {
  19.                      /* If the first character is not a '/', it must be flags */
  20.                      break;
  21.                  }
  22.                  if (dv->addPath(sysfs_path)) {
  23.                      SLOGE("Failed to add devpath %s to volume %s", sysfs_path,label);
  24.                      goto out_fail;
  25.                  }
  26.              }

  27.             /* If sysfs_path is non-null at this point, then it contains
  28.               * the optional flags for this volume
  29.               */
  30.              if (sysfs_path)
  31.                  flags = parse_mount_flags(sysfs_path);
  32.              else
  33.                  flags = 0;
  34.              dv->setFlags(flags);
  35.              vm->addVolume(dv);
  36.          }
複製代碼
DirectVolume後面會講到,執行mount 和unmount 都是它在做。
另外,有時後讀取配置文件會有問題,這是因為它讀取是通過指標下標遞增的方式在讀,如果有問題可以跟蹤打印一下配置文件,看哪裡需要修改。
  
  vold 裡面啟動頁面main做了些什麼
main 主要是初始化socket 連接監聽數據變化,在系統起來時第一時間啟動,並且通過讀取配置文件來識別usb口或者sdcard 的設備地址,來mount 或者unmount 。其它執行mount 、 unmount  或者刪除節點等操作都是由上層或者framework 發送命令給main讓其通知volumeManage 執行相應的操作。




Android SDCard Mount 流程分析(二)

Mount流程分為兩個部分
  • 主動掛載(插入SDCARD或者USB硬盤時系統自動掛載)
  • 手動掛載(卸載SDCARD或者USB硬盤後,再點擊加載設備的手動掛載)
不同掛載走的流程並不相同,比如手動掛載是由上層發命令給vold 執行掛動作,而主動掛載是由kernel 分命令給vold 再由vold 發掛載消息給上層,上層得到掛載消息和狀態後再發命令給vold 執行掛載。主動掛載較之複雜些。不過雖然流程不一樣,但最終還是要調用Volume的掛載函數,下面將詳細介紹兩者的行走的流程。

由於會涉及SDCARD或者USB硬盤,其中調用的方法就不詳細說明,這裡只說出當插入SDCARD或者USB硬盤會走的流程。

  主動掛載
主動掛載時,會走向DirectVolume類,調用DirectVolume::mountVol方法,代碼如下:
  1. int DirectVolume::mountVol() {
  2.     char errmsg[255];
  3.     dev_t deviceNodes[64];
  4.     int i, n = 0;
  5.     if (getState() == Volume::State_NoMedia) {
  6.         snprintf(errmsg, sizeof(errmsg),
  7.                  "Volume %s %s mount failed - no media",
  8.                  getLabel(), getMountpoint());
  9.         mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeMountFailedNoMedia,errmsg, false);
  10.         errno = ENODEV;
  11.         return -1;
  12.     } else if (getState() != Volume::State_Idle) {
  13.         errno = EBUSY;
  14.         return -1;
  15.     }
  16.     n = getDeviceNodes((dev_t *) &deviceNodes, 64);
  17.     if (!n) {
  18.         SLOGE("Failed to get device nodes (%s)\n", strerror(errno));
  19.         return -1;
  20.     }
  21.     bool mounted = false;
  22.     for (i = 0; i < n; i++) {
  23.         mDevNodeIndex = deviceNodes[i];
  24.         //XXX: hack mountpoint
  25.         if ( mMountpointParsed ) { free(mMountpointParsed); mMountpointParsed = NULL; }
  26.         mMountpointParsed = getParsedMountPoint(mMountpoint, i);

  27.         if (isMountpointMounted(getMountpoint())) {
  28.             SLOGW("Volume is idle but appears to be mounted - fixing");
  29.             setState(Volume::State_Mounted);
  30.             // mCurrentlyMountedKdev = XXX
  31.             errno = EBUSY;
  32.             continue;
  33.         }

  34.         if (!Volume::mountVol()) {
  35.             mounted = true;
  36.         }

  37.         mState = Volume::State_Idle;
  38.     }

  39.     if ( mMountpointParsed ) { free(mMountpointParsed); mMountpointParsed = NULL; }

  40.     if ( mounted ) {
  41.         // at least on partition has been mounted successful, mark disk as mounted
  42.         setState(Volume::State_Mounted);
  43.         return 0;
  44.     }

  45.     SLOGE("Volume %s found no suitable devices for mounting :(\n", getLabel());
  46.     setState(Volume::State_Idle);
  47.     return -1;
  48. }
複製代碼
代碼加亮部分,藍色部分,會循環整個設備節點系統目錄位於(/dev/block/vold),然後調用紅色部分代碼,調用Volume的掛載方法。
這裡,無論是SDCARD或者USB硬盤在主動掛載時,都會走DirectVolume。

  手動掛載
手動掛載是由上層發Mount 命令,代碼位於MountService裡面的doMountVolume方法,具體如何實現我們先不深究,它這裡通過發送socket(mount)命令到Vold 的CommandListener裡面的CommandListener::VolumeCmd::runCommand方法進入代碼這裡:
  1. else if (!strcmp(argv[1], "mount")) {
  2.         if (argc != 3) {
  3.             cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume mount <path>", false);
  4.             return 0;
  5.         }

  6.         if(!strcmp(argv[2],"firstMount")){
  7.             VolumeCollection::iterator i;
  8.               if(mVolumes!=NULL){
  9.               for (i = mVolumes->begin(); i != mVolumes->end(); ++i) {
  10.               if (strcmp("/sdcard", (*i)->getMountpoint())) {
  11.                   vm->mountVolume((*i)->getMountpoint());
  12.                }
  13.             }
  14.          }
  15.         }else{
  16.            vm->mountVolume(argv[2]);
  17.         }

  18.     }
複製代碼
這裡執行掛載動作,看上面藍色代碼是為了系統第一次啟動上層發送命令firstMount給CommandListener執行掛載USB硬盤的動作,紅色代碼即是核心要掛載的方法,調用的VolumeManage的mountVolume 方法,只需傳入掛載點。該方法代碼是:
  1. int VolumeManager::mountVolume(const char *label) {
  2.     Volume *v = lookupVolume(label);
  3.     if (!v) {
  4.         errno = ENOENT;
  5.         return -1;
  6.     }
  7.     return v->mountVol();
  8. }
複製代碼
可以看出,這裡同樣調用的是Volume的mountVol方法,殊途同歸,接下來著重看一下Volume類裡面這個mountVol方法,究竟幹了些啥。
Volume::mountVol 方法深究
別的先不管,來看一下代碼
  1. int Volume::mountVol() {
  2.     int rc = 0;
  3.     char errmsg[255];
  4.     const char *mountPath;

  5.         char devicePath[255];

  6.         sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(mDevNodeIndex),
  7.                 MINOR(mDevNodeIndex));//得到设备节点,如:/dev/block/vold/8:1
  8.      
  9.         SLOGI("%s being considered for volume %s ...major : %d minor: %d\n", devicePath, getLabel(),
  10.          MAJOR(mDevNodeIndex),MINOR(mDevNodeIndex));

  11.         errno = 0;
  12.         setState(Volume::State_Checking);//设置状态为checking整型为3

  13.         // TODO: find a way to read the filesystem ID
  14.         bool isFatFs = true;
  15.         bool isNtfsFS = true;
  16.          //检查设备格式是否为Fat32
  17.         if (Fat::check(devicePath)) {
  18.             if (errno == ENODATA) {
  19.                 SLOGW("%s does not contain a FAT filesystem\n", devicePath);
  20.                 isFatFs = false;
  21.             } else {
  22.               errno = EIO;
  23.               /* Badness - abort the mount */
  24.               SLOGE("%s failed FS checks (%s)", devicePath, strerror(errno));
  25.               setState(Volume::State_Idle);
  26.               return -1;
  27.             }
  28.         }

  29.         //创建挂载目录
  30.        // create mountpoint
  31.         if (mkdir(getMountpoint(), 0755)) {
  32.             if (errno != EEXIST) {
  33.                 SLOGE("Failed to create mountpoint %s (%s)", getMountpoint(), strerror(errno));
  34.                 return -1;
  35.             }
  36.         }

  37.         /*
  38.          * Mount the device on our internal staging mountpoint so we can
  39.          * muck with it before exposing it to non priviledged users.
  40.          */
  41.         errno = 0;
  42.         //如果为sdcard则挂载到/mnt/secure/staging,否则挂载到挂载点
  43.          if(!strcmp(getLabel(),"sdcard"))
  44.             mountPath="/mnt/secure/staging";
  45.         else
  46.             mountPath=getMountpoint();
  47.          //接下来就是不同格式不同的挂载,这里支持两种格式:fat32,Ntfs
  48.         if ( isFatFs ) {
  49.             if (Fat::doMount(devicePath,mountPath, false, false, 1000, 1015, 0702, true)) {
  50.                 SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
  51.                 isFatFs = false;
  52.             }
  53.             isNtfsFS = false;
  54.         }
複製代碼
  1. int Volume::mountVol() {
  2.     int rc = 0;
  3.     char errmsg[255];
  4.     const char *mountPath;

  5.         char devicePath[255];

  6.         sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(mDevNodeIndex),
  7.                 MINOR(mDevNodeIndex));//得到设备节点,如:/dev/block/vold/8:1

  8.         SLOGI("%s being considered for volume %s ...major : %d minor: %d\n", devicePath, getLabel(),
  9.          MAJOR(mDevNodeIndex),MINOR(mDevNodeIndex));

  10.         errno = 0;
  11.         setState(Volume::State_Checking);//设置状态为checking整型为3

  12.         // TODO: find a way to read the filesystem ID
  13.         bool isFatFs = true;
  14.         bool isNtfsFS = true;
  15.          //检查设备格式是否为Fat32
  16.         if (Fat::check(devicePath)) {
  17.             if (errno == ENODATA) {
  18.                 SLOGW("%s does not contain a FAT filesystem\n", devicePath);
  19.                 isFatFs = false;
  20.             } else {
  21.               errno = EIO;
  22.               /* Badness - abort the mount */
  23.               SLOGE("%s failed FS checks (%s)", devicePath, strerror(errno));
  24.               setState(Volume::State_Idle);
  25.               return -1;
  26.             }
  27.         }

  28.         //创建挂载目录
  29.        // create mountpoint
  30.         if (mkdir(getMountpoint(), 0755)) {
  31.             if (errno != EEXIST) {
  32.                 SLOGE("Failed to create mountpoint %s (%s)", getMountpoint(), strerror(errno));
  33.                 return -1;
  34.             }
  35.         }

  36.         /*
  37.          * Mount the device on our internal staging mountpoint so we can
  38.          * muck with it before exposing it to non priviledged users.
  39.          */
  40.         errno = 0;
  41.         //如果为sdcard则挂载到/mnt/secure/staging,否则挂载到挂载点
  42.          if(!strcmp(getLabel(),"sdcard"))
  43.             mountPath="/mnt/secure/staging";
  44.         else
  45.             mountPath=getMountpoint();
  46.          //接下来就是不同格式不同的挂载,这里支持两种格式:fat32,Ntfs
  47.         if ( isFatFs ) {
  48.             if (Fat::doMount(devicePath,mountPath, false, false, 1000, 1015, 0702, true)) {
  49.                 SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));

  50.                 isFatFs = false;
  51.             }
  52.             isNtfsFS = false;
  53.         }

  54.         if ( isNtfsFS ) {
  55.             if (Ntfs::doMount(devicePath, mountPath, true)) {
  56.                 SLOGE("%s failed to mount via NTFS (%s)\n", devicePath, strerror(errno));
  57.                 isNtfsFS = false;
  58.             }
  59.         }

  60.         if ( !isFatFs && !isNtfsFS ) {
  61.             // unsupported filesystem
  62.             return -1;
  63.         }

  64.         SLOGI("Device %s, target %s mounted @ /mnt/secure/staging", devicePath, getMountpoint());

  65.         if ( !strcmp(getLabel(), "sdcard") ) {

  66.             protectFromAutorunStupidity();

  67.             if (createBindMounts()) {
  68.                 SLOGE("Failed to create bindmounts (%s)", strerror(errno));
  69.                 umount("/mnt/secure/staging");
  70.                 setState(Volume::State_Idle);
  71.                 return -1;
  72.             }
  73.         }

  74.         /*
  75.          * Now that the bindmount trickery is done, atomically move the
  76.          * whole subtree to expose it to non priviledged users.
  77.          * 如果为sdcard则将/mnt/secure/staging 目录移动到挂载点,并将该目录unmount
  78.          */
  79.         if(!strcmp(getLabel(),"sdcard")){
  80.           if (doMoveMount("/mnt/secure/staging", getMountpoint(), false)) {
  81.               SLOGE("Failed to move mount (%s)", strerror(errno));
  82.               umount("/mnt/secure/staging");
  83.               setState(Volume::State_Idle);
  84.                return -1;
  85.           }
  86.        }
  87.         setState(Volume::State_Mounted);//设置状态到MountService
  88.         mCurrentlyMountedKdev = mDevNodeIndex;

  89.         return 0;

  90. }
複製代碼
注意:原生的代碼可能跟上面貼出來的代碼有點不同,上面的代碼是增加了Ntfs-3g掛載的支持和多分區掛載的支持,但基本流程是相同的。

代碼有詳細的註釋,這裡要注意的是:sdcard和USB的支持不同,sdcard 掛載時需要先掛載到臨時目錄/mnt/secure/staging,然後再移動到最終需要掛載的掛載點,而USB硬盤特別是多分區的支持,不用先掛載到臨時目錄,而是可以支持掛載到想要掛載的掛載點,這裡是比較需要注意到的地方(在這裡栽過跟頭,會出現「隨機性的掛載失敗」)。
ok.




前一篇講到SDCard unmout onEvent 發送socket 到框架層,接下來分析框架層得到數據後的流程。

MoutService

當android 系統啟動時,system將MountService 添加到啟動服務裡面,而MountService 會開啟一個線程來運行NativeDaemonConnector,由它來監聽vold的消息,代碼:

  1.          mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG);
  2.          mReady = false;
  3.          Thread thread = new Thread(mConnector, VOLD_TAG);
  4.          thread.start();
複製代碼

該函數運行在MountService的構造函數里面,而NativeDaemonConnector 本身就是繼承自Runnable。

NativeDaemonConnector

Framework與vold 的通信是通過socket來實現的,不過該socket 由 android做了一個封裝,LocalSocket 實現的socket功能。

NativeDaecomConnector 位於framework/base/service/java/com/android/server目錄下, 監聽vold 的消息代碼在繼承自Runnable對象的run方法裡面 :

  1. @Override
  2.      public void run() {
  3.          HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
  4.          thread.start();
  5.          mCallbackHandler = new Handler(thread.getLooper(), this);

  6.          while (true) {
  7.              try {
  8.                  listenToSocket();
  9.              } catch (Exception e) {
  10.                  Slog.e(TAG, "Error in NativeDaemonConnector", e);
  11.                  SystemClock.sleep(5000);
  12.              }
  13.          }
  14.      }
複製代碼

NativeDaemonConnector 類實例化了一個LocalSocket來與vold 通信。LocalSocket 裡面有一個類LocalSocketImpl,該類部分是通過JNI實現的。

關於socket 內部如何通信,這個不是我們所關心的內容,因為如果要深入進去估計沒完沒了,有興趣的朋友可以參考源碼進入SocketListener查看:

建立連接

SocketListener::SocketListener


當main初始化CommandListener 後,會為socketName 傳入一個叫vold 的字符串

SocketListener::startListener


等待並接收連接請求
SocketListener::runListener

獲得命令參數
bool FrameworkListener::onDataAvailable

dispatchCommand 到相應的命令類,並返回一部分消息給上層
FrameworkListener::dispatchCommand

再回過頭看NativeDaemonConnector 的listenToSocket,代碼中實例化了一個LocalSocketAddress的實例,並傳入一個叫"vold"字符串的socket 名稱,這與CommandListener中繼承了FrameworkListener時給的"vold"名稱是一致的,兩個socket名稱一致則可以互相進行通訊了,代碼如下:
  1. private void listenToSocket() throws IOException {
  2.          LocalSocket socket = null;
  3.      Slog.w(TAG,String.format("NativeDaemonConnector--->listenToSocket:start"));
  4.          try {
  5.              socket = new LocalSocket();
  6.              LocalSocketAddress address = new LocalSocketAddress(mSocket,
  7.                     LocalSocketAddress.Namespace.RESERVED);

  8.              socket.connect(address);

  9.              InputStream inputStream = socket.getInputStream();
  10.              mOutputStream = socket.getOutputStream();

  11.              mCallbacks.onDaemonConnected();

  12.              byte[] buffer = new byte[BUFFER_SIZE];
  13.              int start = 0;

  14.              while (true) {
  15.                  int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
  16.                  if (count < 0) break;

  17.                  // Add our starting point to the count and reset the start.
  18.                 count += start;
  19.                  start = 0;

  20.                  for (int i = 0; i < count; i++) {
  21.                      if (buffer == 0) {
  22.                          String event = new String(buffer, start, i - start);//解析socket 的數據並獲取event
  23.                          if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));

  24.                          String[] tokens = event.split(" ", 2);
  25.                          try {
  26.                              int code = Integer.parseInt(tokens[0]);

  27.                              if (code >= ResponseCode.UnsolicitedInformational) {
  28.                                  mCallbackHandler.sendMessage(
  29.                                          mCallbackHandler.obtainMessage(code, event));//發送消息給handler
  30.                              } else {
  31.                                  try {
  32.                                      mResponseQueue.put(event);
  33.                                  } catch (InterruptedException ex) {
  34.                                      Slog.e(TAG, "Failed to put response onto queue", ex);
  35.                                  }
  36.                              }
  37.                          } catch (NumberFormatException nfe) {
  38.                              Slog.w(TAG, String.format("Bad msg (%s)", event));
  39.                          }
  40.                          start = i + 1;
  41.                      }
  42.                  }

  43.                  // We should end at the amount we read. If not, compact then
  44.                  // buffer and read again.
  45.                 if (start != count) {
  46.                      final int remaining = BUFFER_SIZE - start;
  47.                      System.arraycopy(buffer, start, buffer, 0, remaining);
  48.                      start = remaining;
  49.                  } else {
  50.                      start = 0;
  51.                  }
  52.              }
  53.          } catch (IOException ex) {
  54.              Slog.e(TAG, "Communications error", ex);
  55.              throw ex;
  56.          } finally {
  57.              synchronized (mDaemonLock) {
  58.                  if (mOutputStream != null) {
  59.                      try {
  60.                          mOutputStream.close();
  61.                      } catch (IOException e) {
  62.                          Slog.w(TAG, "Failed closing output stream", e);
  63.                      }
  64.                      mOutputStream = null;
  65.                  }
  66.              }

  67.              try {
  68.                  if (socket != null) {
  69.                      socket.close();
  70.                  }
  71.              } catch (IOException ex) {
  72.                  Slog.w(TAG, "Failed closing socket", ex);
  73.              }
  74.          }
  75.      }
複製代碼
上面代碼,通過socket 並event 解析出來,並通handler 發送到handleMessage 中,當handleMessage接收到傳過來的消息時,會調用MountService 的onEvent 方法將code和event和sdcard 的狀態傳遞進去。代碼如下:
  1. public boolean handleMessage(Message msg) {
  2.          String event = (String) msg.obj;
  3.          Slog.w(TAG,String.format("NativeDaemonConnector--->handleMessage the event value is "+event));
  4.          try {
  5.              if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
  6.                  Slog.w(TAG, String.format(
  7.                          "Unhandled event '%s'", event));
  8.              }
  9.          } catch (Exception e) {
  10.              Slog.e(TAG, String.format(
  11.                      "Error handling '%s'", event), e);
  12.          }
  13.          return true;
  14.      }
複製代碼

又回到MountService ,在onEvent裡面當接收到的code ==VoldResponseCode.VolumeBadRemoval時會調用updatePublicVolumeState,發送unmount改變的廣播,代碼如下:

  1. else if (code == VoldResponseCode.VolumeBadRemoval) {
  2.                  if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
  3.                  /* Send the media unmounted event first */
  4.                  updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
  5.                  action = Intent.ACTION_MEDIA_UNMOUNTED;

  6.                  if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
  7.                  updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
  8.                  action = Intent.ACTION_MEDIA_BAD_REMOVAL;}
複製代碼

到這裡,進入updatePublicVolumeState看該函數里的主要代碼:

  1. synchronized (mListeners) {
  2.              for (int i = mListeners.size() -1; i >= 0; i--) {
  3.                  MountServiceBinderListener bl = mListeners.get(i);
  4.                  try {
  5.                      Slog.w(TAG,"MountService--->updatePublicVolumeState-->bl.mListener.onStorageStateChanged");
  6.                      bl.mListener.onStorageStateChanged(path, oldState, state);
  7.                  } catch (RemoteException rex) {
  8.                      Slog.e(TAG, "Listener dead");
  9.                      mListeners.remove(i);
  10.                  } catch (Exception ex) {
  11.                      Slog.e(TAG, "Listener failed", ex);
  12.                  }
  13.              }
  14.          }
  15. }
複製代碼

並且調用sendStorageIntent 方法將SDCard的Action:android.intent.action.MEDIA_BAD_REMOVAL 和dat:file:///mnt/sdcard 通過這個廣播發送出去,代碼如下:

  1. private void sendStorageIntent(String action, String path) {
  2.          Intent intent = new Intent(action, Uri.parse("file://" + path));
  3.          // add StorageVolume extra
  4.         intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolumeMap.get(path));
  5.          Slog.d(TAG, "sendStorageIntent " + intent);
  6.          mContext.sendBroadcast(intent);
  7.      }
複製代碼

再回到 updatePublicVolumeState ,調用了stateChange 後,將狀態為632的標識發送到NativeDaemonConnector 的handlemessage,當NativeDaemonConnector 發現SDCard的狀態發送改變時,比如unmount 的時候,從632(VolumeBadRemoval)變到605(VolumeStateChange)到onEvent,當onEvent再次得到請求時,進入判斷會直接執行notifyVolumeStateChange 函數,代碼如下:

  1. if (code == VoldResponseCode.VolumeStateChange) {
  2.              /*
  3.               * One of the volumes we're managing has changed state.
  4.               * Format: "NNN Volume <label> <path> state changed
  5.               * from <old_#> (<old_str>) to <new_#> (<new_str>)"
  6.               */
  7.              notifyVolumeStateChange(
  8.                      cooked[2], cooked[3], Integer.parseInt(cooked[7]),
  9.                              Integer.parseInt(cooked[10]));
  10.          }
複製代碼

notifyStateChange 會調用updatePublicVolumeState通知packageManger SDCard己經unmount.


再回到Vold

由於vold 啟動文件一開始就啟動了CommandListener的runcommand由於socket 一直在通訊,當發現值改變後,進入以下代碼runCommand 方法裡面:

  1. else if (!strcmp(argv[1], "unmount")) {
  2.          if (argc < 3 || argc > 4 ||
  3.             ((argc == 4 && strcmp(argv[3], "force")) &&
  4.              (argc == 4 && strcmp(argv[3], "force_and_revert")))) {
  5.              cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume unmount <path> [force|force_and_revert]", false);
  6.              return 0;
  7.          }

  8.          bool force = false;
  9.          bool revert = false;
  10.          if (argc >= 4 && !strcmp(argv[3], "force")) {
  11.              force = true;
  12.          } else if (argc >= 4 && !strcmp(argv[3], "force_and_revert")) {
  13.              force = true;
  14.              revert = true;
  15.          }
  16.          rc = vm->unmountVolume(argv[2], force, revert);
  17.      }
複製代碼

這時調用VolumeManage的unmoutVolume。該方法來源於Volume 的unmountVol,調用這個函數會unmount 三個掛載點,並同時調用setState通知框架unmount 成功,可以改變UI等一系列動作。

最後總結

MountService: 實現用於管理存儲設備的後台服務

StorageManage:訪問MountService 接口,並向應用層提供接口

PackageMangeService:是用於管理系統中所有apk,當SDCard發生變化時,向應用層發送消息

NativeDaemonConnector:創建socket實現mountservice 和vold 的通信


可以這麼說:當vold 捕獲到uevent 事件,會將事件消息通知framework,framework 進行判斷,然後再下發執行命令。


粗糙圖

最後附一張比較粗糙的結構圖,時間較急,沒仔細畫好



轉帖於:http://blog.csdn.net/gangyanliang/article/details/8254478

 

臉書網友討論
2#
發表於 2013-12-14 23:10:31 | 只看該作者
頂!好貼  好文~~~一定要分享出去.

版主招募中

*滑块验证:
您需要登錄後才可以回帖 登錄 | 註冊 |

本版積分規則



Archiver|手機版|小黑屋|免責聲明|TShopping

GMT+8, 2024-5-8 06:36 , Processed in 0.090690 second(s), 22 queries .

本論壇言論純屬發表者個人意見,與 TShopping綜合論壇 立場無關 如有意見侵犯了您的權益 請寫信聯絡我們。

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表