前段時間對Android 的SDCard unmount 流程進行了幾篇簡短的分析,由於當時只是紙上談兵,沒有實際上的跟進,可能會有一些誤導人或者小錯誤。今天重新梳理了頭緒,針對mount的流程再重新分析一次。
本篇大綱
‧ android 系統如何開機啟動監聽mount服務
‧ 默認掛載點在Android 系統的哪個目錄
‧ vold.fstab 配置文件的分析
‧ vold 裡面啟動頁面main做了些什麼
android 系統如何開機啟動監聽mount服務
android sdcard 熱插拔監測和執行操作是由一個啟動文件vold 所統領的,系統開機會讀取初始化配置文件init.rc,該文件位於比如我的板子是:device/ti/omap3evm/init.rc,具體根據自己平台查找。裡面有一個是默認啟動vold 服務的代碼,如下:- service vold /system/bin/vold
- socket vold stream 0660 root mount
- 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
如以下的配置文件為:- dev_mount sdcard /mnt/sdcard
複製代碼 dev_mount 代表掛載格式
sdcard 代表掛載的標籤
/mnt/sdcard 代表掛載點
auto 為自定義選項可以為任何,但必須在main 裡面自己判斷比如這裡的意思為自動掛載
後面兩個目錄為設備路徑,第一個如果被佔用會選擇第二個
配置文件可以根據自己的需要編寫,並不是固定的,但最好遵循google vold 啟動文件代碼的格式編寫,要不然會給我們修改代碼或者增加多分區功能帶來不小的麻煩,如以下我自己編寫的多分區掛載支持vold.fstab 配置文件:- 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函數。看代碼,裡面正有一段用來讀取配置文件:- if (!(fp = fopen("/etc/vold.fstab", "r"))) {
- return -1;
- }
複製代碼 在這個函數里面會根據讀取到的數據存放起來,然後滿足條件時執行操作。比如代碼裡面的:- if (!strcmp(type, "dev_mount")) {
- DirectVolume *dv = NULL;
- char *part;
- if (!(part = strtok_r(NULL, delim, &save_ptr))) {
- SLOGE("Error parsing partition");
- goto out_syntax;
- }
- if (strcmp(part, "auto") && atoi(part) == 0) {
- SLOGE("Partition must either be 'auto' or 1 based index instead of '%s'", part);
- goto out_syntax;
- }
- if (!strcmp(part, "auto")) {
- dv = new DirectVolume(vm, label, mount_point, -1);
- } else {
- dv = new DirectVolume(vm, label, mount_point, atoi(part));
- }
- while ((sysfs_path = strtok_r(NULL, delim, &save_ptr))) {
- if (*sysfs_path != '/') {
- /* If the first character is not a '/', it must be flags */
- break;
- }
- if (dv->addPath(sysfs_path)) {
- SLOGE("Failed to add devpath %s to volume %s", sysfs_path,label);
- goto out_fail;
- }
- }
- /* If sysfs_path is non-null at this point, then it contains
- * the optional flags for this volume
- */
- if (sysfs_path)
- flags = parse_mount_flags(sysfs_path);
- else
- flags = 0;
- dv->setFlags(flags);
- vm->addVolume(dv);
- }
複製代碼 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方法,代碼如下:
- int DirectVolume::mountVol() {
- char errmsg[255];
- dev_t deviceNodes[64];
- int i, n = 0;
- if (getState() == Volume::State_NoMedia) {
- snprintf(errmsg, sizeof(errmsg),
- "Volume %s %s mount failed - no media",
- getLabel(), getMountpoint());
- mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeMountFailedNoMedia,errmsg, false);
- errno = ENODEV;
- return -1;
- } else if (getState() != Volume::State_Idle) {
- errno = EBUSY;
- return -1;
- }
- n = getDeviceNodes((dev_t *) &deviceNodes, 64);
- if (!n) {
- SLOGE("Failed to get device nodes (%s)\n", strerror(errno));
- return -1;
- }
- bool mounted = false;
- for (i = 0; i < n; i++) {
- mDevNodeIndex = deviceNodes[i];
- //XXX: hack mountpoint
- if ( mMountpointParsed ) { free(mMountpointParsed); mMountpointParsed = NULL; }
- mMountpointParsed = getParsedMountPoint(mMountpoint, i);
- if (isMountpointMounted(getMountpoint())) {
- SLOGW("Volume is idle but appears to be mounted - fixing");
- setState(Volume::State_Mounted);
- // mCurrentlyMountedKdev = XXX
- errno = EBUSY;
- continue;
- }
- if (!Volume::mountVol()) {
- mounted = true;
- }
- mState = Volume::State_Idle;
- }
- if ( mMountpointParsed ) { free(mMountpointParsed); mMountpointParsed = NULL; }
- if ( mounted ) {
- // at least on partition has been mounted successful, mark disk as mounted
- setState(Volume::State_Mounted);
- return 0;
- }
- SLOGE("Volume %s found no suitable devices for mounting :(\n", getLabel());
- setState(Volume::State_Idle);
- return -1;
- }
複製代碼 代碼加亮部分,藍色部分,會循環整個設備節點系統目錄位於(/dev/block/vold),然後調用紅色部分代碼,調用Volume的掛載方法。 這裡,無論是SDCARD或者USB硬盤在主動掛載時,都會走DirectVolume。
手動掛載手動掛載是由上層發Mount 命令,代碼位於MountService裡面的doMountVolume方法,具體如何實現我們先不深究,它這裡通過發送socket(mount)命令到Vold 的CommandListener裡面的CommandListener::VolumeCmd::runCommand方法進入代碼這裡: - else if (!strcmp(argv[1], "mount")) {
- if (argc != 3) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume mount <path>", false);
- return 0;
- }
- if(!strcmp(argv[2],"firstMount")){
- VolumeCollection::iterator i;
- if(mVolumes!=NULL){
- for (i = mVolumes->begin(); i != mVolumes->end(); ++i) {
- if (strcmp("/sdcard", (*i)->getMountpoint())) {
- vm->mountVolume((*i)->getMountpoint());
- }
- }
- }
- }else{
- vm->mountVolume(argv[2]);
- }
- }
複製代碼 這裡執行掛載動作,看上面藍色代碼是為了系統第一次啟動上層發送命令firstMount給CommandListener執行掛載USB硬盤的動作,紅色代碼即是核心要掛載的方法,調用的VolumeManage的mountVolume 方法,只需傳入掛載點。該方法代碼是: - int VolumeManager::mountVolume(const char *label) {
- Volume *v = lookupVolume(label);
- if (!v) {
- errno = ENOENT;
- return -1;
- }
- return v->mountVol();
- }
複製代碼 可以看出,這裡同樣調用的是Volume的mountVol方法,殊途同歸,接下來著重看一下Volume類裡面這個mountVol方法,究竟幹了些啥。 Volume::mountVol 方法深究 別的先不管,來看一下代碼 - int Volume::mountVol() {
- int rc = 0;
- char errmsg[255];
- const char *mountPath;
- char devicePath[255];
- sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(mDevNodeIndex),
- MINOR(mDevNodeIndex));//得到设备节点,如:/dev/block/vold/8:1
-
- SLOGI("%s being considered for volume %s ...major : %d minor: %d\n", devicePath, getLabel(),
- MAJOR(mDevNodeIndex),MINOR(mDevNodeIndex));
- errno = 0;
- setState(Volume::State_Checking);//设置状态为checking整型为3
- // TODO: find a way to read the filesystem ID
- bool isFatFs = true;
- bool isNtfsFS = true;
- //检查设备格式是否为Fat32
- if (Fat::check(devicePath)) {
- if (errno == ENODATA) {
- SLOGW("%s does not contain a FAT filesystem\n", devicePath);
- isFatFs = false;
- } else {
- errno = EIO;
- /* Badness - abort the mount */
- SLOGE("%s failed FS checks (%s)", devicePath, strerror(errno));
- setState(Volume::State_Idle);
- return -1;
- }
- }
- //创建挂载目录
- // create mountpoint
- if (mkdir(getMountpoint(), 0755)) {
- if (errno != EEXIST) {
- SLOGE("Failed to create mountpoint %s (%s)", getMountpoint(), strerror(errno));
- return -1;
- }
- }
- /*
- * Mount the device on our internal staging mountpoint so we can
- * muck with it before exposing it to non priviledged users.
- */
- errno = 0;
- //如果为sdcard则挂载到/mnt/secure/staging,否则挂载到挂载点
- if(!strcmp(getLabel(),"sdcard"))
- mountPath="/mnt/secure/staging";
- else
- mountPath=getMountpoint();
- //接下来就是不同格式不同的挂载,这里支持两种格式:fat32,Ntfs
- if ( isFatFs ) {
- if (Fat::doMount(devicePath,mountPath, false, false, 1000, 1015, 0702, true)) {
- SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
- isFatFs = false;
- }
- isNtfsFS = false;
- }
複製代碼- int Volume::mountVol() {
- int rc = 0;
- char errmsg[255];
- const char *mountPath;
- char devicePath[255];
- sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(mDevNodeIndex),
- MINOR(mDevNodeIndex));//得到设备节点,如:/dev/block/vold/8:1
- SLOGI("%s being considered for volume %s ...major : %d minor: %d\n", devicePath, getLabel(),
- MAJOR(mDevNodeIndex),MINOR(mDevNodeIndex));
- errno = 0;
- setState(Volume::State_Checking);//设置状态为checking整型为3
- // TODO: find a way to read the filesystem ID
- bool isFatFs = true;
- bool isNtfsFS = true;
- //检查设备格式是否为Fat32
- if (Fat::check(devicePath)) {
- if (errno == ENODATA) {
- SLOGW("%s does not contain a FAT filesystem\n", devicePath);
- isFatFs = false;
- } else {
- errno = EIO;
- /* Badness - abort the mount */
- SLOGE("%s failed FS checks (%s)", devicePath, strerror(errno));
- setState(Volume::State_Idle);
- return -1;
- }
- }
- //创建挂载目录
- // create mountpoint
- if (mkdir(getMountpoint(), 0755)) {
- if (errno != EEXIST) {
- SLOGE("Failed to create mountpoint %s (%s)", getMountpoint(), strerror(errno));
- return -1;
- }
- }
- /*
- * Mount the device on our internal staging mountpoint so we can
- * muck with it before exposing it to non priviledged users.
- */
- errno = 0;
- //如果为sdcard则挂载到/mnt/secure/staging,否则挂载到挂载点
- if(!strcmp(getLabel(),"sdcard"))
- mountPath="/mnt/secure/staging";
- else
- mountPath=getMountpoint();
- //接下来就是不同格式不同的挂载,这里支持两种格式:fat32,Ntfs
- if ( isFatFs ) {
- if (Fat::doMount(devicePath,mountPath, false, false, 1000, 1015, 0702, true)) {
- SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
- isFatFs = false;
- }
- isNtfsFS = false;
- }
- if ( isNtfsFS ) {
- if (Ntfs::doMount(devicePath, mountPath, true)) {
- SLOGE("%s failed to mount via NTFS (%s)\n", devicePath, strerror(errno));
- isNtfsFS = false;
- }
- }
- if ( !isFatFs && !isNtfsFS ) {
- // unsupported filesystem
- return -1;
- }
- SLOGI("Device %s, target %s mounted @ /mnt/secure/staging", devicePath, getMountpoint());
- if ( !strcmp(getLabel(), "sdcard") ) {
- protectFromAutorunStupidity();
- if (createBindMounts()) {
- SLOGE("Failed to create bindmounts (%s)", strerror(errno));
- umount("/mnt/secure/staging");
- setState(Volume::State_Idle);
- return -1;
- }
- }
- /*
- * Now that the bindmount trickery is done, atomically move the
- * whole subtree to expose it to non priviledged users.
- * 如果为sdcard则将/mnt/secure/staging 目录移动到挂载点,并将该目录unmount
- */
- if(!strcmp(getLabel(),"sdcard")){
- if (doMoveMount("/mnt/secure/staging", getMountpoint(), false)) {
- SLOGE("Failed to move mount (%s)", strerror(errno));
- umount("/mnt/secure/staging");
- setState(Volume::State_Idle);
- return -1;
- }
- }
- setState(Volume::State_Mounted);//设置状态到MountService
- mCurrentlyMountedKdev = mDevNodeIndex;
- return 0;
- }
複製代碼注意:原生的代碼可能跟上面貼出來的代碼有點不同,上面的代碼是增加了Ntfs-3g掛載的支持和多分區掛載的支持,但基本流程是相同的。
代碼有詳細的註釋,這裡要注意的是:sdcard和USB的支持不同,sdcard 掛載時需要先掛載到臨時目錄/mnt/secure/staging,然後再移動到最終需要掛載的掛載點,而USB硬盤特別是多分區的支持,不用先掛載到臨時目錄,而是可以支持掛載到想要掛載的掛載點,這裡是比較需要注意到的地方(在這裡栽過跟頭,會出現「隨機性的掛載失敗」)。 ok.
前一篇講到SDCard unmout onEvent 發送socket 到框架層,接下來分析框架層得到數據後的流程。 MoutService 當android 系統啟動時,system將MountService 添加到啟動服務裡面,而MountService 會開啟一個線程來運行NativeDaemonConnector,由它來監聽vold的消息,代碼: - mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG);
- mReady = false;
- Thread thread = new Thread(mConnector, VOLD_TAG);
- thread.start();
複製代碼 該函數運行在MountService的構造函數里面,而NativeDaemonConnector 本身就是繼承自Runnable。 NativeDaemonConnector
Framework與vold 的通信是通過socket來實現的,不過該socket 由 android做了一個封裝,LocalSocket 實現的socket功能。 NativeDaecomConnector 位於framework/base/service/java/com/android/server目錄下, 監聽vold 的消息代碼在繼承自Runnable對象的run方法裡面 : - @Override
- public void run() {
- HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
- thread.start();
- mCallbackHandler = new Handler(thread.getLooper(), this);
- while (true) {
- try {
- listenToSocket();
- } catch (Exception e) {
- Slog.e(TAG, "Error in NativeDaemonConnector", e);
- SystemClock.sleep(5000);
- }
- }
- }
複製代碼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名稱一致則可以互相進行通訊了,代碼如下: - private void listenToSocket() throws IOException {
- LocalSocket socket = null;
- Slog.w(TAG,String.format("NativeDaemonConnector--->listenToSocket:start"));
- try {
- socket = new LocalSocket();
- LocalSocketAddress address = new LocalSocketAddress(mSocket,
- LocalSocketAddress.Namespace.RESERVED);
- socket.connect(address);
- InputStream inputStream = socket.getInputStream();
- mOutputStream = socket.getOutputStream();
- mCallbacks.onDaemonConnected();
- byte[] buffer = new byte[BUFFER_SIZE];
- int start = 0;
- while (true) {
- int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
- if (count < 0) break;
- // Add our starting point to the count and reset the start.
- count += start;
- start = 0;
- for (int i = 0; i < count; i++) {
- if (buffer == 0) {
- String event = new String(buffer, start, i - start);//解析socket 的數據並獲取event
- if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
- String[] tokens = event.split(" ", 2);
- try {
- int code = Integer.parseInt(tokens[0]);
- if (code >= ResponseCode.UnsolicitedInformational) {
- mCallbackHandler.sendMessage(
- mCallbackHandler.obtainMessage(code, event));//發送消息給handler
- } else {
- try {
- mResponseQueue.put(event);
- } catch (InterruptedException ex) {
- Slog.e(TAG, "Failed to put response onto queue", ex);
- }
- }
- } catch (NumberFormatException nfe) {
- Slog.w(TAG, String.format("Bad msg (%s)", event));
- }
- start = i + 1;
- }
- }
- // We should end at the amount we read. If not, compact then
- // buffer and read again.
- if (start != count) {
- final int remaining = BUFFER_SIZE - start;
- System.arraycopy(buffer, start, buffer, 0, remaining);
- start = remaining;
- } else {
- start = 0;
- }
- }
- } catch (IOException ex) {
- Slog.e(TAG, "Communications error", ex);
- throw ex;
- } finally {
- synchronized (mDaemonLock) {
- if (mOutputStream != null) {
- try {
- mOutputStream.close();
- } catch (IOException e) {
- Slog.w(TAG, "Failed closing output stream", e);
- }
- mOutputStream = null;
- }
- }
- try {
- if (socket != null) {
- socket.close();
- }
- } catch (IOException ex) {
- Slog.w(TAG, "Failed closing socket", ex);
- }
- }
- }
複製代碼上面代碼,通過socket 並event 解析出來,並通handler 發送到handleMessage 中,當handleMessage接收到傳過來的消息時,會調用MountService 的onEvent 方法將code和event和sdcard 的狀態傳遞進去。代碼如下: - public boolean handleMessage(Message msg) {
- String event = (String) msg.obj;
- Slog.w(TAG,String.format("NativeDaemonConnector--->handleMessage the event value is "+event));
- try {
- if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
- Slog.w(TAG, String.format(
- "Unhandled event '%s'", event));
- }
- } catch (Exception e) {
- Slog.e(TAG, String.format(
- "Error handling '%s'", event), e);
- }
- return true;
- }
複製代碼 又回到MountService ,在onEvent裡面當接收到的code ==VoldResponseCode.VolumeBadRemoval時會調用updatePublicVolumeState,發送unmount改變的廣播,代碼如下: - else if (code == VoldResponseCode.VolumeBadRemoval) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
- /* Send the media unmounted event first */
- updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
- action = Intent.ACTION_MEDIA_UNMOUNTED;
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
- updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
- action = Intent.ACTION_MEDIA_BAD_REMOVAL;}
複製代碼 到這裡,進入updatePublicVolumeState看該函數里的主要代碼: - synchronized (mListeners) {
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- Slog.w(TAG,"MountService--->updatePublicVolumeState-->bl.mListener.onStorageStateChanged");
- bl.mListener.onStorageStateChanged(path, oldState, state);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Listener dead");
- mListeners.remove(i);
- } catch (Exception ex) {
- Slog.e(TAG, "Listener failed", ex);
- }
- }
- }
- }
複製代碼 並且調用sendStorageIntent 方法將SDCard的Action:android.intent.action.MEDIA_BAD_REMOVAL 和dat:file:///mnt/sdcard 通過這個廣播發送出去,代碼如下: - private void sendStorageIntent(String action, String path) {
- Intent intent = new Intent(action, Uri.parse("file://" + path));
- // add StorageVolume extra
- intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolumeMap.get(path));
- Slog.d(TAG, "sendStorageIntent " + intent);
- mContext.sendBroadcast(intent);
- }
複製代碼再回到 updatePublicVolumeState ,調用了stateChange 後,將狀態為632的標識發送到NativeDaemonConnector 的handlemessage,當NativeDaemonConnector 發現SDCard的狀態發送改變時,比如unmount 的時候,從632(VolumeBadRemoval)變到605(VolumeStateChange)到onEvent,當onEvent再次得到請求時,進入判斷會直接執行notifyVolumeStateChange 函數,代碼如下: - if (code == VoldResponseCode.VolumeStateChange) {
- /*
- * One of the volumes we're managing has changed state.
- * Format: "NNN Volume <label> <path> state changed
- * from <old_#> (<old_str>) to <new_#> (<new_str>)"
- */
- notifyVolumeStateChange(
- cooked[2], cooked[3], Integer.parseInt(cooked[7]),
- Integer.parseInt(cooked[10]));
- }
複製代碼 notifyStateChange 會調用updatePublicVolumeState通知packageManger SDCard己經unmount.
再回到Vold 由於vold 啟動文件一開始就啟動了CommandListener的runcommand由於socket 一直在通訊,當發現值改變後,進入以下代碼runCommand 方法裡面: - else if (!strcmp(argv[1], "unmount")) {
- if (argc < 3 || argc > 4 ||
- ((argc == 4 && strcmp(argv[3], "force")) &&
- (argc == 4 && strcmp(argv[3], "force_and_revert")))) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume unmount <path> [force|force_and_revert]", false);
- return 0;
- }
- bool force = false;
- bool revert = false;
- if (argc >= 4 && !strcmp(argv[3], "force")) {
- force = true;
- } else if (argc >= 4 && !strcmp(argv[3], "force_and_revert")) {
- force = true;
- revert = true;
- }
- rc = vm->unmountVolume(argv[2], force, revert);
- }
複製代碼 這時調用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
|