Android Volume 挂载、存储流程

文章详细介绍了Android系统在分区存储上的设计变化,特别是外部存储被分为应用私有目录和共享目录。当外部存储设备插入时,Kernel通过uevent事件通知Vold,Vold解析事件并由MountService处理,最终完成挂载操作。Vold作为一个守护进程,包含NetlinkManager、VolumeManager和CommandListener组件,负责管理外部存储设备。整个流程涉及uevent的处理、VolumeManager和NetlinkManager的初始化,以及U盘插拔事件的读取和响应。
该文章已生成可运行项目,

1. 分区存储

在 Android 10 以后,Google 对外部分区存储进行了重新设计,以达到更好管理文件的效果。外部存储被分为应用私有目录以及共享目录两个部分。

  1. 内部应用存储专有路径:data/data/package_name

​ 存储应用私有数据,其他应用不能访问。

  1. 外部存储应用专有路径:sdcard/Android/data/package_name。用来存储应用私有数据,其他应用不能访问(之前的版本可以访问——读写)。
  2. 外部存储共享路径:
    在这里插入图片描述

2. Volume 挂载

2.1 流程概述

  • 当一个外部存储设备插入的时候,kernel 会产生一个 uevent 事件,此事件会发送给 Native 层的Vold 模块。
  • Vold 为一个守护进程,其通过 socket 机制从 kernel 获取 uevent 事件,然后解析事件。
  • MountService 根据 Vold 解析出的相应状态去决定发出什么样的广播、给 Vold 作出什么样的反应。
  • 最后 Vold 依据 MountService 的反应稍加处理交由 Kernel 处理
  • 注意:当插入 SD 卡的时候 kernel 发出 uevent、Vold 处理 event,MountService 从 Vold 获取相应信息发出广播,app 在接收到广播之后会作出相应的处理;其实到这里 SD 卡并没有真正的被挂载到系统中,仅仅是触发了相应的 uevent,而真正的挂载并没有执行。实际是 Vold 先得到 uevent 后再交由 Vold 进行解析,然后由 MountService 获取信息发出 mount、unmount 等命令给Vold,在由 kernel 进行针对存储设备的挂载、卸载、格式化等操作。

2.2 Vold

2.2.1 概念

2.2.1.1 Vold 概念

Vold 全称为 Volume Daemon,用于管理外部存储设备的 Native daemon 进程,这是一个非常重要的守护进程,主要由 NetlinkManager,VolumeManager,CommandListener 这 3 部分组成,当内核检测到文件系统的时候会通过 Vold 自动挂载它们。

  • NetlinkManager:接受 uevent 事件的上报,最终干活的是 NetlinkHandler;负责监听块设备子系统使用 NetlinkHandler 传递给 VolumeManager 的内核 NetLink 事件
  • VolumeManager:处理所有挂载事件的,统一在这里根据事件的类型进行分发;负责维护卷状态并处理各种卷操作,这个单独的类提供了所有面向框架的功能
  • CommandListener:接收来自 VolumeManager 的事件,**通过 socket 通信方式(早期)**发送给 MountService;负责监听 /dev/socket/vold 这个 socket,其用于接收来自 framework 层发出的命令,并传递这些命令的执行结果,或者从其他 VolumeManager 那里接收到的事件
2.2.1.2 uevent

uevent 和 Linux 的 udev 设备文件系统及设备模型有关,它实际就是一串字符串,字符串的内容可告知发生了什么事情。

在 SD 卡插入手机后,系统会检测到这个设备的插入,然后内核会通过 Netlink 发送一个信息给 Vold,Vold 将根据接收到的信息进行处理,例如挂载这个 SD 卡。内核发送的这个信息,就是 uevent,其中 U 代表 User space (应用层空间)。下面看 SD 卡插入时,Vold 截获到的 uevent 消息。

以 SD 卡插入手机为例,kernel 发送的 uevent 字符串内容如下:

// mmc 表示 MultiMedia Card,这里就称为 SD 卡。
add@/devices/platform.msm_sdcc.2/mmc_host/mmc1/mmc1:c9f2/block/mmcblk0
ACTION=add  // add 表示设备插入,另外还有 remove 和 change 等动作。
// DEVPATH 表示该设备位于 /sys 目录中的设备路径。
DEVPATH=/devices/platform/msm_adcc.2/mmc_host/mmc1/mmc1:c9f2/block/mmcblk0
/*
SUBSYSTEM 表示该设备属于哪一类设备,block 为块设备,磁盘也属于这一类设备,另外还有
character(字符)设备等类型。
*/
SUBSYSTEM=block
MAJOR=179     // MAJOR 和 MINOR 分别表示该设备的主次设备号,二者联合起来可以标识一个设备。
MINOR=0
DEVNAME=mmcblk0
DEVTYPE=disk  // 设备类型为磁盘
NPARTS=3      // 这个表示该 SD 卡上的分区,我的 SD 卡上有三块分区
SEQNUM=1357   // 序号

2.2.2 加载流程

首先在 init.rc 中有 start vold 的命令会被解析到,其中有 start 函数对应的 do_start(const BuiltinArguments& args) 可以启动对应的 service。要启动 vold 服务,即寻找 vold.rc 文件。该文件内容如下,会启动 vold 程序。

service vold /system/bin/vold \
        --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
        --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
    class core
    ioprio be 2
    writepid /dev/cpuset/foreground/tasks
    shutdown critical
    group root reserved_disk

2.2.2.1 VolumeManager, NetlinkManager 初始化

main.cpp -> vm, nm

vold 程序内容如下(只列出关键部分)。创建了 VolumeManagerNetlinkManager 实例,并调用了它们各自的 start() 函数。

// system/vold/main.cpp
int main(int argc, char** argv) {
...
    VolumeManager* vm;
    NetlinkManager* nm;
    
    // 初始化VolumeManager,VoldNativeService功能的执行者
    if (!(vm = VolumeManager::Instance())) {
        LOG(ERROR) << "Unable to create VolumeManager";
        exit(1);
    }

    // 初始化NetLinkManager用来监听内核的热插拔事件,通知到vold进程USB设备已经接入了
    if (!(nm = NetlinkManager::Instance())) {
        LOG(ERROR) << "Unable to create NetlinkManager";
        exit(1);
    }
    
    if (vm->start()) {
        PLOG(ERROR) << "Unable to start VolumeManager";
        exit(1);
    }
    
    /**********************************************************************************
    **process_config函数用来解析/etc/vold.fstab的配置文件,从代码可以看出,配置文件的参数以空格和
    **制表格(Tab键)分隔;系统启动起来,分析该配置文件,挂载相应的分区,相当于Linux系统的/etc/fstab文件
    **********************************************************************************/
    bool has_adoptable;
    bool has_quota;
    bool has_reserved;
    if (process_config(vm, &has_adoptable, &has_quota, &has_reserved)) {
        PLOG(ERROR) << "Error reading configuration... continuing anyways";
    }
    
    ATRACE_BEGIN("VoldNativeService::start");
    if (android::vold::VoldNativeService::start() != android::OK) {
        LOG(ERROR) << "Unable to start VoldNativeService";
        exit(1);
    }
    ATRACE_END();

    // 启动VoldNativeService
    ATRACE_BEGIN("NetlinkManager::start");
    if (nm->start()) {
        PLOG(ERROR) << "Unable to start NetlinkManager";
        exit(1);
    }
    ATRACE_END();
    
    // 应用层往/sys/block目录下的uevent文件写"add\n"指令,触发kernel向上发送Uevent消息,获取设备的当前信息
    coldboot("/sys/block");
...
}

VolumeManager::start()

VolumeManager::start() 会卸载掉目录中的所有内容,然后构造出虚拟的内置存储目录 data/media,在代码中体现为对象 EmulatedVolume 。根据给的注释可知,Vold 会创建一个虚拟磁盘,即 /data/misc/vold/virtual_disk。(这块不是很理解)

// /system/vold/VolumeManager.cpp
int VolumeManager::start() {
    ATRACE_NAME("VolumeManager::start");

    // Always start from a clean slate by unmounting everything in
    // directories that we own, in case we crashed.
    unmountAll();

    Devmapper::destroyAll();
    Loop::destroyAll();

    // Assume that we always have an emulated volume on internal
    // storage; the framework will decide if it should be mounted.
    CHECK(mInternalEmulated == nullptr);
    mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(
        new android::vold::EmulatedVolume("/data/media"));
    mInternalEmulated->create();

    // Consider creating a virtual disk
    updateVirtualDisk();

    return 0;
}

process_config()

执行完 vm->start() 后,会调用 process_config() 解析 fstab 文件。fstab 用于记录文件系统信息,示例如下:

#<src>   <mnt_point>    <type>    <mnt_flags and options>   <fs_mgr_flags>
/devices/platform/passthrough/5b0d0000.usb/ci_hdrc.0/* auto  auto  defaults  voldmanaged=usb:auto
/devices/platform/5b0d0000.usb/ci_hdrc.0/* auto  auto  defaults  voldmanaged=usb:auto

src 表示 待挂载的设备节点路径
mount point 表示 挂载点,即 被挂载的目录
type 表示 所挂载磁盘的文件系统类型
mnt_flags and options 表示 指定所挂载的文件系统的一些参数

vold 只关心 fstab 文件中设置了 vold_managed 标志的设备,该标志表示该设备归 vold 管理,其他设备的挂载都通过 init 进程初始化阶段进行挂载。(参考 fstab.sailfish, /data, /system, /vender 这些内置存储都不需要 vold 来管理)。vold 启动的时候来会读取 fstab 文件来确定自己要管理的磁盘,vold 要管理的磁盘使用 DiskSource 数据结构来描述。vold 会使用 NetlinkManager 和内核建立链接,监听 udev 事件。vold 进程收到 udev 事件的时候,读取设备信息,对于需要 vold 管理的设备会创建 Disk 数据结构来代表一块设备,然后读取分区表(使用 /system/bin/sgdisk 命令读取),最后创建 Volume 结构描述分区,并放到 Disk 下的 volumes 集合中,来建立关系。vold就是通过 DiskSource 来确认设备是否由自己管理。

总结:process_config 解析 fstab 文件,把每一条设备挂载信息作为 DiskSource 都通过addDiskSource 添加到 VolumeManager 的 list 数组中,接下来任何 uevent 事件都会在这里面查询处理。

问题:
什么是 DiskSource 对象?有什么用?

NetlinkManager->start()

**NetlinkManager 通过套接字实现 vold 和内核的连接。**NetlinkManager 内部建立了一个 socket,传递给 NetlinkHandler,调用 NetlinkHandler 来进行监听 uevent。

NetlinkHandler 的父类是 NetlinkListener, NetlinkListener 的父类是 SocketListener。调用层次如下:

NetlinkManager::start() -> 
	NetlinkHandler.start() -> 
		SocketListener.startListener() -> 
			pthread_create() -> 
				SocketListener::threadStart() ->
					runListener() ->
					

NetlinkManager::start() 最终调用了 SocketListener::startListener() ,该函数调用 pthread_create -> runListener() ,开启一个线程去循环监听 socket 也就是 kernel 的事件上报,最后给 onDataAvailable() 处理。

// /system/vold/NetlinkManager.cpp
int NetlinkManager::start() {
    struct sockaddr_nl nladdr;
    int sz = 64 * 1024;
    int on = 1;
    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = getpid();
    nladdr.nl_groups = 0xffffffff;
    if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) < 0) {
        PLOG(ERROR) << "Unable to create uevent socket";
        return -1;
    }

    ...
    
    mHandler = new NetlinkHandler(mSock);
    if (mHandler->start()) {
        PLOG(ERROR) << "Unable to start NetlinkHandler";
        goto out;
    }
}
// /system/core/libsysutils/src/SocketListener.cpp
int SocketListener::startListener(int backlog) {

    if (pthread_create(&mThread, nullptr, SocketListener::threadStart, this)) {
        SLOGE("pthread_create (%s)", strerror(errno));
        return -1;
    }

    return 0;
}
// /system/core/libsysutils/src/SocketListener.cpp
void *SocketListener::threadStart(void *obj) {
    SocketListener *me = reinterpret_cast<SocketListener *>(obj);

    me->runListener();	// ==== here ====
    pthread_exit(nullptr);
    return nullptr;
}
// /system/core/libsysutils/src/SocketListener.cpp 
void SocketListener::runListener() {
    while (true) {
        ...
        for (SocketClient* c : pending) {
            SLOGV("processing fd %d", c->getSocket());
            /* 解析uevent事件 */
            if (!onDataAvailable(c)) {
                release(c, false);
            }
            c->decRef();
        }
    }
}

onDataAvailable() 中有一个 decode() 函数,用于将 uevent 字符串解析为类对象。然后,调用 onEvent()

// /system/core/libsysutils/src/NetlinkListener.cpp
bool NetlinkListener::onDataAvailable(SocketClient *cli)
{
    NetlinkEvent *evt = new NetlinkEvent();
    if (evt->decode(mBuffer, count, mFormat)) {
        onEvent(evt);	
    }
}

onEvent() 调用 VolumeManager 实例,调用 vm->handleBlockEvent(evt) 来解析 uevent 的实际内容。

// /system/vold/NetlinkHandler.cpp
void NetlinkHandler::onEvent(NetlinkEvent* evt) {
    VolumeManager* vm = VolumeManager::Instance();
    vm->handleBlockEvent(evt);
}
TODO: 再看一下 这几个 listener 之间的类的设计,感受一下实际项目的虚函数、继承的使用
2.2.2.2 coldboot 冷启动

继续看 /system/vold/main.cpp 的流程。

// /system/vold/main.cpp
	// Do coldboot here so it won't block booting,
    // also the cold boot is needed in case we have flash drive
    // connected before Vold launched
    coldboot("/sys/block");
// /system/vold/main.cpp
static void coldboot(const char* path) {
    DIR* d = opendir(path);
    do_coldboot(d, 0);
}
// /system/vold/main.cpp
static void do_coldboot(DIR* d, int lvl) {
    fd = openat(dfd, "uevent", O_WRONLY | O_CLOEXEC);
    if (fd >= 0) {
        write(fd, "add\n", 4);
        close(fd);
    }
}

coldboot() 实现的功能就是遍历 path 下所有的 uevent 文件,然后写入"add\n" 四个字节到 uevent 文件。uevent 主要是 udev 协议的一部分,当设备进行热插拔的时候,上层应用程序可以通过 netlink 收到相应的 add,remove,change 等消息,这样就可以调用 mknod 在 /dev/ 目录下创建对应的驱动设备文件,这样才可以使用设备文件来操作设备。 在 vold 的启动过程中,由于块设备早就准备好了,不会收到设备插拔的消息,没有办法对固有的设备来创建对应的 Disk 和 Volume 结构。好在 udev 提供了一套机制,就是可以通过写 uevent 来触发热插拔事件,这里写入 add 就代表触发一个设备插入事件。前面我们已经启动了 NetlinkManager 来监听 netlink 事件,这里来触发 uevent 的设备插入事件, VolumeManager 就可以创建相应的 Disk 和 Volume 数据结构了。

2.2.3 U 盘插拔事件读取

上文提到,最终的 uevent 事件会传入到 VolumeManager::handleBlockEvent(NetlinkEvent* evt) 处理。VolumeManager 的 handleBlockEvent 在处理 U 盘插入事件时会创建一个 Disk 对象,通过该对象 readMetadata 和 readPartitions 去读取 U 盘中的数据和分析信息(分区信息、文件系统等)。最后会创建 PublicVolume 对象去处理 U 盘的具体业务逻辑,包括和 StorageManagerService 的通信。其中Disk 对象还有一个及其重要的功能,就是通过 Binder 机制去通知 StorageManagerService 创建磁盘。

// /system/vold/VolumeManager.cpp
void VolumeManager::handleBlockEvent(NetlinkEvent* evt) {
  
    switch (evt->getAction()) {
        case NetlinkEvent::Action::kAdd: {
            for (const auto& source : mDiskSources) {
                if (source->matches(eventPath)) {
                    // For now, assume that MMC and virtio-blk (the latter is
                    // specific to virtual platforms; see Utils.cpp for details)
                    // devices are SD, and that everything else is USB
                    int flags = source->getFlags();
                    if (major == kMajorBlockMmc || IsVirtioBlkDevice(major)) {
                        flags |= android::vold::Disk::Flags::kSd;
                    } else {
                        flags |= android::vold::Disk::Flags::kUsb;
                    }
					
                    // ==== here ==== 创建了一个 Disk 对象
                    auto disk =
                        new android::vold::Disk(eventPath, device, source->getNickname(), flags);
                    handleDiskAdded(std::shared_ptr<android::vold::Disk>(disk));
                    break;
                }
            }
            break;
        }
        case NetlinkEvent::Action::kChange: {
            handleDiskChanged(device);
            break;
        }
        case NetlinkEvent::Action::kRemove: {
            handleDiskRemoved(device);
            break;
        }
        
        default: {
            LOG(WARNING) << "Unexpected block event action " << (int)evt->getAction();
            break;
        }
    }
}
// /system/vold/VolumeManager.cpp
void VolumeManager::handleDiskAdded(const std::shared_ptr<android::vold::Disk>& disk) {
    if (mSecureKeyguardShowing) {
        mPendingDisks.push_back(disk);
    } else {
        disk->create();
        mDisks.push_back(disk);
    }
}

判断该 U 盘的文件系统格式是否支持,进入创建 PublicVolume 或 PrivateVolume

// /system/vold/model/Disk.cpp
status_t Disk::create() {
    CHECK(!mCreated);
    mCreated = true;

    auto listener = VolumeManager::Instance()->getListener();
    if (listener) listener->onDiskCreated(getId(), mFlags);

    if (isStub()) {
        createStubVolume();
        return OK;
    }
    readMetadata(); 	// ==== here ====
    readPartitions();	// ==== here ====
    return OK;
}
// /system/vold/model/Disk.cpp
status_t Disk::readPartitions() {
    for (auto line : output) {
 	...	// 判断该U盘的文件系统格式是否支持,进入创建 PublicVolume
        switch (type) {
            case 0x06:  // FAT16
            case 0x07:  // HPFS/NTFS/exFAT
            case 0x0b:  // W95 FAT32 (LBA)
            case 0x0c:  // W95 FAT32 (LBA)
            case 0x0e:  // W95 FAT16 (LBA)
            case 0x83: //A special filesystem format through linux
                LOG(DEBUG) << "createPublicVolume!"  << "\n";
                hit = true;
                /* mbr分区表直接创建PublicVolume */
                /* 处理U盘 挂载,卸载,格式化这些具体业务 */
                createPublicVolume(partDevice);
                break;
        }
    
    ...
        else if (android::base::EqualsIgnoreCase(typeGuid, kGptAndroidExpand)) {
            createPrivateVolume(partDevice, partGuid);
            hit = true;
        }    
    }
    return OK;
}

在上述分析过程中最终通过 createXxxxVolume 方法去创建 volume 对象,其中分3种类型,分别为 createPublicVolumecreatePrivateVolumecreateEmulatedVolume 。其中,都调用了 create() 函数, 如下:

// /system/vold/model/Disk.cpp
void Disk::createPublicVolume(dev_t device) {
    auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(device));
    /* 调用VolumeBase的create方法.由于PublicVolume是VolumeBase的子类,且具体实现VolumeBase的方法,
       这里其实调用的是PublicVolume的doCreate方法 */
    vol->create();
}

void Disk::createPrivateVolume(dev_t device, const std::string& partGuid) {
    auto vol = std::shared_ptr<VolumeBase>(new PrivateVolume(device, keyRaw,
                                mFlags));
    vol->create();
}

VolumeBase::create():由于 PublicVolume 与 PrivateVolume 均为 VolumeBase 的子类,其并没有实现 create 方法,于是二者均使用的由父类 VolumeBase 所实现的 create,其中就直接通过 binder 直接去调用由存在于 Java FrameWork 层的 StorageManagerService 所实现的 onVolumeCreated 方法(直接调用 Java 代码)。

/* in /system/vold/model/VolumeBase.cpp */
status_t VolumeBase::create() {
    CHECK(!mCreated);

    mCreated = true;
    /* 调用虚函数doCreate()去真正创建不同类型的分区 */
    status_t res = doCreate();

    auto listener = getListener();
    if (listener) {
        /* 通过binder机制,直接调用FrameWork层中由java代码实现的StorageManagerService方法去创建volume */
        listener->onVolumeCreated(getId(), static_cast<int32_t>(mType), mDiskId, mPartGuid);
    }

    setState(State::kUnmounted);
    return res;
}

其中,doCreate() 创建外部储存分区,创建设备节点,调用 mknod 来创建卷对应 的/dev/block/vold/${mId} 文件(即设备节点)。(这里没有把代码贴上来)

然后看 Framework 中的 Java 代码:

onVolumeCreated -> onVolumeCreateLocked -> mHandler.obtainMessage(...).sendToTarget() ,这里有 H_VOLUME_MOUNT 值;最终调用 mount() ,发送挂载命令,进行挂载。

// frameworks/base/services/core/java/com/android/server/StorageManagerService.java
public void onVolumeCreated(String volId, int type, String diskId, String partGuid) {
    synchronized (mLock) {
        final DiskInfo disk = mDisks.get(diskId);
        //保存Volume的信息到VolumeInfo
        final VolumeInfo vol = new VolumeInfo(volId, type, disk, partGuid);
        mVolumes.put(volId, vol);
        onVolumeCreatedLocked(vol);		// ======= Here =======
    }
}
// frameworks/base/services/core/java/com/android/server/StorageManagerService.java
private void onVolumeCreatedLocked(VolumeInfo vol) {
	if (vol.type == VolumeInfo.TYPE_EMULATED) {
        mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
    } else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
        mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
    } else if (vol.type == VolumeInfo.TYPE_PRIVATE) {
        mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
	} else if (vol.type == VolumeInfo.TYPE_STUB) {
            vol.mountUserId = mCurrentUserId;
            mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
        } else {
            Slog.d(TAG, "Skipping automatic mounting of " + vol);
        }
    }
}
// frameworks/base/services/core/java/com/android/server/StorageManagerService.java
class StorageManagerServiceHandler extends Handler {
        public StorageManagerServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
			   ...;
                case H_VOLUME_MOUNT: {
					final VolumeInfo vol = (VolumeInfo) msg.obj;
					if (isMountDisallowed(vol)) {
						Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");
						break;
					}
 
					mount(vol);	// ============= Here =============
					break;
				}
                ...;
        }
	...;
}
// in /frameworks/base/core/java/android/os/storage/StorageManager.java
public void mount(String volId) {
         try {
             /* 发送挂载命令 */
             mStorageManager.mount(volId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
}

在 Framework 层调用了 mStorageManager.mount(volId) 方法之后又会进入到 Native 层,此刻会调用到 VoldNativeService 对象的 mount 方法,然后调用 VolumeBase 的 doMount(),并调用 setState() 调用 Java 层的内容。

/* in /system/vold/model/VoldNativeService.cpp */
binder::Status VoldNativeService::mount(const std::string& volId, int32_t mountFlags,
                                        int32_t mountUserId) {
    auto vol = VolumeManager::Instance()->findVolume(volId);
	/* 调用VolumeBase的mount方法 */
	int res = vol->mount();
    return ok();
}
/* in /system/vold/model/VolumeBase.cpp */
status_t VolumeBase::mount() {
    /* 调用子类PublicVolume的doMount方法 */
    /* 注意:该方法是一个虚函数,对于EmulatedVolume,PublicVolume和PrivateVolume有不同的实现 */
    status_t res = doMount();
    /* 设置挂载成功状态,挂载成功就向上通知onVolumeStateChanged */
    setState(res == OK ? State::kMounted : State::kUnmountable);
    return res;
}
/* in /system/vold/model/VolumeBase.cpp */
void VolumeBase::setState(State state) {
    std::lock_guard<std::mutex> lock(mStateLock);
    mState = state;

    auto listener = getListener();
    if (listener) {
        /* 此处挂载成功的话又会通过binder机制调用java层实现的onVolumeStateChanged函数 */
        listener->onVolumeStateChanged(getId(), static_cast<int32_t>(mState));
    }
}
本文章已经生成可运行项目
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值