http://blog.csdn.net/yihongyuelan/article/category/844867
Android 2.3 SD卡挂载流程浅析(一)
Android 2.3中关于SD卡挂载简介
在Android 2.3中,当SD卡插入系统之后,系统会自动挂载。Vold 就是负责挂载SD卡的,vold 的全称是volume daemon。实际上是负责完成系统的CDROM,USB 大容量存储,MMC 卡(后文有简介,具体请百度)等扩展存储的挂载任务自动完成的守护进程。它提供的主要特点是支持这些存储外设的热插拔。在Android上的这个vold系统和GNU/Linux的之间存在很大的差异。自Android 2.2开始,vold又做了大改动,升级为vold 2.0,之前的配置文件是system/etc/vold.conf,vold 2.0变为system/etc/vold.fstab。
vold.fstab中的内容显示如下:
## Vold 2.0 Generic fstab
## - San Mehat ([email protected])
##
#######################
## Regular device mount
##
## Format: dev_mount <label> <mount_point> <part> <sysfs_path1...>
## label - Label for the volume
## mount_point - Where the volume will be mounted
## part - Partition # (1 based), or 'auto' for first usable partition.
## <sysfs_path> - List of sysfs paths to source devices
######################
## Example of a standard sdcard mount for the emulator / Dream
# Mounts the first usable partition of the specified device
dev_mount sdcard /mnt/sdcard auto /devices/platform/goldfish_mmc.0 /devices/platform/msm_sdcc.2/mmc_host/mmc1
## Example of a dual card setup
# dev_mount left_sdcard /sdcard1 auto /devices/platform/goldfish_mmc.0 /devices/platform/msm_sdcc.2/mmc_host/mmc1
# dev_mount right_sdcard /sdcard2 auto /devices/platform/goldfish_mmc.1 /devices/platform/msm_sdcc.3/mmc_host/mmc1
## Example of specifying a specific partition for mounts
# dev_mount sdcard /sdcard 2 /devices/platform/goldfish_mmc.0 /devices/platform/msm_sdcc.2/mmc_host/mmc1
可以看到大部分是注释,最重要的为以下这句:
dev_mount sdcard /mnt/sdcard auto /devices/platform/goldfish_mmc.0 /devices/platform/msm_sdcc.2/mmc_host/mmc1
这句代码意思是:外置SD卡的挂载路径,auto 代表挂载SD卡的第一个分区,后面是vold监测的路径,当插入sd时,/devices/platform/msm_sdcc.2/mmc_host/mmc1 路径下会多出一个文件夹,在该文件夹中包含了SD卡的各种ID信息,以及生产日期等。
如果把sd卡插入设备,在 /dev/block/ 目录下面也会多出几个设备节点,证明sd卡的驱动已经成功加载。 我自己测试的目录下面会形成 mmcblk0 和 mmcblk0p1 节点,注意:这两个节点的意思,mmcblk0代表第一个SD卡设备,mmcblk0p1代表第一个SD卡设备的第一个分区。真正挂载到系统中的是mmcblk0p1而不是mmcblk0,这一点很重要。
PS:
MMC(MultiMedia Card)卡由西门子公司和首推CF的SanDisk于1997年推出。1998年1月十四家公司联合成立了MMC协会(MultiMedia Card Association简称MMCA),现在已经有超过84个成员。MMC的发展目标主要是针对数码影像、音乐、手机、PDA、电子书、玩具等产品,号称是目前世界上最小的Flash Memory存贮卡,尺寸只有32mm x 24mm x 1.4mm。虽然比SmartMedia厚,但整体体积却比SmartMedia小,而且也比SmartMedia轻,只有1.5克。MMC也是把存贮单元和控制器一同做到了卡上,智能的控制器使得MMC保证兼容性和灵活性。
MMC_百度百科
SD/MMC 卡的设备构造差不多,不过 MMC 当时的设计比 SD 小一半。所以,SD/MMC 的驱动通用,进一步的,Linux 的设备节点就延续了 MMC 的这个名字,后面的 blk 是块设备这个英文的简写, mmcblk 也就是“ mmc/sd 块设备”,0 就是这个 mmc/sd 设备的顺序编号,p1 就是第一个分区。
挂载流程简析
内核层(kernel):
当有新的SD/USB设备插入时,kernel将自动检测并加载对应的驱动,同时kernel中的sysfs机制会在有新的驱动加载时给用户层发送相应的event,然后将kernel产生的这些event传递给vold。
用户层(user):
用户层通过sysfs可以接收来自kernel的uevent,这些收到的信息可以在/sys/block/mmcblk0下用命令cat *来查看,如:
# cat *
bdi: invalid length
10
179:0
device: invalid length
8
holders: invalid length
power: invalid length
queue: invalid length
8
0
0
524288
slaves: invalid length
278 813 8686 1050 0 0 0 0 0 240 1040
subsystem: invalid length
MAJOR=179
MINOR=0
DEVTYPE=disk
NPARTS=0
#
如果这时候在终端输入"pwd"指令,大家会发现路径并不是我们之前进入的路径/sys/block/mmcblk0,而是/sys/devices/platform/goldfish_mmc.0/mmc_host/mmc0/mmc0:e118/block/mmcblk0。其中mmc0:e118这个文件是插入SD卡之后生成的文件。Sysfs传递来的是一个多行的文档,vold需要解析这个文档。Vold将处理之后的事件传递给MountService,然后MoutService会将信息进一步处理传递给StorageManager,最后我们可以在系统设置界面看到SD卡挂载成功的信息,这包括了SD卡的总容量以及可用空间如下图:
SD卡的挂载流程大致如此,MountServie实际上还会通知PackageManagerService,因为这里分析的是SD卡挂载从底层到上层的表现,因此这里暂不分析。简约流程图如下:
Android 2.3 SD卡挂载流程浅析(二)
在上一篇博文《Android 2.3 SD卡挂载流程浅析(一)》主要简单的介绍了SD卡的挂载流程。包括了从内核层到用户层事件消息的传递,以及Vold的简介。本文将继续介绍SD卡的挂载,但文中并不会涉及代码的详细分析,因为这部分网上已有资料,我会在文章结尾贴出来供大家参考。本文主要目的是一方面对自己学习这一部分的总结,另一方面希望大家能够指出文中理解错误的地方。
1.SD卡挂载流程图
SD卡的挂载流程图如下:
绿色箭头:表示插入SD卡后事件传递以及SD卡挂载
红色箭头:表示挂载成功后的消息传递流程
黄色箭头:表示MountService发出挂载/卸载SD卡的命令
大家可能对图中突然出现的这么多的名称感到奇怪,这些都是在Android 2.3 源码中可以找到的,接下来我会为大家一一解释这些类的作用。
2.各个文件的主要作用
(1)Kernel:这个是系统内核啦。不是我要分析的文件,本文涉及内容不是内核级的哦!(努力学习中...)
(2)NetlinkManager:全称是NetlinkManager.cpp位于Android 2.3源码位置/system/vold/NetlinkManager.cpp。该类的主要通过引用NetlinkHandler类中的onEvent()方法来接收来自内核的事件消息,NetlinkHandler位于/system/vold/NetlinkHandler.cpp。
(3)VolumeManager:全称是VolumeManager.cpp位于Android 2.3源码位置/system/vold/VolumeManager.cpp。该类的主要作用是接收经过NetlinkManager处理过后的事件消息。因为我们这里是SD卡的挂载,因此经过NetlinkManager处理过后的消息会分为五种,分别是:block,switch,usb_composite,battery,power_supply。这里SD卡挂载的事件是block。
(4)DirectVolume:位于/system/vold/DirectVolume.cpp。该类的是一个工具类,主要负责对传入的事件进行进一步的处理,block事件又可以分为:Add,Removed,Change,Noaction这四种。后文通过介绍Add事件展开。
(5)Volume:Volume.cpp位于/system/vold/Volume.cpp,该类是负责SD卡挂载的主要类。Volume.cpp主要负责检查SD卡格式,以及对复合要求的SD卡进行挂载,并通过Socket将消息SD卡挂载的消息传递给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卡的挂载流程图可以知道,Android 系统是如何从底层获取SD卡挂载信息的。
后文将继续分析程序调用流程图。
Android 2.3 SD卡挂载流程浅析(三)
在前面两篇博文《Android 2.3 SD卡挂载流程浅析(一)》《Android 2.3 SD卡挂载流程浅析(二)》中,主要简单介绍了SD卡的挂载流程以及所涉及的关键文件。在《Android 2.3 SD卡挂载流程浅析(三)》中,将简要介绍Android 2.3中Vold的运行机制,并从接收内核uevent开始介绍程序调用流程。
1. Vold
Vold的全称是volume daemon。主要负责系统对大容量存储设备(USB/SD)的挂载/卸载任务,它是一个守护进程,该进程支持这些存储外设的热插拔。自Android 2.2开始,Vold升级为vold 2.0,之前的配置文件路径在system/etc/vold.conf,Android 2.3之后变为system/etc/vold.fstab。
2.Vold工作流程
Vold的工作流程大致可以分为三个部分:创建监听、引导、事件处理。
(1)创建监听
创建监听指的是创建监听链接,一方面用于监听来自内核的uevent,另一方面用于监听来自上层的控制命令,这些命令包括控制SD卡的挂载与卸载,这里所说的链接也就是Socket。在Android 系统启动的时候,init进程会去解析init.rc文件,在该文件中,有如下代码:
Service vold /system/bin/vold
Socket vold stream 0660 root mount
Iprio be 2
这样系统会在启动的时候创建与上层通信的Socket。
在Android 2.3源码/system/vold路径下的main.cpp中创建了与内核通信的Socket。在main.cpp中通过实例化VolumeManager和NetlinkManager时创建。
(2)引导
Vold进程启动时候会对现有的外部存储设备进行检查。首先加载并解析vold.fstab,并检查挂载点是否已被挂载。然后执行SD卡的挂载,最后处理USB大容量存储。因为系统是按行解析的,通过查看vold.fstab可以很清楚的知道这一点。
vold.fatab中最重要的语句:
dev_mount sdcard /mnt/sdcard auto /devices/platform/goldfish_mmc.0 /devices/platform/msm_sdcc.2/mmc_host/mmc1
dev_mount <lable> <mount_point> <part> <sysfs_path…>
挂载命令 标签 挂载点 子分区个数 挂载路径
注:
子分区个数如果为auto则表示只有1个子分区,也可以为任何不为0的整数。
参数之间不能有空格,只能以tab为间隔(注意:这里为了对齐因此采用空格隔开,如果自行修改vold.fstab之后加以空格的话系统会识别不到的)。
如果vold.fstab解析无误,VolueManager将创建DirectVolume,若vold.fstab解析不存在或者打开失败,Vold将会读取Linux内核中的参数,此时如果参数中存在SDCARD(也就是SD的默认路径),VolumeManager则会创建AutoVolume,如果不存在这个默认路径那么就不会创建。
(3)事件处理
通过对两个socket的监听,完成对事件的处理以及对上层应用的响应。
a. Kernel发出uevent
NetlinkManager检测到kernel发出的uevent,解析后调用NetlinkHandler::onEvent()方法。该方法会分别处理不同的事件,这里重要的事件有:
“block”事件主要指Volume的mount、unmount、createAsec等。由VolumeManager的handleBlockEvent(evt)来处理,根据多态性最终将会调用AutoVolume或者DirectVolume的handleBlockEvent方法来处理。
“switch”事件主要指Volume的connet、disconnet等。根据相关操作,改变设备参数(设备类型、挂载点等)通过CommandListener告知FrameWork层。
b. FrameWork发出控制命令
与a相反,CommandListener检测到FrameWork层的命令(MountService发出的命令)调用VolumeManager的函数,VolumeManager找出对应的Volume,调用Volume函数去挂载/卸载操作。而Volume类中的相关操作最终通过调用Linux函数完成。
这里再次贴上这张流程图:
3.SD卡挂载流程代码浅析
这里只是简要的分析SD卡挂载过程中重要的代码调用,并没有深入分析代码,因为这一部分网上已有牛人比较详尽的分析了,后面我会贴出这些参考文章。
整个过程从Kernel检测到SD卡插入事件开始,之前的一些硬件中断的触发以及driver的加载这里并不叙述,一直到SD卡挂载消息更新到“Android——系统设置——存储”一项中。
1. Kernel发出SD卡插入uevent。
2. NetlinkHandler::onEvent()接收内核发出的uevent并进行解析。
3. VolumeManager::handlBlockEvent()处理经过第二步处理后的事件。
4. 接下来调用DirectVolume:: handleBlockEvent()。
在该方法中主要有两点需要注意:
第一,程序首先会遍历mPath容器,寻找与event对应的sysfs_path是否存在与mPath容器中。
第二,针对event中的action有4种处理方式:Add,Removed,Change,Noaction 。
例如:在Add action中会有如下操作(因为我们这里所讲的是SD卡的挂载流程,因此以Add来说明),首先创建设备节点,其次对disk和partition两种格式的设备分别进行处理。SD卡属于disk类型。
5. 经过上一步之后会调用DirectVolume::handleDiskAdded()方法,在该方法中会广播disk insert消息。
6. SocketListener::runListener会接收DirectVolume::handleDiskAdded()广播的消息。该方法主要完成对event中数据的获取,通过Socket。(PS:这里的SocketListener.cpp位于Android源码/system/core/libsysutils/src/中,后文的FramworkListener.cpp也是,之前自己找了很久 T_T)
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()方法。(PS:Java与Native通信可以通过JNI,那么Native与Java通信就需要通过Socket来实现了。Android中Native与Frameworks通信 这篇文章中有简介,感兴趣的朋友可以参考一下)
19. onDaemonConnected()方法是在接口INativeDaemonConnectorCallbacks中定义的,MountService实现了该接口并Override了onDaemonConnected()方法。该方法开启一个线程用于更新外置存储设备的状态,主要更新状态的方法也在其中实现。
20. 然后回到ListenToSocket中,通过inputStream来获取Vold传递来的event,并存放在队列中。
21. 然后这些event会在onDaemonConnected()通过队列的”队列.take()”方法取出。并根据不同的event调用updatePublicVolumeState()方法,在该方法中调用packageManagerService中的updateExteralState()方法来更新存储设备的状态。(注:这里不太理解packageManagerService中的unloadAllContainers(args)方法)
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卡的挂载从底层到达了上层。
在经过了上面步骤之后,SD卡的挂载的消息已经从底层到达了上层。这是自己在网上查找资料同时一边跟踪代码后得出的结论,其中可能还有很多不正确的地方,也有很多自己没有理解的地方,希望大家能够帮忙指正,感激不尽。
后续将继续分析SD挂载广播的发出流程,以及SD卡挂载程序调用流程图。
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
- 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);
- } else if (!strcmp(subsys, "switch")) {
- vm->handleSwitchEvent(evt);
- } else if (!strcmp(subsys, "usb_composite")) {
- vm->handleUsbCompositeEvent(evt);
- } else if (!strcmp(subsys, "battery")) {
- } else if (!strcmp(subsys, "power_supply")) {
- }
- }
- 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);
- } else if (!strcmp(subsys, "switch")) {
- vm->handleSwitchEvent(evt);
- } else if (!strcmp(subsys, "usb_composite")) {
- vm->handleUsbCompositeEvent(evt);
- } else if (!strcmp(subsys, "battery")) {
- } else if (!strcmp(subsys, "power_supply")) {
- }
- }
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。
- 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)) {
- #ifdef NETLINK_DEBUG
- SLOGD("Device '%s' event handled by volume %s\n", devpath, (*it)->getLabel());
- #endif
- hit = true;
- break;
- }
- }
-
- if (!hit) {
- #ifdef NETLINK_DEBUG
- SLOGW("No volumes handled block event for '%s'", devpath);
- #endif
- }
- }
- 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)) {
- #ifdef NETLINK_DEBUG
- SLOGD("Device '%s' event handled by volume %s\n", devpath, (*it)->getLabel());
- #endif
- hit = true;
- break;
- }
- }
-
- if (!hit) {
- #ifdef NETLINK_DEBUG
- SLOGW("No volumes handled block event for '%s'", devpath);
- #endif
- }
- }
3.对Block挂载事件进行处理
DirectVolume::handleBlockEvent(NetlinkEvent *evt)
//代码路径:AndroidSourcecode2.3/system/vold/DirectVolume.cpp
//在Add action中首先会创建设备节点,然后对disk和partion两种格式的设备分别进行处理。这里是disk格式。
- int DirectVolume::handleBlockEvent(NetlinkEvent *evt) {
- const char *dp = evt->findParam("DEVPATH");
-
- PathCollection::iterator it;
- for (it = mPaths->begin(); it != mPaths->end(); ++it) {
- if (!strncmp(dp, *it, strlen(*it))) {
-
- int action = evt->getAction();
- const char *devtype = evt->findParam("DEVTYPE");
-
- if (action == NetlinkEvent::NlActionAdd) {
- int major = atoi(evt->findParam("MAJOR"));
- int minor = atoi(evt->findParam("MINOR"));
- char nodepath[255];
-
- snprintf(nodepath,
- sizeof(nodepath), "/dev/block/vold/%d:%d",
- major, minor);
- if (createDeviceNode(nodepath, major, minor)) {
- SLOGE("Error making device node '%s' (%s)", nodepath,
- strerror(errno));
- }
- if (!strcmp(devtype, "disk")) {
- <span style="color:#ff0000;">handleDiskAdded(dp, evt);<span style="color:#33cc00;">
- } else {
- handlePartitionAdded(dp, evt);
- }
- } else if (action == NetlinkEvent::NlActionRemove) {
- if (!strcmp(devtype, "disk")) {
- handleDiskRemoved(dp, evt);
- } else {
- handlePartitionRemoved(dp, evt);
- }
- } else if (action == NetlinkEvent::NlActionChange) {
- if (!strcmp(devtype, "disk")) {
- handleDiskChanged(dp, evt);
- } else {
- handlePartitionChanged(dp, evt);
- }
- } else {
- SLOGW("Ignoring non add/remove/change event");
- }
-
- return 0;
- }
- }
- errno = ENODEV;
- return -1;
- }
- int
- DirectVolume::handleBlockEvent(NetlinkEvent *evt) {
- const char *dp = evt->findParam("DEVPATH");
-
- PathCollection::iterator it;
- for (it = mPaths->begin(); it != mPaths->end(); ++it) {
- if (!strncmp(dp, *it, strlen(*it))) {
-
- int action = evt->getAction();
- const char *devtype = evt->findParam("DEVTYPE");
-
- if (action == NetlinkEvent::NlActionAdd) {
- int major = atoi(evt->findParam("MAJOR"));
- int minor = atoi(evt->findParam("MINOR"));
- char nodepath[255];
-
- snprintf(nodepath,
- sizeof(nodepath), "/dev/block/vold/%d:%d",
- major, minor);
- if (createDeviceNode(nodepath, major, minor)) {
- SLOGE("Error making device node '%s' (%s)",
- nodepath,
-
- strerror(errno));
- }
- if (!strcmp(devtype, "disk")) {
- <span
- style="color:#ff0000;">handleDiskAdded(dp, evt);<span
- style="color:#33cc00;">
- } else {
- handlePartitionAdded(dp, evt);
- }
- } else if (action == NetlinkEvent::NlActionRemove) {
- if (!strcmp(devtype, "disk")) {
- handleDiskRemoved(dp, evt);
- } else {
- handlePartitionRemoved(dp, evt);
- }
- } else if (action == NetlinkEvent::NlActionChange) {
- if (!strcmp(devtype, "disk")) {
- handleDiskChanged(dp, evt);
- } else {
- handlePartitionChanged(dp, evt);
- }
- } else {
- SLOGW("Ignoring non add/remove/change event");
- }
-
- return 0;
- }
- }
- errno = ENODEV;
- return -1;
- }
4.处理DiskAdd事件
DirectVolume::handleDiskAdded(const char *devpath, NetlinkEvent *evt)
//代码路径:AndroidSourcecode2.3/system/vold/DirectVolume.cpp
//在该方法中广播disk insert的广播消息(这里的广播不同于Java中的广播,这里实际上是Socket)。
- void DirectVolume::handleDiskAdded(const char *devpath, NetlinkEvent *evt) {
- mDiskMajor = atoi(evt->findParam("MAJOR"));
- mDiskMinor = atoi(evt->findParam("MINOR"));
-
- const char *tmp = evt->findParam("NPARTS");
- if (tmp) {
- mDiskNumParts = atoi(tmp);
- } else {
- SLOGW("Kernel block uevent missing 'NPARTS'");
- mDiskNumParts = 1;
- }
-
- char msg[255];
-
- int partmask = 0;
- int i;
- for (i = 1; i <= mDiskNumParts; i++) {
- partmask |= (1 << i);
- }
- mPendingPartMap = partmask;
-
- if (mDiskNumParts == 0) {
- #ifdef PARTITION_DEBUG
- SLOGD("Dv::diskIns - No partitions - good to go son!");
- #endif
- setState(Volume::State_Idle);
- } else {
- #ifdef PARTITION_DEBUG
- SLOGD("Dv::diskIns - waiting for %d partitions (mask 0x%x)",
- mDiskNumParts, mPendingPartMap);
- #endif
- setState(Volume::State_Pending);
- }
-
- snprintf(msg, sizeof(msg), "Volume %s %s disk inserted (%d:%d)",
- getLabel(), getMountpoint(), mDiskMajor, mDiskMinor);
- <span style="color:#ff0000;">mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeDiskInserted,
- msg, false);
- </span>}
- void DirectVolume::handleDiskAdded(const char *devpath, NetlinkEvent *evt) {
- mDiskMajor = atoi(evt->findParam("MAJOR"));
- mDiskMinor = atoi(evt->findParam("MINOR"));
-
- const char *tmp = evt->findParam("NPARTS");
- if (tmp) {
- mDiskNumParts = atoi(tmp);
- } else {
- SLOGW("Kernel block uevent missing 'NPARTS'");
- mDiskNumParts = 1;
- }
-
- char msg[255];
-
- int partmask = 0;
- int i;
- for (i = 1; i <= mDiskNumParts; i++) {
- partmask |= (1 << i);
- }
- mPendingPartMap = partmask;
-
- if (mDiskNumParts == 0) {
- #ifdef PARTITION_DEBUG
- SLOGD("Dv::diskIns - No partitions - good to go son!");
- #endif
- setState(Volume::State_Idle);
- } else {
- #ifdef PARTITION_DEBUG
- SLOGD("Dv::diskIns - waiting for %d partitions (mask 0x%x)",
- mDiskNumParts, mPendingPartMap);
- #endif
- setState(Volume::State_Pending);
- }
-
- snprintf(msg, sizeof(msg), "Volume %s %s disk inserted (%d:%d)",
- getLabel(), getMountpoint(), mDiskMajor, mDiskMinor);
- <span style="color:#ff0000;">mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeDiskInserted,
- msg, false);
- </span>}
5.处理广播消息
SocketListener::runListener()
//代码路径:AndroidSourcecode2.3/system/core/libsysutils/src/SocketListener.cpp
//完成对Socket的监听以及对数据的处理onDataAvailable(* it );
- void SocketListener::runListener() {
-
- while(1) {
- SocketClientCollection::iterator it;
- fd_set read_fds;
- int rc = 0;
- int max = 0;
-
- FD_ZERO(&read_fds);
-
- if (mListen) {
- max = mSock;
- FD_SET(mSock, &read_fds);
- }
-
- FD_SET(mCtrlPipe[0], &read_fds);
- if (mCtrlPipe[0] > max)
- max = mCtrlPipe[0];
-
- pthread_mutex_lock(&mClientsLock);
- for (it = mClients->begin(); it != mClients->end(); ++it) {
- FD_SET((*it)->getSocket(), &read_fds);
- if ((*it)->getSocket() > max)
- max = (*it)->getSocket();
- }
- pthread_mutex_unlock(&mClientsLock);
-
- if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {
- SLOGE("select failed (%s)", strerror(errno));
- sleep(1);
- continue;
- } else if (!rc)
- continue;
-
- if (FD_ISSET(mCtrlPipe[0], &read_fds))
- break;
- if (mListen && FD_ISSET(mSock, &read_fds)) {
- struct sockaddr addr;
- socklen_t alen = sizeof(addr);
- int c;
-
- if ((c = accept(mSock, &addr, &alen)) < 0) {
- SLOGE("accept failed (%s)", strerror(errno));
- sleep(1);
- continue;
- }
- pthread_mutex_lock(&mClientsLock);
- mClients->push_back(new SocketClient(c));
- pthread_mutex_unlock(&mClientsLock);
- }
-
- do {
- pthread_mutex_lock(&mClientsLock);
- for (it = mClients->begin(); it != mClients->end(); ++it) {
- int fd = (*it)->getSocket();
- if (FD_ISSET(fd, &read_fds)) {
- pthread_mutex_unlock(&mClientsLock);
- if (!<span style="color:#ff0000;">onDataAvailable(*it)</span>) {
- close(fd);
- pthread_mutex_lock(&mClientsLock);
- delete *it;
- it = mClients->erase(it);
- pthread_mutex_unlock(&mClientsLock);
- }
- FD_CLR(fd, &read_fds);
- pthread_mutex_lock(&mClientsLock);
- continue;
- }
- }
- pthread_mutex_unlock(&mClientsLock);
- } while (0);
- }
- }
- void SocketListener::runListener() {
-
- while(1) {
- SocketClientCollection::iterator it;
- fd_set read_fds;
- int rc = 0;
- int max = 0;
-
- FD_ZERO(&read_fds);
-
- if (mListen) {
- max = mSock;
- FD_SET(mSock, &read_fds);
- }
-
- FD_SET(mCtrlPipe[0], &read_fds);
- if (mCtrlPipe[0] > max)
- max = mCtrlPipe[0];
-
- pthread_mutex_lock(&mClientsLock);
- for (it = mClients->begin(); it != mClients->end(); ++it) {
- FD_SET((*it)->getSocket(), &read_fds);
- if ((*it)->getSocket() > max)
- max = (*it)->getSocket();
- }
- pthread_mutex_unlock(&mClientsLock);
-
- if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {
- SLOGE("select failed (%s)", strerror(errno));
- sleep(1);
- continue;
- } else if (!rc)
- continue;
-
- if (FD_ISSET(mCtrlPipe[0], &read_fds))
- break;
- if (mListen && FD_ISSET(mSock, &read_fds)) {
- struct sockaddr addr;
- socklen_t alen = sizeof(addr);
- int c;
-
- if ((c = accept(mSock, &addr, &alen)) < 0) {
- SLOGE("accept failed (%s)", strerror(errno));
- sleep(1);
- continue;
- }
- pthread_mutex_lock(&mClientsLock);
- mClients->push_back(new SocketClient(c));
- pthread_mutex_unlock(&mClientsLock);
- }
-
- do {
- pthread_mutex_lock(&mClientsLock);
- for (it = mClients->begin(); it != mClients->end(); ++it) {
- int fd = (*it)->getSocket();
- if (FD_ISSET(fd, &read_fds)) {
- pthread_mutex_unlock(&mClientsLock);
- if (!<span style="color:#ff0000;">onDataAvailable(*it)</span>) {
- close(fd);
- pthread_mutex_lock(&mClientsLock);
- delete *it;
- it = mClients->erase(it);
- pthread_mutex_unlock(&mClientsLock);
- }
- FD_CLR(fd, &read_fds);
- pthread_mutex_lock(&mClientsLock);
- continue;
- }
- }
- pthread_mutex_unlock(&mClientsLock);
- } while (0);
- }
- }
6.处理消息内容
FrameworkListener::onDataAvailable(SocketClient *c)
//代码路径:AndroidSourcecode2.3/system/core/libsysutils/src/FrameworkListener.cpp
//对接收到的广播消息进行处理
- bool FrameworkListener::onDataAvailable(SocketClient *c) {
- char buffer[255];
- int len;
-
- if ((len = read(c->getSocket(), buffer, sizeof(buffer) -1)) < 0) {
- SLOGE("read() failed (%s)", strerror(errno));
- return false;
- } else if (!len)
- return false;
-
- int offset = 0;
- int i;
-
- for (i = 0; i < len; i++) {
- if (buffer[i] == '\0') {
- <span style="color:#ff0000;">dispatchCommand</span>(c, buffer + offset);
- offset = i + 1;
- }
- }
- return true;
- }
- bool FrameworkListener::onDataAvailable(SocketClient *c) {
- char buffer[255];
- int len;
-
- if ((len = read(c->getSocket(), buffer, sizeof(buffer) -1)) < 0) {
- SLOGE("read() failed (%s)", strerror(errno));
- return false;
- } else if (!len)
- return false;
-
- int offset = 0;
- int i;
-
- for (i = 0; i < len; i++) {
- if (buffer[i] == '\0') {
- <span style="color:#ff0000;">dispatchCommand</span>(c, buffer + offset);
- offset = i + 1;
- }
- }
- return true;
- }
7.分发指令
FrameworkListener::dispatchCommand(SocketClient *cli, char *data)
//代码路径:AndroidSourcecode2.3/system/core/libsysutils/src/FrameworkListener.cpp
//分配指令:DumpCmd、VolumeCmd、AsecCmd、ShareCmd、StorageCmd、XwarpCmd
- void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {
- FrameworkCommandCollection::iterator i;
- int argc = 0;
- char *argv[FrameworkListener::CMD_ARGS_MAX];
- char tmp[255];
- char *p = data;
- char *q = tmp;
- bool esc = false;
- bool quote = false;
- int k;
-
- memset(argv, 0, sizeof(argv));
- memset(tmp, 0, sizeof(tmp));
- while(*p) {
- if (*p == '\\') {
- if (esc) {
- *q++ = '\\';
- esc = false;
- } else
- esc = true;
- p++;
- continue;
- } else if (esc) {
- if (*p == '"')
- *q++ = '"';
- else if (*p == '\\')
- *q++ = '\\';
- else {
- cli->sendMsg(500, "Unsupported escape sequence", false);
- goto out;
- }
- p++;
- esc = false;
- continue;
- }
-
- if (*p == '"') {
- if (quote)
- quote = false;
- else
- quote = true;
- p++;
- continue;
- }
-
- *q = *p++;
- if (!quote && *q == ' ') {
- *q = '\0';
- argv[argc++] = strdup(tmp);
- memset(tmp, 0, sizeof(tmp));
- q = tmp;
- continue;
- }
- q++;
- }
-
- argv[argc++] = strdup(tmp);
- #if 0
- for (k = 0; k < argc; k++) {
- SLOGD("arg[%d] = '%s'", k, argv[k]);
- }
- #endif
-
- if (quote) {
- cli->sendMsg(500, "Unclosed quotes error", false);
- goto out;
- }
-
- for (i = mCommands->begin(); i != mCommands->end(); ++i) {
- FrameworkCommand *c = *i;
-
- if (!strcmp(argv[0], c->getCommand())) {
- if (c-><span style="color:#ff0000;">runCommand</span>(cli, argc, argv)) {
- SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));
- }
- goto out;
- }
- }
-
- cli->sendMsg(500, "Command not recognized", false);
- out:
- int j;
- for (j = 0; j < argc; j++)
- free(argv[j]);
- return;
- }
- void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {
- FrameworkCommandCollection::iterator i;
- int argc = 0;
- char *argv[FrameworkListener::CMD_ARGS_MAX];
- char tmp[255];
- char *p = data;
- char *q = tmp;
- bool esc = false;
- bool quote = false;
- int k;
-
- memset(argv, 0, sizeof(argv));
- memset(tmp, 0, sizeof(tmp));
- while(*p) {
- if (*p == '\\') {
- if (esc) {
- *q++ = '\\';
- esc = false;
- } else
- esc = true;
- p++;
- continue;
- } else if (esc) {
- if (*p == '"')
- *q++ = '"';
- else if (*p == '\\')
- *q++ = '\\';
- else {
- cli->sendMsg(500, "Unsupported escape sequence", false);
- goto out;
- }
- p++;
- esc = false;
- continue;
- }
-
- if (*p == '"') {
- if (quote)
- quote = false;
- else
- quote = true;
- p++;
- continue;
- }
-
- *q = *p++;
- if (!quote && *q == ' ') {
- *q = '\0';
- argv[argc++] = strdup(tmp);
- memset(tmp, 0, sizeof(tmp));
- q = tmp;
- continue;
- }
- q++;
- }
-
- argv[argc++] = strdup(tmp);
- #if 0
- for (k = 0; k < argc; k++) {
- SLOGD("arg[%d] = '%s'", k, argv[k]);
- }
- #endif
-
- if (quote) {
- cli->sendMsg(500, "Unclosed quotes error", false);
- goto out;
- }
-
- for (i = mCommands->begin(); i != mCommands->end(); ++i) {
- FrameworkCommand *c = *i;
-
- if (!strcmp(argv[0], c->getCommand())) {
- if (c-><span style="color:#ff0000;">runCommand</span>(cli, argc, argv)) {
- SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));
- }
- goto out;
- }
- }
-
- cli->sendMsg(500, "Command not recognized", false);
- out:
- int j;
- for (j = 0; j < argc; j++)
- free(argv[j]);
- return;
- }
8.开始执行挂载
CommandListener::VolumeCmd::runCommand(SocketClient *cli,int argc, char **argv)
//代码路径:AndroidSourcecode2.3/system/vold/CommandListener.cpp
//rc = vm->mountVolume(argv[2]);
- int CommandListener::VolumeCmd::runCommand(SocketClient *cli,int argc, char **argv) {
- dumpArgs(argc, argv, -1);
-
- if (argc < 2) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing Argument", false);
- return 0;
- }
-
- VolumeManager *vm = VolumeManager::Instance();
- int rc = 0;
-
- if (!strcmp(argv[1], "list")) {
- return vm->listVolumes(cli);
- } else if (!strcmp(argv[1], "debug")) {
- if (argc != 3 || (argc == 3 && (strcmp(argv[2], "off") && strcmp(argv[2], "on")))) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume debug <off/on>", false);
- return 0;
- }
- vm->setDebug(!strcmp(argv[2], "on") ? true : false);
- } else if (!strcmp(argv[1], "mount")) {
- if (argc != 3) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume mount <path>", false);
- return 0;
- }
- <span style="color:#ff0000;">rc = vm->mountVolume(argv[2]);</span>
- } else if (!strcmp(argv[1], "unmount")) {
- if (argc < 3 || argc > 4 || (argc == 4 && strcmp(argv[3], "force"))) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume unmount <path> [force]", false);
- return 0;
- }
-
- bool force = false;
- if (argc >= 4 && !strcmp(argv[3], "force")) {
- force = true;
- }
- rc = vm->unmountVolume(argv[2], force);
- } else if (!strcmp(argv[1], "format")) {
- if (argc != 3) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume format <path>", false);
- return 0;
- }
- rc = vm->formatVolume(argv[2]);
- } else if (!strcmp(argv[1], "share")) {
- if (argc != 4) {
- cli->sendMsg(ResponseCode::CommandSyntaxError,
- "Usage: volume share <path> <method>", false);
- return 0;
- }
- rc = vm->shareVolume(argv[2], argv[3]);
- } else if (!strcmp(argv[1], "unshare")) {
- if (argc != 4) {
- cli->sendMsg(ResponseCode::CommandSyntaxError,
- "Usage: volume unshare <path> <method>", false);
- return 0;
- }
- rc = vm->unshareVolume(argv[2], argv[3]);
- } else if (!strcmp(argv[1], "shared")) {
- bool enabled = false;
- if (argc != 4) {
- cli->sendMsg(ResponseCode::CommandSyntaxError,
- "Usage: volume shared <path> <method>", false);
- return 0;
- }
-
- if (vm->shareEnabled(argv[2], argv[3], &enabled)) {
- cli->sendMsg(
- ResponseCode::OperationFailed, "Failed to determine share enable state", true);
- } else {
- cli->sendMsg(ResponseCode::ShareEnabledResult,
- (enabled ? "Share enabled" : "Share disabled"), false);
- }
- return 0;
- } else {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume cmd", false);
- }
-
- if (!rc) {
- cli->sendMsg(ResponseCode::CommandOkay, "volume operation succeeded", false);
- } else {
- int erno = errno;
- rc = ResponseCode::convertFromErrno();
- cli->sendMsg(rc, "volume operation failed", true);
- }
-
- return 0;
- }
- int CommandListener::VolumeCmd::runCommand(SocketClient *cli,int argc, char **argv) {
- dumpArgs(argc, argv, -1);
-
- if (argc < 2) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing Argument", false);
- return 0;
- }
-
- VolumeManager *vm = VolumeManager::Instance();
- int rc = 0;
-
- if (!strcmp(argv[1], "list")) {
- return vm->listVolumes(cli);
- } else if (!strcmp(argv[1], "debug")) {
- if (argc != 3 || (argc == 3 && (strcmp(argv[2], "off") && strcmp(argv[2], "on")))) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume debug <off/on>", false);
- return 0;
- }
- vm->setDebug(!strcmp(argv[2], "on") ? true : false);
- } else if (!strcmp(argv[1], "mount")) {
- if (argc != 3) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume mount <path>", false);
- return 0;
- }
- <span style="color:#ff0000;">rc = vm->mountVolume(argv[2]);</span>
- } else if (!strcmp(argv[1], "unmount")) {
- if (argc < 3 || argc > 4 || (argc == 4 && strcmp(argv[3], "force"))) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume unmount <path> [force]", false);
- return 0;
- }
-
- bool force = false;
- if (argc >= 4 && !strcmp(argv[3], "force")) {
- force = true;
- }
- rc = vm->unmountVolume(argv[2], force);
- } else if (!strcmp(argv[1], "format")) {
- if (argc != 3) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume format <path>", false);
- return 0;
- }
- rc = vm->formatVolume(argv[2]);
- } else if (!strcmp(argv[1], "share")) {
- if (argc != 4) {
- cli->sendMsg(ResponseCode::CommandSyntaxError,
- "Usage: volume share <path> <method>", false);
- return 0;
- }
- rc = vm->shareVolume(argv[2], argv[3]);
- } else if (!strcmp(argv[1], "unshare")) {
- if (argc != 4) {
- cli->sendMsg(ResponseCode::CommandSyntaxError,
- "Usage: volume unshare <path> <method>", false);
- return 0;
- }
- rc = vm->unshareVolume(argv[2], argv[3]);
- } else if (!strcmp(argv[1], "shared")) {
- bool enabled = false;
- if (argc != 4) {
- cli->sendMsg(ResponseCode::CommandSyntaxError,
- "Usage: volume shared <path> <method>", false);
- return 0;
- }
-
- if (vm->shareEnabled(argv[2], argv[3], &enabled)) {
- cli->sendMsg(
- ResponseCode::OperationFailed, "Failed to determine share enable state", true);
- } else {
- cli->sendMsg(ResponseCode::ShareEnabledResult,
- (enabled ? "Share enabled" : "Share disabled"), false);
- }
- return 0;
- } else {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume cmd", false);
- }
-
- if (!rc) {
- cli->sendMsg(ResponseCode::CommandOkay, "volume operation succeeded", false);
- } else {
- int erno = errno;
- rc = ResponseCode::convertFromErrno();
- cli->sendMsg(rc, "volume operation failed", true);
- }
-
- return 0;
- }
9.调用方法执行挂载动作
VolumeManager::mountVolume(const char *label)
//代码路径:AndroidSourcecode2.3/system/vold/VolumeManager.cpp
//v->mountVol();
- int VolumeManager::mountVolume(const char *label) {
- Volume *v = lookupVolume(label);
-
- if (!v) {
- errno = ENOENT;
- return -1;
- }
-
- return <span style="color:#ff0000;">v->mountVol();
- </span>}
- int VolumeManager::mountVolume(const char *label) {
- Volume *v = lookupVolume(label);
-
- if (!v) {
- errno = ENOENT;
- return -1;
- }
-
- return <span style="color:#ff0000;">v->mountVol();
- </span>}
10.真正的挂载者
Volume::mountVol()
//代码路径:AndroidSourcecode2.3/system/vold/Volume.cpp
//SD卡的挂载最终是在该方法中实现的,如果挂载有异常,也会在该方法的执行过程通过setState方法发出广播。
- int Volume::mountVol() {
- dev_t deviceNodes[4];
- int n, i, rc = 0;
- char errmsg[255];
-
- 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;
- }
-
- if (isMountpointMounted(getMountpoint())) {
- SLOGW("Volume is idle but appears to be mounted - fixing");
- setState(Volume::State_Mounted);
-
- return 0;
- }
-
- n = getDeviceNodes((dev_t *) &deviceNodes, 4);
- if (!n) {
- SLOGE("Failed to get device nodes (%s)\n", strerror(errno));
- return -1;
- }
-
- for (i = 0; i < n; i++) {
- char devicePath[255];
-
- sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(deviceNodes[i]),
- MINOR(deviceNodes[i]));
-
- SLOGI("%s being considered for volume %s\n", devicePath, getLabel());
-
- errno = 0;
- setState(Volume::State_Checking);
-
- if (Fat::check(devicePath)) {
- if (errno == ENODATA) {
- SLOGW("%s does not contain a FAT filesystem\n", devicePath);
- continue;
- }
- errno = EIO;
-
- SLOGE("%s failed FS checks (%s)", devicePath, strerror(errno));
- setState(Volume::State_Idle);
- return -1;
- }
-
-
-
-
-
- errno = 0;
- if (Fat::doMount(devicePath, "/mnt/secure/staging", false, false, false,
- 1000, 1015, 0702, true)) {
- SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
- continue;
- }
-
- SLOGI("Device %s, target %s mounted @ /mnt/secure/staging", devicePath, getMountpoint());
-
- protectFromAutorunStupidity();
-
- if (createBindMounts()) {
- SLOGE("Failed to create bindmounts (%s)", strerror(errno));
- umount("/mnt/secure/staging");
- setState(Volume::State_Idle);
- return -1;
- }
-
-
-
-
-
- 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);
- mCurrentlyMountedKdev = deviceNodes[i];
- return 0;
- }
-
- SLOGE("Volume %s found no suitable devices for mounting :(\n", getLabel());
- setState(Volume::State_Idle);
-
- return -1;
- }
- int Volume::mountVol() {
- dev_t deviceNodes[4];
- int n, i, rc = 0;
- char errmsg[255];
-
- 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;
- }
-
- if (isMountpointMounted(getMountpoint())) {
- SLOGW("Volume is idle but appears to be mounted - fixing");
- setState(Volume::State_Mounted);
-
- return 0;
- }
-
- n = getDeviceNodes((dev_t *) &deviceNodes, 4);
- if (!n) {
- SLOGE("Failed to get device nodes (%s)\n", strerror(errno));
- return -1;
- }
-
- for (i = 0; i < n; i++) {
- char devicePath[255];
-
- sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(deviceNodes[i]),
- MINOR(deviceNodes[i]));
-
- SLOGI("%s being considered for volume %s\n", devicePath, getLabel());
-
- errno = 0;
- setState(Volume::State_Checking);
-
- if (Fat::check(devicePath)) {
- if (errno == ENODATA) {
- SLOGW("%s does not contain a FAT filesystem\n", devicePath);
- continue;
- }
- errno = EIO;
-
- SLOGE("%s failed FS checks (%s)", devicePath, strerror(errno));
- setState(Volume::State_Idle);
- return -1;
- }
-
-
-
-
-
- errno = 0;
- if (Fat::doMount(devicePath, "/mnt/secure/staging", false, false, false,
- 1000, 1015, 0702, true)) {
- SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
- continue;
- }
-
- SLOGI("Device %s, target %s mounted @ /mnt/secure/staging", devicePath, getMountpoint());
-
- protectFromAutorunStupidity();
-
- if (createBindMounts()) {
- SLOGE("Failed to create bindmounts (%s)", strerror(errno));
- umount("/mnt/secure/staging");
- setState(Volume::State_Idle);
- return -1;
- }
-
-
-
-
-
- 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);
- mCurrentlyMountedKdev = deviceNodes[i];
- return 0;
- }
-
- SLOGE("Volume %s found no suitable devices for mounting :(\n", getLabel());
- setState(Volume::State_Idle);
-
- return -1;
- }
到这里SD卡的挂载基本上已经完成,但是我们的目的是理清一条从底向上的路线,因此我会继续向上分析。我会将这一点在下一篇博文《Android 2.3 SD卡挂载流程浅析(五)》中继续分析。
因为这些都是我个人的一些见解,因此不见得都正确,希望读者抱着怀疑的态度阅读。不过不是有句名言叫“大胆假设,小心求证”吗!如文中有错误,还恳请各位看官指正。
Android 2.3 SD卡挂载流程浅析(五)
前面四篇博文:《Android 2.3 SD卡挂载流程浅析(一)》、《Android 2.3 SD卡挂载流程浅析(二)》、《Android 2.3 SD卡挂载流程浅析(三)》、《Android 2.3 SD卡挂载流程浅析(四)》主要是对SD卡的挂载流程从底到上的一个分析,本文将继续接着《Android 2.3 SD卡挂载流程浅析(四)》文章分析,前文主要分析了C/C++的一些代码,本文将主要分析Java代码。废话不多说,依然上这张老图:
图中绿色箭头表示的就是SD卡挂载消息从底向上传递的一个流程。本文主要是分析红色箭头的传递了,因为现在消息要在上层反应出来,这里是从VolumeManager开始分析,我们把从Mount SD/USB到VolumeManager之间的流程总体当作Vold来讲,也就是Vold向上层反馈SD卡挂载的消息。
上文我们分析到,SD卡被doMount方法执行挂载了,该消息由setState方法将消息传递到上层,setState是通过发送一个广播,这里所说的广播不是Android中的BroadCast,这里实际山是Socket,上层负责监听这个Socket,并解析其中的内容。我们需要从MountService.java开始查找。这里我要解释以下为什么要从这里开始找,不是说一开始我就知道这个类里面有我们需要的东西,这是在查找SD卡挂载过程的时候,通过不同的线索联系起来的。那我们先来看看MountService吧。
MountService
位于AndroidSourcecode2.3/frameworks/base/services/java/com/android/server/MountService.java
MountService是一个服务类,通过ServiceManager注册为系统服务,对外部存储设备提供管理和查询等服务,在外部存储设备状态发生改变的时候发出相应的通知给注册了该服务的应用程序。MountService相当于一个中间桥梁,负责接收Vold的消息并传递给上层应用。这里就不详细阐述MountService是如何启动的了,对于Android服务这一块,我将另写后续的文章分析。
MountService在SystemServer.java(AndroidSourcecode2.3/frameworks/base/services/java/com/android/server/SystemServer.java)中启动,在启动的时候调用MountService的构造函数:
- ServiceManager.addService("mount", new MountService(context));
找到MountService的构造函数:
- public MountService(Context context) {
- mContext = context;
-
-
- mPms = (PackageManagerService) ServiceManager.getService("package");
-
- mContext.registerReceiver(mBroadcastReceiver,
- new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
-
- mHandlerThread = new HandlerThread("MountService");
- mHandlerThread.start();
- mHandler = new MountServiceHandler(mHandlerThread.getLooper());
-
-
- mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
-
-
-
-
-
- if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
- mReady = true;
- mUmsEnabling = true;
- return;
- }
-
-
-
-
-
-
- <span style="color:#000000;">mConnector = new NativeDaemonConnector(this, "vold",
- PackageManagerService.MAX_CONTAINERS * 2, VOLD_TAG);
- mReady = false;
- </span><span style="color:#ff0000;"><span style="color:#000000;">Thread thread = new Thread(mConnector, VOLD_TAG);</span><span style="color:#000000;">
- thread.start();</span>
- </span> }
这里我们重点关注最后两句,这两句的意思我相信有一点java基础的人都知道吧,对,没错,就是开启一个新线程,我继续跟踪这个传进来的Runnable对象mConnector,查看NativeDaemonConnector.java后可以知道,该类实现了Runnable接口,同时也覆写了Runnable中的run()方法,在该方法中有一个死循环,主要负责监听来自Vold的Socket消息,这是一个阻塞方法。
1.监听者
listenToSocket();
//代码路径:AndroidSourcecode2.3/frameworks/base/services/java/com/android/server/NativeDaemonConnector.java/run()方法中
//该方法负责监听来自Vold的Socket消息,这些消息包括SD卡的插入,SD的检测,SD卡的挂载等等。
- public void run() {
-
- while (true) {
- try {
- <span style="color:#ff0000;"><span style="color:#000000;">listenToSocket();</span>
- </span> } catch (Exception e) {
- Slog.e(TAG, "Error in NativeDaemonConnector", e);
- SystemClock.sleep(5000);
- }
- }
- }
我们查看listenToSocket()中的代码,如下:
- private void listenToSocket() throws IOException {
- LocalSocket socket = null;<span style="color:#000000;">
-
- try {
- socket = new LocalSocket();
- LocalSocketAddress address = new LocalSocketAddress(mSocket,
- LocalSocketAddress.Namespace.RESERVED);
-
- socket.connect(address);
- <span style="color:#000000;">mCallbacks.onDaemonConnected();</span>
-
- InputStream inputStream = socket.getInputStream();
- mOutputStream = socket.getOutputStream();<span style="color:#000000;">
-
- byte[] buffer = new byte[BUFFER_SIZE];
- int start = 0;
-
- while (true) {
- int count = inputStream.read(buffer, start, BUFFER_SIZE - start);<span style="color:#000000;">
- if (count < 0) break;
-
-
- count += start;
- start = 0;
-
- for (int i = 0; i < count; i++) {
- if (buffer[i] == 0) {
- String event = new String(buffer, start, i - start);
- if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
-
- String[] tokens = event.split(" ");
- try {
- int code = Integer.parseInt(tokens[0]);
-
- if (code >= ResponseCode.UnsolicitedInformational) {
- try {
- if (!mCallbacks.onEvent(code, event, tokens)) {
- Slog.w(TAG, String.format(
- "Unhandled event (%s)", event));
- }
- } catch (Exception ex) {
- Slog.e(TAG, String.format(
- "Error handling '%s'", event), ex);
- }
- } 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;
- }
- }
-
-
-
- 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 (this) {
- 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);
- }
- }
- }
2.处理者
mCallbacks.onDaemonConnected();
//代码路径:AndroidSourcecode2.3/frameworks/base/services/java/com/android/server/MountService.java
//因为MountService实现了INativeDaemonConnectorCallbacks接口并覆写了其中的方法,因此这里会调用MountService中的onDaemonConnected()方法。该方法完成了对挂载消息的处理
- public void onDaemonConnected() {
-
-
-
-
- new Thread() {
- public void run() {
-
-
-
- String path = Environment.getExternalStorageDirectory().getPath();
- String state = Environment.MEDIA_REMOVED;
-
- try {
-
- <span style="color:#ff0000;"><span style="color:#000000;">String[] vols = mConnector.doListCommand(
- "volume list", VoldResponseCode.VolumeListResult);</span>
- </span> for (String volstr : vols) {
- String[] tok = volstr.split(" ");
-
- if (!tok[1].equals(path)) {
- Slog.w(TAG, String.format(
- "Skipping unknown volume '%s'",tok[1]));
- continue;
- }
- int st = Integer.parseInt(tok[2]);
- if (st == VolumeState.NoMedia) {
- state = Environment.MEDIA_REMOVED;
- } else if (st == VolumeState.Idle) {
- state = Environment.MEDIA_UNMOUNTED;
- } else if (st == VolumeState.Mounted) {
- state = Environment.MEDIA_MOUNTED;
- Slog.i(TAG, "Media already mounted on daemon connection");
- } else if (st == VolumeState.Shared) {
- state = Environment.MEDIA_SHARED;
- Slog.i(TAG, "Media shared on daemon connection");
- } else {
- throw new Exception(String.format("Unexpected state %d", st));
- }
- }
- if (state != null) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
- updatePublicVolumeState(path, state);
- }
- } catch (Exception e) {
- Slog.e(TAG, "Error processing initial volume state", e);
- updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
- }
-
- try {
- boolean avail = doGetShareMethodAvailable("ums");
- notifyShareAvailabilityChange("ums", avail);
- } catch (Exception ex) {
- Slog.w(TAG, "Failed to get share availability");
- }
-
-
-
-
- mReady = true;
- }
- }.start();
- }
在该方法中首先执行:
- String[] vols = mConnector.doListCommand(
- "volume list", VoldResponseCode.VolumeListResult);
继续跟踪doListCommand可以知道:
- public String[] doListCommand(String cmd, int expectedResponseCode)
- throws NativeDaemonConnectorException {
-
- ArrayList<String> rsp = <span style="color:#000000;">doCommand</span>(cmd);
- String[] rdata = new String[rsp.size()-1];
- int idx = 0;
-
- for (int i = 0; i < rsp.size(); i++) {
- String line = rsp.get(i);
- try {
- String[] tok = line.split(" ");
- int code = Integer.parseInt(tok[0]);
- if (code == expectedResponseCode) {
- rdata[idx++] = line.substring(tok[0].length() + 1);
- } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
- if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
- int last = rsp.size() -1;
- if (i != last) {
- Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
- for (int j = i; j <= last ; j++) {
- Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
- }
- }
- return rdata;
- } else {
- throw new NativeDaemonConnectorException(
- String.format("Expected list response %d, but got %d",
- expectedResponseCode, code));
- }
- } catch (NumberFormatException nfe) {
- throw new NativeDaemonConnectorException(
- String.format("Error reading code '%s'", line));
- }
- }
- throw new NativeDaemonConnectorException("Got an empty response");
- }
继续跟踪doCommand:
- public synchronized ArrayList<String> doCommand(String cmd)
- throws NativeDaemonConnectorException {
- mResponseQueue.clear();
- <span style="color:#000000;">sendCommand</span>(cmd);
-
- ArrayList<String> response = new ArrayList<String>();
- boolean complete = false;
- int code = -1;
-
- while (!complete) {
- try {
-
- String line = mResponseQueue.take();
- if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
- String[] tokens = line.split(" ");
- try {
- code = Integer.parseInt(tokens[0]);
- } catch (NumberFormatException nfe) {
- throw new NativeDaemonConnectorException(
- String.format("Invalid response from daemon (%s)", line));
- }
-
- if ((code >= 200) && (code < 600)) {
- complete = true;
- }
- response.add(line);
- } catch (InterruptedException ex) {
- Slog.e(TAG, "Failed to process response", ex);
- }
- }
-
- if (code >= ResponseCode.FailedRangeStart &&
- code <= ResponseCode.FailedRangeEnd) {
-
-
-
-
- throw new NativeDaemonConnectorException(
- code, cmd, response.get(response.size()-1).substring(4));
- }
- return response;
- <pre></pre>
- <span style="font-size:18px"> 返回doListCommand方法中,大致信息是从ArrayList中取出之前存入的符合要求的event,然后对这些event进行拆分,并截取其中的前部分存放在rdata这个字符串数组中返回。</span>
- <p></p>
- <p><span style="font-size:18px"> 这里继续返回到onDaemonConnected()新开的线程中,接着往下走,对返回的这个字符串数组再次进行分拆并分析,从代码中可以知道,这些字符串中存储了SD卡的挂载路径以及目前的状态信息。因为我们从之前的分析中可以知道,我们的SD卡已经挂载成功了,因此这里的状态是state = Environment.MEDIA_MOUNTED然后执行updatePublicVolumeState(path, state);方法。</span></p>
- <p><span style="font-size:18px"> 3.中转站</span></p>
- <p><span style="font-size:18px"> private void updatePublicVolumeState(String path, String state)</span></p>
- <p><span style="font-size:18px"> <span style="font-size:18px">
- <p><span style="font-size:18px"></span></p>
- <pre name="code" class="java"> private void updatePublicVolumeState(String path, String state) {
- if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
- Slog.w(TAG, "Multiple volumes not currently supported");
- return;
- }
-
- if (mLegacyState.equals(state)) {
- Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
- return;
- }
-
- if (Environment.MEDIA_UNMOUNTED.equals(state)) {
-
- mPms.updateExternalMediaStatus(false, false);
-
-
-
-
-
-
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE,
- path));
- }<span style="color:#ff0000;"> <span style="color:#000000;">else if (Environment.MEDIA_MOUNTED.equals(state)) {
-
- mPms.updateExternalMediaStatus(true, false);</span>
- </span>}
-
- String oldState = mLegacyState;
- mLegacyState = state;
-
- synchronized (mListeners) {
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- 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);
- }
- }
- }
- }</pre> <span style="font-size:18px">这里首先是执行</span>
- <p></p>
- <p><span style="font-size:18px"></span></p>
- <pre name="code" class="java">mPms.updateExternalMediaStatus(true, false);</pre> <span style="font-size:18px">该方法位于PackageManagerService.java中,作用是告诉PackageManager外置media可用。在updateExternalMediaStatus方法中,通知PackageManagerService去更新外置media的状态,这包括了读取SD卡中的内容并识别。通过这个步骤以后,我们打开SD卡才能发现哪些东西是系统已经识别的,哪些东西系统不能识别。</span>
- <p></p>
- <p><span style="font-size:18px"> 接下来我们看看</span></p>
- <p><span style="font-size:18px"></span></p>
- <pre name="code" class="java"> synchronized (mListeners) {
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- 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);
- }
- }
- }</pre> <span style="font-size:18px">这是一个同步块,最重要的一句代码是</span><pre name="code" class="java">bl.mListener.onStorageStateChanged(path, oldState, state);
- </pre> <span style="font-size:18px">分析到这里,如果不去了解StorageManager和MountService关系的话,后面是没有办法分析下去的。我们的目的是从底层SD卡的挂载信息如何传递到上层的"设置-存储-SD卡"这个界面中,从而理清一条从底向上的线路。如果对于这一块也有疑问的朋友,希望能够真的去看看源码,并逐步自己一步一步的跟踪看看,有的时候真的有的复杂,很多机制不懂更多的是没听过的机制,但是只要自己想要弄清楚,那么就坚持下去吧。一开始我插入SD卡系统居然有时候识别不到,我就跟踪上层的源码,结果发现解决不了问题,那么就跟踪下去吧,不会的就一边查资料一边问别人,同时一边做记录,这些记录一方面可以帮助自己整理学习的资料,另一方面可以帮助也遇到同样问题的朋友。所以在此写下这些自己的拙见,错误百出但初衷是单纯的。</span>
- <p></p>
- <p><span style="font-size:18px"> 说了这么多废话,下一篇文章将继续分析SD卡挂载消息是如何在"设置"中显示出来的。<br>
- </span></p>
- <p><br>
- </p>
- <pre></pre>
Android 2.3 SD卡挂载流程浅析(六)
前面五篇文章:
《Android 2.3 SD卡挂载流程浅析(一)》
《Android 2.3 SD卡挂载流程浅析(二)》
《Android 2.3 SD卡挂载流程浅析(三)》
《Android 2.3 SD卡挂载流程浅析(四)》
《Android 2.3 SD卡挂载流程浅析(五)》
在这五篇文章中,简单的分析了将SD卡插入的消息从底层传递到了上层的流程,但并没有深入分析毕竟我的目的是理清一条清晰的消息传递路线,最终目标是再系统的设置界面中显示出来,因此,本文将继续分析该流程。但是本文的分析又不同于前面五篇文章,因为本文是通过从上到下来分析,将分析流程反过来。
1.首先找到系统设置的源码。
路径:AndroidSorceCode2.3/package/app/Settings/src/com/android/settings/deviceinfo/Memory.java
该文件其实就是我们打开系统设置-存储的一个界面,如下图:
在设置中点击存储便进入到Memory的界面,如下:
如果已经插入SD卡并且系统已经挂载了的话,这里会有显示。也就是说我们的最终目标在这里,SD卡的挂载信息是如何传递到这里的。我们继续回到Memory.java文件中。我们要如何知道一步该做什么呢?我们先在Eclipse中的logcat添加TAG名为Memory的TAG,然后插入SD卡,我们会发现有以下log输出:
这就是我们需要的关键点,因为这句log是从Memory.java中输出的,因此我们首先要找到该log的出处:
- <span style="font-size:18px;"> StorageEventListener mStorageListener = new StorageEventListener() {
-
- @Override
- public void onStorageStateChanged(String path, String oldState, String newState) {
- Log.i(TAG, "Received storage state changed notification that " +
- path + " changed state from " + oldState +
- " to " + newState);
- updateMemoryStatus();
- }
- };</span>
从以上代码中可以知道,这里就是输出关键log的地方,换句话说当我们插入SD卡的时候,系统触发这个onStroageStateChanged()方法,在该方法中一并执行了updateMemoryStatus()方法,我们跟踪进入updateMemoryStatus()方法看看,根据这名字我们大致可以猜测其作用是更新存储设备的状态信息:
- <span style="font-size:18px;"> private void updateMemoryStatus() {
- String status = Environment.getExternalStorageState();
- String readOnly = "";
- if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
- status = Environment.MEDIA_MOUNTED;
- readOnly = mRes.getString(R.string.read_only);
- }
-
- if (status.equals(Environment.MEDIA_MOUNTED)) {
- if (!Environment.isExternalStorageRemovable()) {
-
-
- if (mSdMountToggleAdded) {
- mSdMountPreferenceGroup.removePreference(mSdMountToggle);
- mSdMountToggleAdded = false;
- }
- }
- try {
- File path = Environment.getExternalStorageDirectory();
- StatFs stat = new StatFs(path.getPath());
- long blockSize = stat.getBlockSize();
- long totalBlocks = stat.getBlockCount();
- long availableBlocks = stat.getAvailableBlocks();
-
- mSdSize.setSummary(formatSize(totalBlocks * blockSize));
- mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly);
-
- mSdMountToggle.setEnabled(true);
- mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
- mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
-
- } catch (IllegalArgumentException e) {
-
-
- status = Environment.MEDIA_REMOVED;
- }
-
- } else {
- mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
- mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
-
-
- if (!Environment.isExternalStorageRemovable()) {
- if (status.equals(Environment.MEDIA_UNMOUNTED)) {
- if (!mSdMountToggleAdded) {
- mSdMountPreferenceGroup.addPreference(mSdMountToggle);
- mSdMountToggleAdded = true;
- }
- }
- }
-
- if (status.equals(Environment.MEDIA_UNMOUNTED) ||
- status.equals(Environment.MEDIA_NOFS) ||
- status.equals(Environment.MEDIA_UNMOUNTABLE) ) {
- mSdMountToggle.setEnabled(true);
- mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
- mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
- } else {
- mSdMountToggle.setEnabled(false);
- mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
- mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
- }
- }
-
- File path = Environment.getDataDirectory();
- StatFs stat = new StatFs(path.getPath());
- long blockSize = stat.getBlockSize();
- long availableBlocks = stat.getAvailableBlocks();
- findPreference("memory_internal_avail").setSummary(formatSize(availableBlocks * blockSize));
- }</span>
果然不出我们所料,这里也就是真正更新设置-存储界面里面信息的方法。
2.跟着源码中的Memory.java顺藤摸瓜
我们回到StorageEventListener实例化对象的地方:
- <span style="font-size:18px;"> StorageEventListener mStorageListener = new StorageEventListener() {
-
- @Override
- public void onStorageStateChanged(String path, String oldState, String newState) {
- Log.i(TAG, "Received storage state changed notification that " +
- path + " changed state from " + oldState +
- " to " + newState);
- updateMemoryStatus();
- }
- };</span>
通过代码我们可以知道,StorageEventListener是一个抽象类,在这里通过实例化自己的对象并用匿名内部类实现了自己定义中的抽象方法。我接着回到Memory.java中的onCreate()方法中:
- <span style="font-size:18px;"> if (mStorageManager == null) {
- mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
- mStorageManager.registerListener(mStorageListener);
- }</span>
在这里我们可以看到,StorageEventListener的对象mStorageListener通过StorageManager的方法registerListener()完成注册。这里我们需要详细了解一下这个注册的过程,因为这里所谓的注册就为后面的触发埋下了伏笔,注册存储事件监听器(StorageEventListener)的目的就是为了在存储设备状态发生改变并触发事件的时候,接收并处理这些事件。
(1).mStorageManager初始化
- <span style="font-size:18px;">mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);</span>
我直接跟踪getSystemService()方法,首先会跳转到Activity.java中的getSystemService()方法中:
- <span style="font-size:18px;"> @Override
- public Object getSystemService(String name) {
- if (getBaseContext() == null) {
- throw new IllegalStateException(
- "System services not available to Activities before onCreate()");
- }
-
- if (WINDOW_SERVICE.equals(name)) {
- return mWindowManager;
- } else if (SEARCH_SERVICE.equals(name)) {
- ensureSearchManager();
- return mSearchManager;
- }
- return super.getSystemService(name);
- }</span>
这里因为不满足if的判断条件,因此会返回调用父类的getSystemService方法。接下来继续跟踪到其父类中的getSystemService方法中查看,这里的Activity继承了ContextThemeWrapper这个类:
- <span style="font-size:18px;"> @Override public Object getSystemService(String name) {
- if (LAYOUT_INFLATER_SERVICE.equals(name)) {
- if (mInflater == null) {
- mInflater = LayoutInflater.from(mBase).cloneInContext(this);
- }
- return mInflater;
- }
- return mBase.getSystemService(name);
- }</span>
根据if的判断条件来看,这里还是不会满足判断条件。如果这里我们继续点击getSystemService()方法去跟踪的话我们会发现,我们来到了一个抽象类Context类中。该类中的getSystemService()方法也是一个抽象方法,那么到这里我们已经无法分析了吗?非也非也。如果细心的话我们会发现这里的getSystemService()方法前面有一个mBase对象,该对象是Context的,因为抽象类不可能有自己的实例化对象,因此根据多态性可以知道,这里的mBase肯定是其子类的对象,因此我们需要找到该子类。
(2)getSystemService()峰回路转
我们首先看看这个mBase的定义,直接跳转过去可以看到:
- <span style="font-size:18px;"> private Context mBase;
- ...
- public ContextThemeWrapper(Context base, int themeres) {
- super(base);
- mBase = base;
- mThemeResource = themeres;
- }
-
- @Override protected void attachBaseContext(Context newBase) {
- super.attachBaseContext(newBase);
- mBase = newBase;
- }</span>
这里只截取了其中部分,但我已经可以看到给mBase赋值的地方有两处,这里该怎么断定呢?按照常理我们先去跟踪ContextThemeWrapper构造方法的调用处,直接在Eclipse对该方法点击右键,选择Open Call Hierarchy,这样就会出现调用该方法的地方,这样一步步跟踪下去似乎越来越乱,因为调转点实在是太多了,因此先就此打住。我们回过头先查看这里的attachBaseContext方法,通过同样的方法(因为自己也是第一次分析,很多东西都不懂,只能自己摸着石头过河,高手请勿见笑)。我们直接跳转会来到Activity中的attach()方法中:
- <span style="font-size:18px;"> final void attach(Context context, ActivityThread aThread,
- Instrumentation instr, IBinder token, int ident,
- Application application, Intent intent, ActivityInfo info,
- CharSequence title, Activity parent, String id,
- Object lastNonConfigurationInstance,
- HashMap<String,Object> lastNonConfigurationChildInstances,
- Configuration config) {
- attachBaseContext(context);
这里截取了部分代码,只抓取了我们需要的部分,这里发现如果调用了attach()方法的话会传递一个Context的对象,那么我们继续跟踪,在Activity的performLaunchActivity方法中我们发现了attach()方法的调用处:
- <span style="font-size:18px;">activity.attach(appContext, this, getInstrumentation(), r.token,
- r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstance,
- r.lastNonConfigurationChildInstances, config);</span>
通过以上代码我们可以发现这里传递了一个appContext参数,跟踪此参数,可以看到:
- <span style="font-size:18px;">ContextImpl appContext = new ContextImpl();</span>
原来是ContextImpl的对象,原来应该传入的对象是Context的,这里传入的却是ContextImpl的对象,因此不用想我们也知道,ContextImpl肯定是Context的子类,跟踪过去一看,果不其然:
- <span style="font-size:18px;">class ContextImpl extends Context</span>
既然ContextImpl继承了Context类,并将自己的对象作为参数传递进去,那么前面的mBase对象就应该是ContextImpl的对象,因此调用的getSystemService()方法也应该在ContextImpl类中有覆写。直接搜索可以找到:
- <span style="font-size:18px;"> @Override
- public Object getSystemService(String name) {
- if (WINDOW_SERVICE.equals(name)) {
- return WindowManagerImpl.getDefault();
- } else if (LAYOUT_INFLATER_SERVICE.equals(name)) {
- synchronized (mSync) {
- LayoutInflater inflater = mLayoutInflater;
- if (inflater != null) {
- return inflater;
- }
- mLayoutInflater = inflater =
- PolicyManager.makeNewLayoutInflater(getOuterContext());
- return inflater;
- }
-
- } else if (SENSOR_SERVICE.equals(name)) {
- return getSensorManager();
- ......
- } else if (STORAGE_SERVICE.equals(name)) {
- return getStorageManager();
- } else if (USB_SERVICE.equals(name)) {
- return getUsbManager();
- } else if (VIBRATOR_SERVICE.equals(name)) {
- return getVibrator();
- }
- ......
- return null;
- }</span>
原来,我们梦里寻她千百度,蓦然回首,getSystemService()竟然藏在此处。因为我们在Memory.java中传递过来的是STORAGE_SERVICE,因此这里会执行getStorageManager()方法。
(3).继续探索getStorageManager()
继续跟踪getStorageManager()方法我们会看到:
- <span style="font-size:18px;"> private StorageManager getStorageManager() {
- synchronized (mSync) {
- if (mStorageManager == null) {
- try {
- mStorageManager = new StorageManager(mMainThread.getHandler().getLooper());
- } catch (RemoteException rex) {
- Log.e(TAG, "Failed to create StorageManager", rex);
- mStorageManager = null;
- }
- }
- }
- return mStorageManager;
- }</span>
通过该方法可以看到,返回的是一个StorageManager对象。但我们需要关注的是StorageManager(mMainThread.getHandler().getLooper())在这个方法中传递的参数是ActivityThread的handler中的looper。继续跟踪此方法就可以来到StorageManager的带参构造函数:
- <span style="font-size:18px;"> public StorageManager(Looper tgtLooper) throws RemoteException {
- mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
- if (mMountService == null) {
- Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
- return;
- }
- mTgtLooper = tgtLooper;
- mBinderListener = new MountServiceBinderListener();
- mMountService.registerListener(mBinderListener);
- }</span>
在该方法中,首先初始化了IMountService的对象,因为目前自己对Binder这一块还不是很熟悉,因此只能凭借自己的理解来分析。我们先去看看ServiceManager.getService("mount")方法:
- <span style="font-size:18px;"> public static IBinder getService(String name) {
- try {
- IBinder service = sCache.get(name);
- if (service != null) {
- return service;
- } else {
- return getIServiceManager().getService(name);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "error in getService", e);
- }
- return null;
- }</span>
“该方法将返回一个服务的引用,这个服务的名称就是我们传递进去的参数名称。如果这个服务不存在的话将返回null。”源码注释里面是这么说的,但我们就从代码中可以知道,实际上返回的是一个IBinder的对象。
接着调用回到StorageManager的构造函数中的IMountService.Stub.asInterface()方法:
- <span style="font-size:18px;"> public static IMountService asInterface(IBinder obj) {
- if (obj == null) {
- return null;
- }
- IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (iin != null && iin instanceof IMountService) {
- return (IMountService) iin;
- }
- return new IMountService.Stub.Proxy(obj);
- }</span>
“该方法将一个IBinder对象转换成一个IMountService接口,如果必要的话将通过代理来实现”,这里所说的代理指的是Proxy()方法。
这里又需要跳转到obj.queryLocaIInterface()方法中(PS:大家不要觉得枯燥,作为一个新手很多东西我也是第一次接触因此可能会走很多弯路,但过程还是很精彩的):
- <span style="font-size:18px;">public IInterface queryLocalInterface(String descriptor);</span>
很明显,这是一个在IBinder中的接口,因为Binder类实现了IBinder接口,因此我们直接去Binder类中查找该方法:
- <span style="font-size:18px;"> public IInterface queryLocalInterface(String descriptor) {
- if (mDescriptor.equals(descriptor)) {
- return mOwner;
- }
- return null;
- }</span>
以上方法的作用是,根据传入的描述符返回一个IInterface的mOwner对象,该mOwner对象在Binder类中有方法带参数传递如下:
- <span style="font-size:18px;"> public void attachInterface(IInterface owner, String descriptor) {
- mOwner = owner;
- mDescriptor = descriptor;
- }</span>
分析到这一步,我相信大家都头都晕了吧(不管你晕不晕,我反正是晕了,休息休息...)。
*************************************************分割线*********************************************************
休息好了,我们继续分析吧。
因为IInterface实际上也是一个接口,因此不可能实例化对象来传递,所以这里我们也不用想,只要找到其子类那么传递的对象就是其子类实例化的对象。但是要怎么找呢?我们这里的descriptor是“IMountService”因此我们可以在IMountService.java中寻找线索:
- <span style="font-size:18px;">public interface IMountService extends IInterface {
-
- public static abstract class Stub extends Binder implements IMountService {
- private static class Proxy implements IMountService {
- private IBinder mRemote;
-
- Proxy(IBinder remote) {
- mRemote = remote;
- }
- ...省略
-
-
- public Stub() {
- attachInterface(this, DESCRIPTOR);
- }
- ...省略</span>
这里可以看到IMountService继承了IInterface并且其内部类Stub还继承了Binder并实现了IMountService。(这里会涉及到了Android中的AIDL即Android Interface Defenition Language的知识,关于AIDL我会在博客中另起文章介绍并结合源码分析。
虽然通过以上代码的分析,但似乎已经是死胡同了,那么接下来该肿么办呢?
(4).切莫误入歧途
我们回到StorageManager的带参构造函数中(别忘了我们从这里开始分支的):
- <span style="font-size:18px;"> public StorageManager(Looper tgtLooper) throws RemoteException {
- mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
- if (mMountService == null) {
- Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
- return;
- }
- mTgtLooper = tgtLooper;
- mBinderListener = new MountServiceBinderListener();
- mMountService.registerListener(mBinderListener);
- }</span>
因为刚分析到第一句mMountService的实例化,通过以上的分析,我们将实例化对象锁定到了IMountService中的内部类Stub身上。因为分析不动了,那么我们先看看下一句关键代码吧:
- <span style="font-size:18px;">mMountService.registerListener(mBinderListener);
- </span>
这里调用的是IMountService中的方法,因为该类是一个接口,因此不可能去执行其中的registerListener()方法。前面我们在StorageManager中提到了,在IMountService的对象mMountService实例化的过程中,最终IMountService.Stub有关系,mMountService实际上是传递的
IMountService.Stub对象的引用。因此这里调用registerListener()方法的时候会去
IMountService.Stub中查找有没有registerListener()方法,在Stub中有如下代码:
- <span style="font-size:18px;"> public void registerListener(IMountServiceListener listener) throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeStrongBinder((listener != null ? listener.asBinder() : null));
- mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
- _reply.readException();
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- }</span>
估计很多朋友看到这里又晕了,“这是又是什么东西啊...“
大家请勿惊慌,因为在Android中涉及到很多设计思路,这里就是其中之一——Android中的IPC机制。这里我会展开去说这个东西的原理以及如何实现的,因为我们的目标并不是它,所以我们只需要了解其大概意思就行了。
在IMountService.Stub中的registerListener()方法中,实现了对数据的封装并发送。那么哪里会接收呢?
(5).神秘的接收者
那么到底是谁来接收呢?答案是:MountService.java
这里的接收需要有一点AIDL的知识,这一点我回在后面的博文中加入实例以及和源码的分析。
MountService继承了IMountService.Stub并覆写了其中的registerListener()方法,真正调用的也就是MountService中的registerListener()方法:
- <span style="font-size:18px;"> public void registerListener(IMountServiceListener listener) {
- synchronized (mListeners) {
- MountServiceBinderListener bl = new MountServiceBinderListener(listener);
- try {
- listener.asBinder().linkToDeath(bl, 0);
- mListeners.add(bl);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Failed to link to listener death");
- }
- }
- }</span>
首先是一个同步块,然后是MountServiceBinderListener对象的声明以及实例化,但这里的MountServiceBinderListener和StorageManager中的可不是同一个哦。现在我们已经从StorageManager跳转到了MountService,我们回过头再看看我们在StorageManager中的调用吧:
- <span style="font-size:18px;"> public StorageManager(Looper tgtLooper) throws RemoteException {
- mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
- if (mMountService == null) {
- Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
- return;
- }
- mTgtLooper = tgtLooper;
- mBinderListener = new MountServiceBinderListener();
- mMountService.registerListener(mBinderListener);
- }</span>
细心的朋友已经发现了吧!对,没错,我们传递的参数分明是MountServiceBinderListener的对象,而在MountService中的registerListener接收的参数却是IMountServiceListener类型的。这是怎么回事呢?我们可以在StorageManager中跟踪MountServiceBinderListener类,会发现:
- <span style="font-size:18px;">private class MountServiceBinderListener extends IMountServiceListener.Stub</span>
原来MountServiceBinderListener继承了IMountServiceListener.Stub,而Stub有实现了IMountServiceListener,因此根据多态性,参数为IMountServiceListener可以接收为MountServiceBinderListener的对象。
接下来我们回到MountService中的registerListener方法中,继续分析:
- <font xmlns="http://www.w3.org/1999/xhtml" size="4"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml">MountServiceBinderListener bl = new MountServiceBinderListener(listener);
- </font></font></font></font></font></font></font></font>
这里是MountService中的MountServiceBinderListener,直接跟踪过去会发现:
- <font xmlns="http://www.w3.org/1999/xhtml" size="4"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"> private final class MountServiceBinderListener implements IBinder.DeathRecipient {
- final IMountServiceListener mListener;
-
- MountServiceBinderListener(IMountServiceListener listener) {
- mListener = listener;
-
- }
- ...省略
- }</font></font></font></font></font></font></font>
在这个MountServiceBinderListener的构造函数中,相当于对IMountServiceListener对象进行了实例化,而实例化的对象就是StorageManager中传递过来的MountServiceBinderListener对象。
接着分析后面的代码:
- <font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"> listener.asBinder().linkToDeath(bl, 0);
- mListeners.add(bl);</font></font></font></font></font></font></font>
这两句代码的意思就是注册一个IBinder进程死亡标志,该方法用来接收进程退出的消息,然后执行然后执行mListeners.add(bl);将bl对象加入Arraylist中。
(6).胜利的曙光
经过前面那么长,注意啊,是那么长的分析,我们回到到原点:
- <font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"> if (mStorageManager == null) {
- mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
- mStorageManager.registerListener(mStorageListener);
- }</font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font>
我们刚刚分析完第一句,T_T,只是第一句啊。。。。。。
牢骚话不多说了,我们继续分析,接下来跳转到mStorageManager.registerListener()方法中:
- <font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"> public void registerListener(StorageEventListener listener) {
- if (listener == null) {
- return;
- }
-
- synchronized (mListeners) {
- mListeners.add(new ListenerDelegate(listener));
- }
- }</font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font>
继续跳转到ListenerDelegate()方法中:
- <font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"><font xmlns="http://www.w3.org/1999/xhtml"> ListenerDelegate(StorageEventListener listener) {
- mStorageEventListener = listener;
- mHandler = new Handler(mTgtLooper) {
- @Override
- public void handleMessage(Message msg) {
- StorageEvent e = (StorageEvent) msg.obj;
-
- if (msg.what == StorageEvent.EVENT_UMS_CONNECTION_CHANGED) {
- UmsConnectionChangedStorageEvent ev = (UmsConnectionChangedStorageEvent) e;
- mStorageEventListener.onUsbMassStorageConnectionChanged(ev.available);
- } else if (msg.what == StorageEvent.EVENT_STORAGE_STATE_CHANGED) {
- StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e;
- mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState);
- } else {
- Log.e(TAG, "Unsupported event " + msg.what);
- }
- }
- };
- }</font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font></font>
根据以上代码可以知道,这里是将StorageEventListener的对象传入ListenerDelegate的构造函数,并返回一个ListenerDelegate的对象,将该返回的对象加入ArrayList<ListenerDelegate>中。我们可以看到在ListenerDelegate类的构造函数中有一个handleMessage,用于接收handler传递的消息。这一步分析到这里也就完成了,相当于监听的初始化已经完成。
后文将继续分析,敬请关注...
Android 2.3 SD卡挂载流程浅析(七)
前面六篇文章:
《Android 2.3 SD卡挂载流程浅析(一)》
《Android 2.3 SD卡挂载流程浅析(二)》
《Android 2.3 SD卡挂载流程浅析(三)》
《Android 2.3 SD卡挂载流程浅析(四)》
《Android 2.3 SD卡挂载流程浅析(五)》
《Android 2.3 SD卡挂载流程浅析(六)》
前面六篇文章从底向上分析了SD卡挂载的流程,前面五篇文章主要是从底层往上,而第六篇文章则反其道而行之,从上层的设置界面开始往下分析,那么本篇文章会是一个引爆点,也就是说,这篇文章是从下往上和从上往下的一个交接处。因为过了段时间了,我们需要总结一下前面的文章,权当复习吧。哈哈,还是先祭处我们的老老图(T_T...)如下:
这是SD卡挂载的整个流程图,首先插入SD卡后,触发硬件中断,Kernel发出uevent;其次通过NetLinkManager初步处理接收到的uevent然后往上传递处理之后的事件信息;然后VolumeManager接着处理经过初步处理之后的时间信息并将信息进行更细致的划分和处理并传递给上层;最后上层接收到SD卡挂载后的信息并显示在界面上。
以上就是整个SD卡挂载的大致流程,下面是大致的流程图:
废话不多说,从这张图大家应该可以理解SD卡的挂载了吧。那么前面五篇博文从底向上,从Kernel发出的uevent一直到Vold,而第六篇博文则是从Setting开始一直到MountService,那么本文主要则描述底层信息最后是如何传递到上层的。如果大家遗忘了的话,还请看看前面的文章。
在第五篇博文中,我们分析到了MountService中的private void updatePublicVolumeState(String path, String state)方法,在该方法中我们当时只分析到了:
- <span style="font-size:18px;">synchronized (mListeners) {
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- 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);
- }
- }
- }</span>
在第六篇博文中,我们知道了SD卡挂载的消息是通过注册StorageManager的存储设备状态变更监听器来实现的,如下代码:
- <span style="font-size:18px;">if (mStorageManager == null) {
- mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
- mStorageManager.registerListener(mStorageListener);
- }</span>
那么也就是说,如果要通知到上层改变SD卡的挂载状态,底层的信息一定会触发上层注册的监听器,那么我就开始我们的旅程吧!
1.踏上征程
我们先继续查看MountService中的private void updatePublicVolumeState(String path, String state)方法中的同步块中的方法:
- <span style="font-size:18px;">MountServiceBinderListener bl = mListeners.get(i);</span>
该方法是从ArrayList<MoutServiceBinderListener>中获取之前存储的bl对象,如果大家忘记了我们存储bl的地方的话,可以回头看一下上一篇博文的讲解,实际上是在MountService.java中的registerListener()方法中通过mListeners.add(bl);方法添加的。
我们接着看:
- <span style="font-size:18px;">bl.mListener.onStorageStateChanged(path, oldState, state);</span>
我们在第五篇博文《Android 2.3 SD卡挂载流程浅析(五)》的最后,分析到了这句代码,但是只能卡在那里。但我们经过第六篇博文《Android 2.3 SD卡挂载流程浅析(六)》的讲解之后,我们应该能够顺利进行了。
这里我们跟踪mListener到MountServiceBinderListener这个类中:
- <span style="font-size:18px;">private final class MountServiceBinderListener implements IBinder.DeathRecipient {
- final IMountServiceListener mListener;
-
- MountServiceBinderListener(IMountServiceListener listener) {
- mListener = listener;
-
- }
- public void binderDied() {
- if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
- synchronized (mListeners) {
- mListeners.remove(this);
- mListener.asBinder().unlinkToDeath(this, 0);
- }
- }
- }</span>
这里我们看到mListener=listener;因为我们前面已讲过,IMountServiceListener是一个接口,不可能实例化对象,因此传递的参数可以肯定是实现了它的类的对象。那么我们跟踪这里的MountServieBinderListener(IMountServiceListener listener)看传递进来的参数是什么:
- <span style="font-size:18px;">public void registerListener(IMountServiceListener listener) {
- synchronized (mListeners) {
- MountServiceBinderListener bl = new MountServiceBinderListener(listener);
- try {
- listener.asBinder().linkToDeath(bl, 0);
- mListeners.add(bl);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Failed to link to listener death");
- }
- }
- }</span>
在MountService中的registerListener()方法中我们找到了它的影子,但是一看代码就知道,这里也只是一个中转站,但是看过前面文章的朋友因该想起来了吧。对,没错,这就是我前面提到过经过AIDL之后,最终调用的registerListener()方法。
那么这里MountService中的MountServiceBinderListener类中的mListener对象,是从StorageManager中的MountServiceBinderListener转换来的,转换过程前面已经有阐述这里不多讲。也就是说这里的mListener可以当作StorageManager中的MountServiceBinderListener的对象,那么这里bl.mListener.onStorageStateChanged(path, oldState, state);的方法实际上也是StorageManager中的MountServiceBinderListener的方法。所以我们找到StorageManager中的MountServiceBinderListener类:
- <span style="font-size:18px;">private class MountServiceBinderListener extends IMountServiceListener.Stub {
- public void onUsbMassStorageConnectionChanged(boolean available) {
- final int size = mListeners.size();
- for (int i = 0; i < size; i++) {
- mListeners.get(i).sendShareAvailabilityChanged(available);
- }
- }
-
- public void onStorageStateChanged(String path, String oldState, String newState) {
- final int size = mListeners.size();
- for (int i = 0; i < size; i++) {
- mListeners.get(i).sendStorageStateChanged(path, oldState, newState);
- }
- }
- }</span>
果然其中有我们寻找的onStrageStateChanged()方法,那么我们就继续分析吧。
2.披荆斩棘,直捣黄龙
- <span style="font-size:18px;">public void onStorageStateChanged(String path, String oldState, String newState) {
- final int size = mListeners.size();
- for (int i = 0; i < size; i++) {
- mListeners.get(i).sendStorageStateChanged(path, oldState, newState);
- }
- }</span>
首先看到mListeners.get(i)这个方法我们直接就回联想到ArrayList,那么这个是这个ArrayList中存放到底是什么对象呢?如果大家对上一篇文章有影响的话应该不会陌生,这里的存放的是ListenerDelegate的对象。好了我们继续跳转到sendStorageStateChanged()方法中去:
- <span style="font-size:18px;">void sendStorageStateChanged(String path, String oldState, String newState) {
- StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState);
- mHandler.sendMessage(e.getMessage());
- }</span>
那么继续跟踪StroageStateChangedStorageEvent();这个构造方法:
- <span style="font-size:18px;">private class StorageStateChangedStorageEvent extends StorageEvent {
- public String path;
- public String oldState;
- public String newState;
-
- public StorageStateChangedStorageEvent(String p, String oldS, String newS) {
- super(EVENT_STORAGE_STATE_CHANGED);
- path = p;
- oldState = oldS;
- newState = newS;
- }
- }</span>
在这个类中的构造方法中,我们可以看到它通过super掉了父类的构造方法,我们继续跟踪该super方法:
- <span style="font-size:18px;">private class StorageEvent {
- static final int EVENT_UMS_CONNECTION_CHANGED = 1;
- static final int EVENT_STORAGE_STATE_CHANGED = 2;
- static final int EVENT_OBB_STATE_CHANGED = 3;
-
- private Message mMessage;
-
- public StorageEvent(int what) {
- mMessage = Message.obtain();
- mMessage.what = what;
- mMessage.obj = this;
- }
-
- public Message getMessage() {
- return mMessage;
- }
- }</span>
哈哈,原来是封装了一个Message,这里的what值是2。那么我们回到:
- <span style="font-size:18px;">void sendStorageStateChanged(String path, String oldState, String newState) {
- StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState);
- mHandler.sendMessage(e.getMessage());
- }</span>
我们接着看mHandler.sendMessage(e.getMessage());方法,通过前面的代码,我们可以看到,e.getMessage()返回的是一个Message对象,正好这里通过mHandler发送给出去。
可能这里有朋友要问了,我怎么知道发送给谁呢?谁来接收呢?别急,我们回忆一下,在上一篇博文中,我们通过从上往下分析,最后我们分析到ListenerDelegate这个类中,发现了其中的handleMessage()方法,这里我把这个类贴出来:
- <span style="font-size:18px;">private class ListenerDelegate {
- final StorageEventListener mStorageEventListener;
- private final Handler mHandler;
-
- ListenerDelegate(StorageEventListener listener) {
- mStorageEventListener = listener;
- mHandler = new Handler(mTgtLooper) {
- @Override
- public void handleMessage(Message msg) {
- StorageEvent e = (StorageEvent) msg.obj;
-
- if (msg.what == StorageEvent.EVENT_UMS_CONNECTION_CHANGED) {
- UmsConnectionChangedStorageEvent ev = (UmsConnectionChangedStorageEvent) e;
- mStorageEventListener.onUsbMassStorageConnectionChanged(ev.available);
- } else if (msg.what == StorageEvent.EVENT_STORAGE_STATE_CHANGED) {
- StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e;
- mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState);
- } else {
- Log.e(TAG, "Unsupported event " + msg.what);
- }
- }
- };
- }
-
- StorageEventListener getListener() {
- return mStorageEventListener;
- }
-
- void sendShareAvailabilityChanged(boolean available) {
- UmsConnectionChangedStorageEvent e = new UmsConnectionChangedStorageEvent(available);
- mHandler.sendMessage(e.getMessage());
- }
-
- void sendStorageStateChanged(String path, String oldState, String newState) {
- StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState);
- mHandler.sendMessage(e.getMessage());
- }
- }</span>
哈哈,handleMessage()远在天边近在眼前啊。
那我们赶紧继续吧。我们在handleMessage()中找到如下方法:
- <span style="font-size:18px;">StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e;
- mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState);</span>
这里的mStorageEventListener,是通过ListenerDelegate(StorageEventListener listener)的构造方法传递的StorageEventListener对象,因此这里也就是去调用StorageEventListener中的onStorageStateChanged()方法,传递的参数:ev.path,ev.oldState,ev.newState 这些参数都是从MountService那里传送过来的。
3.胜利在望
现在我们回到Memory.java中,这里为什么要回到Memory.java中呢?如果我们直接去StorageEventListener这个类中会发现实际山该类只是一个接口,其中只有空方法。我们回到Memory.java中可以看到:
- <span style="font-size:18px;">StorageEventListener mStorageListener = new StorageEventListener() {
-
- @Override
- public void onStorageStateChanged(String path, String oldState, String newState) {
- Log.i(TAG, "Received storage state changed notification that " +
- path + " changed state from " + oldState +
- " to " + newState);
- updateMemoryStatus();
- }
- };</span>
在Memory.java中,我们可以看到通过匿名内部类的方法,实现了StorageEventListener中的onStorageStateChanged()方法,因此,最终的调用这里的onStorageStateChanged()方法。这样SD卡挂载的消息我们可以通过抓取这里的log来看到。
接下来分析updateMemoryStatus():
- <span style="font-size:18px;">private void updateMemoryStatus() {
- String status = Environment.getExternalStorageState();
- String readOnly = "";
- if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
- status = Environment.MEDIA_MOUNTED;
- readOnly = mRes.getString(R.string.read_only);
- }
-
-
- if (status.equals(Environment.MEDIA_MOUNTED)) {
- if (!Environment.isExternalStorageRemovable()) {
-
-
- if (mSdMountToggleAdded) {
- mSdMountPreferenceGroup.removePreference(mSdMountToggle);
- mSdMountToggleAdded = false;
- }
- }
- try {
- File path = Environment.getExternalStorageDirectory();
- StatFs stat = new StatFs(path.getPath());
-
- long blockSize = stat.getBlockSize();
- long totalBlocks = stat.getBlockCount();
- long availableBlocks = stat.getAvailableBlocks();
-
-
- mSdSize.setSummary(formatSize(totalBlocks * blockSize));
- mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly);
-
- mSdMountToggle.setEnabled(true);
- mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
- mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
-
- } catch (IllegalArgumentException e) {
-
-
- status = Environment.MEDIA_REMOVED;
- }
-
- } else {
- mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
- mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
-
-
- if (!Environment.isExternalStorageRemovable()) {
- if (status.equals(Environment.MEDIA_UNMOUNTED)) {
- if (!mSdMountToggleAdded) {
- mSdMountPreferenceGroup.addPreference(mSdMountToggle);
- mSdMountToggleAdded = true;
- }
- }
- }
-
- if (status.equals(Environment.MEDIA_UNMOUNTED) ||
- status.equals(Environment.MEDIA_NOFS) ||
- status.equals(Environment.MEDIA_UNMOUNTABLE) ) {
- mSdMountToggle.setEnabled(true);
- mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
- mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
- } else {
- mSdMountToggle.setEnabled(false);
- mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
- mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
- }
- }
-
- File path = Environment.getDataDirectory();
- StatFs stat = new StatFs(path.getPath());
- long blockSize = stat.getBlockSize();
- long availableBlocks = stat.getAvailableBlocks();
- findPreference("memory_internal_avail").setSummary(formatSize(availableBlocks * blockSize));
- }</span>
分析到这里我们的目的已经实现了。如果忘记了我们的目的那我这里重申一次:从SD卡插入一直到SD卡挂载信息显示到系统设置界面的过程分析。
总结:先说一下分析SD卡挂载流程这件事情的起因。因为工作原因,接触到了Android,但是在检测SD卡挂载着一块始终有些问题,多方搜索后无果,那我就自己硬着头皮啃吧。一开始找起点,怎么找呢?发现系统设置里面有SD卡挂载的相关信息,那么就开始从Android系统设置的源码开始找,逐渐的发现,越深入越复杂。对于我这个新手来说很多东西不懂,经过不断的搜索,以及查询相关资料,最终奋战了2个星期将SD卡的挂载流程大致分析清楚了。因为自己也是新手,所以很多东西分析得不一定都对,所以还望读者指出错误。
经过本次源码分析,学习到了很多知识,这些知识其实在书本上也有提到,但需要真正理解其意思的话,必须要通过实践。
附上前六篇博文地址:
《Android 2.3 SD卡挂载流程浅析(一)》
《Android 2.3 SD卡挂载流程浅析(二)》
《Android 2.3 SD卡挂载流程浅析(三)》
《Android 2.3 SD卡挂载流程浅析(四)》
《Android 2.3 SD卡挂载流程浅析(五)》
《Android 2.3 SD卡挂载流程浅析(六)》
最后附上参考文章及博客:
http://blog.163.com/lzh_327/blog/static/7219480201122103947556/
http://blog.csdn.net/free2o/article/details/4174275
http://blog.csdn.net/sustzombie/article/details/6120903
http://hi.baidu.com/nnqidhbd/blog/item/15df0151cb2c0d03377abe72.html
http://www.eoeandroid.com/thread-32766-1-1.html
android SD卡学习1
是不是你在使用android 模拟器的sd卡 上的应用时出现过如下的错误呢。。希望这篇文章能够对你有所帮助
ailed to push selection: Invalid argument
Failed to push the item(s).
待我一一到来:
篇章一:sd卡的创建:
有两种方式来创建sd卡:
在创建Android模拟器之初,就需要来对模拟器的sd卡进行创建 (ps: sd卡?你知道sd卡是什么吗?是干吗的?看最后面的介绍吧,不要打岔,接着往下走。。)
方法一:
如下图可以自定义一个大小,由系统来完成该模拟器 虚拟sd开的创建
创建完成后,启动该模拟器,打开eclipse的DDMS——>File-Explorer 看到如下的目录结构
这就是创建的sd 卡文件夹了
方法二:比较麻烦一点,但是更好理解 而且我已经试验过了 没有问题。
通过Android的Tools 来创建sd卡并导入进来
1.打开dos ,windows+R 键打开。转换到你的sdk安装包的tools目录下
如我的目录如下:D:/Android/android sdk/tools
2.创建sd卡 。在命令行中输入:mksdcard 1024M D:/android/sdcard.img
【这个1024M就是我们要创建的SD卡的大小,大家还是根据自己的需要来输入,最后面的那个D:/android/sdcard.img就是我们所设置的SD存放路径,这里也可以根据我们自己的需要改变路径的,但要注意路径中不能有空格出现】
注:输入好SD卡的大小和路径命令后。我们要稍等一下,因为电脑正在创建我们的SD卡。大约30秒后那个黑框又弹出了一行命令,这说明我们所要的SD卡已经创建成功了!
3.在eclipse里面创建模拟器,并以该sd卡来创建
4.创建完成之后,启动该模拟器。可以在Android SDK and AVD Manager 里面指定模拟器并启动
5.同样打开eclipse的DDMS——>File-Explorer 看到如下的目录结构
证明已经安装SD卡 成功了。。恭喜。。恭喜。。呵呵O(∩_∩)O~
篇章二:向sd卡里面上传文件
这里就会出现开头所说的异常问题了。仔细研究哦。
右边两个小手机按钮就是传输文件的按钮了,用它来实现文件的传入和传出了,向做的表示把手机里的文件导出,向右的表示把文件导入到手机里面。这事就有可能会爆出异常了。
1》ailed to push selection: Invalid argument
出现此异常,多半是文件所在的目录里面有中文或者文件的名字是中文导致的。建议修改一下试试。
还有就是观察自己的操作权限是不是不够,如果是:d---rwxr-x 那就是可以的。
如果,sd卡是在eclipse里面系统自动创建的,试着重启一下IDE。
2》Failed to push the item(s).
出现这个异常是因为,传输超时造成的。可以修改。
打开windows——Preference——android——DDMS
把 ADB connection time out 项的超时时间改大点,至于多大,那就随便你了。。
基本就解决了文件的上传问题了。这是我做的音乐播放器初始的模型,出来show一下
备注:文件最好是英文的,中文的文件名称,在Android 里面支持还有问题。我还没怎么搞清楚,就不再纠结了,先用英文的吧。。再继续研究了。。。。
ps:关于虚拟的sd卡的问题,应该好理解吧。以前的手机不都有一个很小的内存卡嘛,所以智能机也继承了这个特点。有的是在手机里面内置的,不可拆卸的。有的还是以前的那样支持拆卸的那种了。
还有就是,在别的机子上建立的SD卡镜像文件,传到另一台机子上是不可用的。因为这个镜像文件,是以本地机器的物理地址为依据,划分出了指定大小的硬盘空间作为模拟器的存储空间的。所以物理地址在不同的电脑上有可能会出现物理地址的冲突。是不可用的。你地,明白?
关于异常的处理办法,欢迎大家的补充哦。。
哎,不过说了也白说,百度空间里的人们太不专业,取完经,就跑了。。。没几个留言的。。。正在酝酿博客搬家的事呢。。。
还是得去专业的技术网站写博客了。。。。技术不交流,就荒废了啊。。。
伤心了,这么复杂的说,找了一个下午,居然以为可以直接浏览将本地文件夹作为SD卡,我怎么这么无知啊,555555
android SD卡学习2
try
{
// 可能产生异常的代码块(一定执行,直到出错,或者不出错,完全执行到本段代码结束)
}
catch() // 异常之一
{ //捕捉到错误要做些什么的代码块
}
catch() // 异常之二
{ //捕捉到错误要做些什么的代码块
}
catch // 其他异常
{ //捕捉到错误要做些什么的代码块
}
finally
{
// 不管产不产生异常都要执行的代码块
}
try
catch
finally
1、将预见可能引发异常的代码包含在try语句块中。
2、如果发生了异常,则转入catch的执行。catch有几种写法:
catch
这将捕获任何发生的异常。
catch(Exception e)
这将捕获任何发生的异常。另外,还提供e参数,你可以在处理异常时使用e参数来获得有关异常的信息。
catch(Exception的派生类 e)
这将捕获派生类定义的异常,例如,我想捕获一个无效操作的异常,可以如下写:
catch(InvalidOperationException e)
{
....
}
这样,如果try语句块中抛出的异常是InvalidOperationException,将转入该处执行,其他异常不处理。
catch可以有多个,也可以没有,每个catch可以处理一个特定的异常。.net按照你catch的顺序查找异常处理块,如果找到,则进行处理,如果找不到,则向上一层次抛出。如果没有上一层次,则向用户抛出,此时,如果你在调试,程序将中断运行,如果是部署的程序,将会中止。
如果没有catch块,异常总是向上层(如果有)抛出,或者中断程序运行。
3、finally
finally可以没有,也可以只有一个。无论有没有发生异常,它总会在这个异常处理结构的最后运行。即使你在try块内用return返回了,在返回前,finally总是要执行,这以便让你有机会能够在异常处理最后做一些清理工作。如关闭数据库连接等等。
注意:如果没有catch语句块,那么finally块就是必须的。
如果你不希望在这里处理异常,而当异常发生时提交到上层处理,但在这个地方无论发生异常,都要必须要执行一些操作,就可以使用try finally,
很典型的应用就是进行数据库操作:
用下面这个原语来说明:
try
{
DataConnection.Open();
DataCommand.ExecuteReader();
...
return;
}
finally
{
DataConnection.Close();
}
无论是否抛出异常,也无论从什么地方return返回,finally语句块总是会执行,这样你有机会调用Close来关闭数据库连接(即使未打开或打开失败,关闭操作永远是可以执行的),以便于释放已经产生的连接,释放资源。
顺便说明,return是可以放在try语句块中的。但不管在什么时机返回,在返回前,finally将会执行。
小结
try { //执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容 }
catch { //除非try里面执行代码发生了异常,否则这里的代码不会执行 }
finally { //不管什么情况都会执行,包括try catch 里面用了return ,可以理解为只要执行了try或者catch,就一定会执行 finally }
android SD卡学习3-读写文件
中所周知android有一套自己的安全模型, 具体可参见android开发文档 。。当应用程序(.apk)在安装时就会分配一个userid,当该应用要去访问其他资源比如文件的时候,就需要userid匹配。默认情况下 ,任何应用创建的文件,数据库, sharedpreferences都应该是私有的(位于/data/data/your_project/files/),其余程序无法访问。除非在创建时指明是MODE_WORLD_READABLE 或者 MODE_WORLD_WRITEABLE,只要这样其余程序才能正确访问。
因为有这种安全上保障,进程打开文件时android要求检查进程的user id。所以不能直接用java的api来打开,因为java的io函数没有提这个机制 。
//无法用java的api直接打开程序私有的数据 ,默认路径为/data/data/your_project/files/
FileReader file = new FileReader("android.txt");
这里特别强调私有数据!言外之意是如果某个文件或者数据不是程序私有的,既访问它时无须经过android的权限检查,那么还是可以用java的io api来直接访问的。所谓的非私有数据是只放在sdcard上的文件或者数据,
//可以用java的io api来直接打开sdcard上文件。
FileReader file = new FileReader("/sdcard/android.txt");
如果要打开程序自己私有的文件和数据,那必须使用Activity提供openFileOutput和openFileInput方法。
//创建程序私有的文件,由于权限方面的要求,必须使用activity提供的方法
FileOutputStream os = this.openFileOutput("android.txt", MODE_PRIVATE);
OutputStreamWriter outWriter = new OutputStreamWriter (os);
//读取程序私有的文件,由于权限方面的要求,必须使用activity提供的方法
FileInputStream os =this.openFileInput("android.txt");
InputStreamReader inReader = new InputStreamReader(os);
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lijiecong/archive/2009/11/26/4881362.aspx
在介绍如何在Android平台下进行文件的读取之前,有必要了解Android平台下的数据存储规则。在其他的操作系统如Windows 平台下,应用程序可以自由地或者在特定的访问权限基础上访问或修改其他应用程序名下的文件等资源,而在Android平台下,一个应用程序中所有的数据都是私有的。
当应用程序被安装到系统中后,其所在的包会有一个文件夹用于存放自己的数据,只有这个应用程序才有对这个文件夹的写入权限,这个私有的文件夹位于Android系统的/data/data/<应用程序包名>目录下,其他的应用程序都无法再这个文件夹中写入数据。除了存放私有的数据文件夹外,应用程序也具有SD卡的写入权限。
使用文件I/O 方法可以直接往手机中存储数据,默认情况下这些文件不可以被其他的应用程序访问。Android平台支持 java平台下的 文件I/O操作, 主要使用FileInputStream 和 FileOutputStream 这两个类来实现文件的存储与读取。获取这两个类对象的方式有两种。
一:第一种方式就是像Java平台下的实现方式一样通过构造器直接创建,如果需要向打开的文件末尾写入数据,可以通过使用构造器FileOutputStream(File file, boolean append)将 append设置为true来实现。不过需要注意的是采用这种方式获得FileOutputStream 对象时如果文件不存在或不可写入时,会抛出 FileNotFoundException 异常。
二:第二种获取 FileInputStream 和 FileOutputStream 对象的方式是调用 Context.openFileInput 和 Context.openFileOutput两个方法来创建。除了这两个方法外,Context对象还提供了其他几个用于对文件操作的方法,如下所示
Context对象中文操作的API及说明
方法名 说明
openFileInput(String filename) 打开应用程序私有目录下的的指定私有文件以读入数据,返回一个FileInputStream 对象
openFileOutput 打开应用程序私有目录下的的指定私有文件以写入数据,返回一个FileOutputStream 对象,如果文件不存在就创建这个文件。
fileList() 搜索应用程序私有文件夹下的私有文件,返回所有文件名的String数组
deleteFile(String fileName) 删除指定文件名的文件,成功返回true,失败返回false
在使用openFileOutput方法打开文件以写入数据时,需要指定打开模式。默认为零,即MODE_PRIVATE。不同的模式对应的的含义如下:
openFileOutput方法打开文件时的模式
常量 含义
MODE_PRIVATE 默认模式,文件只可以被调用该方法的应用程序访问
MODE_APPEND 如果文件已存在就向该文件的末尾继续写入数据,而不是覆盖原来的数据。
MODE_WORLD_READABLE 赋予所有的应用程序对该文件读的权限。
MODE_WORLD_WRITEABLE 赋予所有的应用程序对该文件写的权限。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/hustpzb/archive/2011/03/04/6223328.aspx
SD相关内存获取路径等
1、讲述 Environment 类。
2、讲述 StatFs 类。
3、完整例子读取 SDCard 内存
1、讲述 Environment 类
Environment 是一个提供访问环境变量的类。
Environment 包含常量:
MEDIA_BAD_REMOVAL
解释:返回getExternalStorageState() ,表明SDCard 被卸载前己被移除
MEDIA_CHECKING
解释:返回getExternalStorageState() ,表明对象正在磁盘检查。
MEDIA_MOUNTED
解释:返回getExternalStorageState() ,表明对象是否存在并具有读/写权限
MEDIA_MOUNTED_READ_ONLY
解释:返回getExternalStorageState() ,表明对象权限为只读
MEDIA_NOFS
解释:返回getExternalStorageState() ,表明对象为空白或正在使用不受支持的文件系统。
MEDIA_REMOVED
解释:返回getExternalStorageState() ,如果不存在 SDCard 返回
MEDIA_SHARED
解释:返回getExternalStorageState() ,如果 SDCard 未安装 ,并通过 USB 大容量存储共享 返回
MEDIA_UNMOUNTABLE
解释:返回getExternalStorageState() ,返回 SDCard 不可被安装 如果 SDCard 是存在但不可以被安装
MEDIA_UNMOUNTED
解释:返回getExternalStorageState() ,返回 SDCard 已卸掉如果 SDCard 是存在但是没有被安装
Environment 常用方法:
方法:getDataDirectory()
解释:返回 File ,获取 Android 数据目录。
方法:getDownloadCacheDirectory()
解释:返回 File ,获取 Android 下载/缓存内容目录。
方法:getExternalStorageDirectory()
解释:返回 File ,获取外部存储目录即 SDCard
方法:getExternalStoragePublicDirectory(String type)
解释:返回 File ,取一个高端的公用的外部存储器目录来摆放某些类型的文件
方法:getExternalStorageState()
解释:返回 File ,获取外部存储设备的当前状态
方法:getRootDirectory()
解释:返回 File ,获取 Android 的根目录
2、讲述 StatFs 类
StatFs 一个模拟linux的df命令的一个类,获得SD卡和手机内存的使用情况
StatFs 常用方法:
getAvailableBlocks()
解释:返回 Int ,获取当前可用的存储空间
getBlockCount()
解释:返回 Int ,获取该区域可用的文件系统数
getBlockSize()
解释:返回 Int ,大小,以字节为单位,一个文件系统
getFreeBlocks()
解释:返回 Int ,该块区域剩余的空间
restat(String path)
解释:执行一个由该对象所引用的文件系统
3、完整例子读取 SDCard 内存
存储卡在 Android 手机上是可以随时插拔的,每次的动作都对引起操作系统进行 ACTION_BROADCAST,本例子将使用上面学到的方法,计算出 SDCard 的剩余容量和总容量。代码如下:
04 |
import java.text.DecimalFormat; |
06 |
import android.R.integer; |
07 |
import android.app.Activity; |
08 |
import android.os.Bundle; |
09 |
import android.os.Environment; |
10 |
import android.os.StatFs; |
11 |
import android.view.View; |
12 |
import android.view.View.OnClickListener; |
13 |
import android.widget.Button; |
14 |
import android.widget.ProgressBar; |
15 |
import android.widget.TextView; |
16 |
import android.widget.Toast; |
18 |
public class getStorageActivity extends Activity { |
19 |
private Button myButton; |
20 |
/** Called when the activity is first created. */ |
22 |
public void onCreate(Bundle savedInstanceState) { |
23 |
super .onCreate(savedInstanceState); |
24 |
setContentView(R.layout.main); |
26 |
viewHolder.myButton.setOnClickListener( new OnClickListener() { |
29 |
public void onClick(View arg0) { |
38 |
viewHolder.myButton=(Button)findViewById(R.id.Button01); |
39 |
viewHolder.myBar=(ProgressBar)findViewById(R.id.myProgressBar); |
40 |
viewHolder.myTextView=(TextView)findViewById(R.id.myTextView); |
46 |
viewHolder.myTextView.setText( "" ); |
47 |
viewHolder.myBar.setProgress( 0 ); |
49 |
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ |
50 |
File path =Environment.getExternalStorageDirectory(); |
52 |
StatFs statfs= new StatFs(path.getPath()); |
54 |
long blocSize=statfs.getBlockSize(); |
56 |
long totalBlocks=statfs.getBlockCount(); |
58 |
long availaBlock=statfs.getAvailableBlocks(); |
60 |
String[] total=filesize(totalBlocks*blocSize); |
61 |
String[] availale=filesize(availaBlock*blocSize); |
63 |
int maxValue=Integer.parseInt(availale[ 0 ]) |
64 |
*viewHolder.myBar.getMax()/Integer.parseInt(total[ 0 ]); |
65 |
viewHolder.myBar.setProgress(maxValue); |
66 |
String Text= "总共:" +total[ 0 ]+total[ 1 ]+ "\n" |
67 |
+ "可用:" +availale[ 0 ]+availale[ 1 ]; |
68 |
viewHolder.myTextView.setText(Text); |
70 |
} else if (Environment.getExternalStorageState().equals(Environment.MEDIA_REMOVED)){ |
71 |
Toast.makeText(getStorageActivity. this , "没有sdCard" , 1000 ).show(); |
76 |
String[] filesize( long size){ |
86 |
DecimalFormat formatter= new DecimalFormat(); |
87 |
formatter.setGroupingSize( 3 ); |
88 |
String result[] = new String[ 2 ]; |
89 |
result[ 0 ]=formatter.format(size); |
android4.0 中关于内外置sd卡的获取及读写权限问题
1.在android4.0源码出来以后,关于sd卡问题似乎没有解决好,起码上层api中没有体现到位。其实在framework层中有相应的类去获得内外置sd卡信息,是否可读写的权限。
在2.x的版本中,在manifest中配置的权限android.permission.WRITE_EXTERNAL_STORAGE确实是用来使得sd卡获得写的权限。而在4.0开发的源码当中,由于有了内外置sd卡的区分,android.permission.WRITE_EXTERNAL_STORAGE的权限用来设置了内置sd卡的写权限,如果在manifest中只是配置了这个权限,那个应用只能在内置sd卡中进行写操作,还是无法在外置sd卡中进行写操作。需要写外置sd卡的话,需要配置另一个权限android.permission.WRITE_MEDIA_STORAGE,这样就可以在外置sd卡中进行写入操作了。
这两个权限都被定义在 android源码目录\frameworks\base\data\etc\platform.xml中:
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
-
<group gid="sdcard_rw" />
-
</permission>
-
-
<permission name="android.permission.WRITE_MEDIA_STORAGE" >
-
<group gid="media_rw" />
-
</permission>
使用到的类主要是framework/base/core/java/android/os/storage/StorageVolume.java及相同目录下的StorageManager.java文件。
StorageManager类可以获得所有的存储媒体列表,及StorageVolume类型的数组,从而可以获得对应存储是否可读写一类的信息。
希望可以帮助遇到相同问题的童鞋。
===============================================================================================================================
新入手Android系统的朋友可能都会遇到给SD卡分区的难题,为什么要分区?分区有哪些?分区的方法是什么?这些问题,其实非常简单。
1、分区的目的和SD卡的三个主要分区
SD卡分区的主要目的是为了将应用程序安装到SD卡,以解决因机器自身的内存不足造成的不便,而且能更好地提升系统的稳定性和运行速度。也就是通常所说的APP2SD。
APP2SD的工作原理:
以4G的SD卡为例,我们可以将4G的卡分成3个分区,FAP32分区作为你正常存储音乐、图片的普通存储分区;Swap分区是系统缓存,越大对系统运行速度的提升越大,但一般不会超过96MB(因为Swap分区对SD卡的寿命有影响);还有一个Ext4分区,这个分区的作用就是将你安装的软件安装到SD卡上,不占用手机内存,更好地提升系统稳定性和运行速度。
以Magic为例,目前的民间自制ROM基本都要求SD卡有三个分区才能保证刷ROM成功并工作正常。
这三个分区是:
(1)Fat32主分区:普通的SD卡空间,用于文件存储等;
(2)Ext4主分区:用于APP2SD,即将应用程序安装到SD卡而非手机内存,ROM自动完成;
(3)Swap主分区:用于解决系统内存不足的问题,系统自动调用此分区。
2、有关Linux-Swap分区、SD卡、分区与SD卡损耗的一些信息
micro-SD的储存单元分为两类:SLC(Single Layer Cell,单层单元)和MLC(Multi-Level Cell,多层单元)。SLC闪存的优点是复写次数高达100000次,比MLC闪存高10倍。此外,为了保证MLC的寿命,控制芯片都校验和智能磨损平衡技术算法,使得每个存储单元的写入次数可以平均分摊,达到100万小时故障间隔时间(MTBF)。目前的TF卡无写入次数平均分摊管理,导致写入次数为理论上的1万次。那么建立SWAP分区意味着系统会对这一组单元集中作频繁写入操作,是否会导致TF卡的迅速损坏呢?
我们可以通过修改系统中的/dev/sys/vm/swappiness的值来告诉系统你想交换得多勤快。在Linux里面,swappiness的值的大小对如何使用Swap分区是有着直接联系的。swappiness=0的时候表示最大限度使用物理内存,然后才是Swap空间;swappiness=100的时候表示积极的使用Swap分区,并且把内存上的数据及时的搬运到Swap空间里面。两个极端,对于Ubuntu的默认设置,这个值等于60。所以我们可修改swappiness来控制系统对Swap分区的写入频率。
Linux的Swap分区的作用可简单描述为:当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间被临时保存到Swap空间中,等到那些程序要运行时,再从Swap中恢复保存的数据到内存中。这样,系统总是在物理内存不够时,才进行Swap交换。
关键词就是虚拟内存。
3、SD卡分区的次序与分区大小
SD卡的三个分区依次为:FAT32主分区、Ext4主分区、Swap主分区。
举例的分区大小:Linux-Swap:96M;Ext4:500M;FAT32:剩下所有SD卡空间。一定要注意,先分区FAT32,再分区Ext4,最后分区Swap。问题由此出现:怎样最先确定FAT32分区的大小?很简单,从系统里看看你SD卡空间,减去500MB的Ext4空间,再减去96MB的Linux-Swap空间就好。例如8G卡实际容量若为7.59GB,那么FAT32分区大小为7590-500-96=6994MB分区大小即可。
还有一个细节,使用分区软件进行分区操作的时候,分区大小可能不会正好是上述数值,多多少少,或者出现小数都是没关系的。
=============================================================================================================================