另外文章 http://blog.csdn.net/Fybon/article/details/25904215
Android 外部存储权限分析 http://blog.csdn.net/zjbpku/article/details/25161131
1. 比如写代码: /sdcard/ , /storage/emulated/0 , /mount/sdcard/ , /mnt/sdcard/, /storage/sdcard/
(1)有什么区别
/sdcard是 /mnt/sdcard的符号链,指向 ---> /mnt/sdcard,(/sdcard/: this is a symlink to...)
/storage/sdcard/ 是sdcard的分区
/mnt/sdcard (Android < 4.0)
/storage/sdcard0 (Android 4.0+)
重要:sdcard/、storage/sdcard0、mnt/sdcard等等,会让人很难以理解。这其中的很多路径都是指向同一个路径,有点指针的味道。
其中的sdcard/、mnt/sdcard、storage/sdcard0、storage/emulated/0、storage/emulated/legacy都是同一个路径的不同”指针“,说到底都是内部存储。
真正的外部存储卡是mnt/sdcard2和storage/sdcard1,
(2)安卓系统是从Linux而衍生出来的,而mnt是unix/Linux传统系统下挂载外部设备的专用目录,Linux默认挂载外部设备都会挂到这个目录下面去,
如将sd卡挂载上去后,会生成一个/mnt/sdcard 目录。
/sdcard 目录,这是一个软链接,一个映射,它相当于windows的文件夹的快捷方式,链接到/mnt/sdcard 目录,所以这个目录的内容就是sdcard的内容。
产生了一个类似于快捷方式的内容放置到根目录上供大家查看
因此,不要在程序中直接使用sdcard文件夹,因为他不再根目录上,除非自己建立,要进行文件的操作,还是需要在mnt/sdcard 这个路径下获取
mount 的缩写是mnt ,在Linux系统中,这是一个命令,意思就是挂载一个文件系统,在这个目录下的文件就可以访问了,
在Windows下是对磁盘进行分区,而在Linux下是以文件夹进行存储的,所以当Linux下需要访问Windows系统的磁盘时,就是用这个命令,
将Windows下的一个磁盘分区挂载到Linux系统上,然后Linux系统就可以进行访问这个磁盘中的文件。
mnt/目录下的文件也是这样理解的,sdcard也在这个目录中,他就是挂载到android系统上的文件系统,
关于Android的数据外部存储,在API Level 8之前,所有的文件都是建议放在Environment.getExternalStorageState()目录下的;
从API Level 8开始,
1>对于应用程序的私有文件应该放在Context.getExternalFilesDir目录下,
2>非私有的(shared)的文件应该放在目录下Environment.getExternalStoragePublicDirectory(String)所指定的目录下。
3>对于缓存文件应该放在Context.getExternalCacheDir()目录下。
4>另外在准备把数据保存外部存储之前应该先通过Environment.getExternalStorageState()获取其状态,再根据其状态确定其是否可用,
(3)Android开发:filePath放在哪个文件夹
Environment.getRootDirectory() = /system
getPackageCodePath() ---- /data/app/com.my.app-1.apk
getPackageResourcePath() ---- /data/app/com.my.app-1.apk
getCacheDir() ---- /data/data/com.my.app/cache
getDatabasePath(“test”) ---- /data/data/com.my.app/databases/test
getDir(“test”, Context.MODE_PRIVATE) = /data/data/com.my.app/app_test
getFilesDir() ----/data/data/com.my.app/files
Environment.getExternalFilesDir() ---- /mnt/sdcard/Android/data/com.xxx.xxx/files 一般放一些长时间保存的数据,卸载后会被删除
Environment.getExternalCacheDir() ---- /mnt/sdcard/Android/data/com.my.app/cache 一般存放临时缓存数据,卸载后会被删除
Environment.getExternalFilesDir(“test”) ---- /mnt/sdcard/Android/data/com.my.app/files/test
Environment.getExternalFilesDir(null) ---- /mnt/sdcard/Android/data/com.my.app/files
Environment.getExternalFilesDirs("abc") ----/storage/emulated/0/Android/data/com.xxx.xxx/files/abc
Environment.getExternalStorageDirectory() ----- /mnt/sdcard
Environment.getDownloadCacheDirectory() ------ /cache
Environment.getDataDirectory() ------- /data
Environment.getExternalStoragePublicDirectory(“test”) ------ /mnt/sdcard/test
• DIRECTORY_ALARMS //警报铃声
• DIRECTORY_DCIM //相机拍摄的图片和视频
• DIRECTORY_DOWNLOADS //下载文件保存
• DIRECTORY_MOVIES //电影的保存,比如通过google play下载的电影
• DIRECTORY_MUSIC //音乐保存
• DIRECTORY_NOTIFICATIONS //通知音乐保存
• DIRECTORY_PICTURES //下载的图片
• DIRECTORY_PODCASTS //用于保存podcast(博客)的音频文件
• DIRECTORY_RINGTONES //保存铃声
本次图片保存在Download目录下,所以使用Environment.DIRECTORY_DOWNLOADS。
2.关于sd卡中storage/emulated/0找不到问题,DDMS中查找
没有, storage/emulated/0 , 只有 storage/emulated/legacy --指向-- /mnt/shell/emulated/0
3.最近遇到了无法通过 pm 命令使用系统的安装器把 apk 安装到 sdcard 的问题,返回
Failure [INSTALL_FAILED_INSUFFICIENT_STORAGE]
主要原因是 emulated sdcard 不能安装 apk,因此在安装之前需要判断 sdcard 是否是 emulated。
这个功能系统已经提供了api,Environment.isExternalStorageEmulated();
Returns whether the device has an external storage device which is emulated. If true, the device does not have real external storage,
and the directory returned by getExternalStorageDirectory() will be allocated using a portion of the internal storage system.
Certain system services, such as the package manager, use this to determine where to install an application.
Emulated external storage may also be encrypted - see android.app.admin.DevicePolicyManager.setStorageEncryption(android.content.ComponentName, boolean) for additional details.
4./storage/emulated/0/ 中的“0”是什么意思?
系统服务只会有一个,不同的用户共用一个系统服务,不像一般的应用程序,不同的用户启动的应用程序会有多个,用uid区别开
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
得到的绝对路径为/storage/emulated/0/Pictures,这之间的0代表着什么含义呢?
这其实就是一个uid的区别,不同用户的数据存储空间以此相互分隔。
如何进行用户识别?
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
am.getCurrentUser(); //默认用户 =0;
基于多用户可以实现很多有意思的事情,比如老人模式,儿童模式,绿色模式等等,实质上就是进行一个用户的切换,
而不同用户配置不同的权限,分配不同的资源。但由于切换用户的时间,资源等成本过高,一般需要用动画等进行过渡,避免用户体验不佳。
很多时候我们并不需要如此重量级的解决方案,因此,谷歌有提供了另一个概念,叫Profile,以一种更轻量的方式实现隔离的效果。
目前,应用多开,保险箱,隐私空间等大都再此基础上进行设计。
5.Android 总结外置 内置SD卡路径问题 ---- fuse 功能
我们先来看下android5.1 init.rc中
mkdir /mnt/shell/emulated 0700 shell shell
mkdir /storage/emulated 0555 root root
mkdir /storage/sdcard1 0000 system system
mkdir /storage/usbotg 0700 system system
mkdir /mnt/media_rw/usbotg 0700 media_rw media_rw
mkdir /mnt/media_rw/sdcard1 0700 media_rw media_rw
export EXTERNAL_STORAGE /storage/emulated/legacy
export PRIMARY_STORAGE /storage/sdcard0
export SECONDARY_STORAGE /storage/sdcard1
export EMULATED_STORAGE_SOURCE /mnt/shell/emulated
export EMULATED_STORAGE_TARGET /storage/emulated
# Support legacy paths
symlink /storage/emulated/legacy /sdcard
symlink /storage/emulated/legacy /mnt/sdcard
symlink /mnt/shell/emulated/0 /storage/emulated/legacy
symlink /storage/emulated/legacy /storage/sdcard0
下面这段是fuse功能,将storage/sdcard1 转到/mnt/media_rw/sdcard1 并且拥有权限
# virtual sdcard daemon running as media_rw (1023)
service sdcard /system/bin/sdcard -u 1023 -g 1023 -l /data/media /mnt/shell/emulated
class late_start
chown system system /data/etc/storage.config
# fusewrapped external sdcard daemon running as media_rw (1023)
service fuse_sdcard1 /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d /mnt/media_rw/sdcard1 /storage/sdcard1
class late_start
service fuse_usbotg /system/bin/sdcard -u 1023 -g 1023 -d /mnt/media_rw/usbotg /storage/usbotg
class late_start
再来看Environment.getExternalStorageDirectory其实这个获得的内部SD卡的路径,只是Android把它理解成了外部存储。
它的路径就是storage/sdcard0,进入目录看,群组的权限是都有的。
它的群组是sdcard_r。在APK中,加入相关权限会加入这个群组。
也就拥有这些文件夹的读写权限了。
drwxrwx--- root sdcard_r 1980-01-01 08:06 Alarms
drwxrwx--x root sdcard_r 2015-10-20 15:40 Android
drwxrwx--- root sdcard_r 2015-12-03 10:17 Camera360
drwxrwx--- root sdcard_r 2015-11-16 17:24 DCIM
drwxrwx--- root sdcard_r 2015-10-29 13:48 Download
drwxrwx--- root sdcard_r 2015-10-19 17:08 Movies
drwxrwx--- root sdcard_r 2015-10-21 18:55 Music
drwxrwx--- root sdcard_r 1980-01-01 08:06 Notifications
drwxrwx--- root sdcard_r 2015-11-25 13:49 Pictures
drwxrwx--- root sdcard_r 1980-01-01 08:06 Podcasts
drwxrwx--- root sdcard_r 1980-01-01 08:06 Ringtones
drwxrwx--- root sdcard_r 2015-12-03 11:21 baidu
drwxrwx--- root sdcard_r 1980-01-01 08:00 elog
drwxrwx--- root sdcard_r 2015-12-03 10:51 libs
drwxrwx--- root sdcard_r 2015-12-03 11:21 soufun
drwxrwx--- root sdcard_r 2015-12-03 10:17 system
如果默认存储是外部sd卡的话,又想获取内部存储的路径怎么办呢?
如果是系统应用可以从storageManager中getVolumList可以获取所有的Volume,后去Volume去看它是不是Primary的,如果是就是内部存储。也可以直接使用getPrimaryVolume获取内存存储的Volume,但是一般的APK调不到这函数。
一般apk应该可以使用mount命令这种方式查看内部存储和外部存储的信息。
[html] view plain copy
rootfs / rootfs ro,relatime 0 0
tmpfs /dev tmpfs rw,seclabel,nosuid,relatime,mode=755 0 0
devpts /dev/pts devpts rw,seclabel,relatime,mode=600 0 0
proc /proc proc rw,relatime 0 0
sysfs /sys sysfs rw,seclabel,relatime 0 0
selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0
debugfs /sys/kernel/debug debugfs rw,relatime 0 0
none /acct cgroup rw,relatime,cpuacct 0 0
none /sys/fs/cgroup tmpfs rw,seclabel,relatime,mode=750,gid=1000 0 0
none /sys/fs/cgroup/memory cgroup rw,relatime,memory 0 0
tmpfs /mnt/asec tmpfs rw,seclabel,relatime,mode=755,gid=1000 0 0
tmpfs /mnt/obb tmpfs rw,seclabel,relatime,mode=755,gid=1000 0 0
none /dev/memcg cgroup rw,relatime,memory 0 0
none /dev/cpuctl cgroup rw,relatime,cpu 0 0
tmpfs /tmp tmpfs rw,seclabel,relatime 0 0
/dev/block/platform/comip-mmc.1/by-name/system /system ext4 ro,seclabel,relatime,data=ordered 0 0
/dev/block/platform/comip-mmc.1/by-name/cache /cache ext4 rw,seclabel,nosuid,nodev,noatime,data=ordered 0 0
/dev/block/platform/comip-mmc.1/by-name/userdata /data ext4 rw,seclabel,nosuid,nodev,noatime,noauto_da_alloc,data=ordered 0 0
/dev/block/platform/comip-mmc.1/by-name/amt /amt ext4 rw,seclabel,relatime,data=ordered 0 0
/dev/fuse /mnt/shell/emulated fuse rw,nosuid,nodev,noexec,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/sdcard1 fuse rw,nosuid,nodev,noexec,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/usbotg fuse rw,nosuid,nodev,noexec,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
上面就是mount命令打出来的信息,可以看到3个fuse文件系统,这个手机上没有外部SD卡,就没有外部SD卡的挂载信息了,内部的SD卡其实就是data分区的一部分。
方案一:通过Enviroment类获取存储设备路径------
方案二:读取system/etc/vold.fstab文件的内容来获取存储设备路径
参考文档:http://blog.csdn.net/bbmiku/article/details/7937745
内置和外置SD卡的信息存在system/etc/vold.fstab 里面,我们可以从这里获得外置SD卡的路径。
方案三:方案三的原理是linux命令,在命令窗口中输入 mount 或者 cat /proc/mounts 可得到系统挂载的存储。
你也可以在DOS窗口中输入 adb shell -> mount ,或者 adb shell , cat /proc/mounts 来查看
好,我来DOS窗口中输入adb shell -> mount 来看下,会看到一些信息
相关代码实现:
写一个广播来监听sdcard是否拔插来获得外置sdcard路径
IntentFilter intentFilter = new IntentFilter();// sd卡被插入,且已经挂载
intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
intentFilter.addDataScheme("file");
registerReceiver(sdcardRec, intentFilter);// 注册监听函数
public class SDcaedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
intent.getData().getPath();//外置设备路径
}
}
try {
Runtime runtime = Runtime.getRuntime();
Process proc = runtime.exec("mount");
InputStream is = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
String line;
BufferedReader br = new BufferedReader(isr);
while ((line = br.readLine()) != null) {
if (line.contains("secure")) continue;
if (line.contains("asec")) continue;
if (line.contains("fat")) {
String columns[] = line.split(" ");
if (columns != null && columns.length > 1) {
sdcard_path = sdcard_path.concat("*" + columns[1] );
}
} else if (line.contains("fuse")) {
String columns[] = line.split(" ");
if (columns != null && columns.length > 1) {
sdcard_path = sdcard_path.concat(columns[1] );
}
}
}
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sdcard_path;
方案四:android常见的SD卡存储位置
/storage/emulated/0/
/storage/extSdCard
/mnt/external_sd/
/mnt/sdcard2/
/mnt/sdcard/external_sd/
/mnt/sdcard-ext/
/mnt/sdcard/
/storage/sdcard0/
/mnt/extSdCard/
/mnt/extsd/
/mnt/emmc/
/mnt/extern_sd/
/mnt/ext_sd/
/mnt/ext_card/
/mnt/_ExternalSD/
/sdcard2/
/sdcard/
/sdcard/sd/
/sdcard/external_sd/
/mnt/sd/
/mnt/
/storage/
/mnt/sdcard/sd/
/mnt/exsdcard/
/mnt/sdcard/extStorages/SdCard/
/ext_card/
/storage/extSdCard
3.0以上可以通过反射获取:
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
// 获取sdcard的路径:外置和内置
String[] paths = (String[]) sm.getClass().getMethod("getVolumePaths", null).invoke(sm, null);
Android 4.1上
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
String[] volumePaths = sm.getgetVolumePaths();
/**
* 获取外置SD卡路径
*
* @return
*/
public static List<String> getSDCardPaths() {
List<String> sdcardPaths = new ArrayList<String>();
String cmd = "cat /proc/mounts";
Runtime run = Runtime.getRuntime();// 返回与当前 Java 应用程序相关的运行时对象
try {
Process p = run.exec(cmd);// 启动另一个进程来执行命令
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String lineStr;
while ((lineStr = inBr.readLine()) != null) {
// 获得命令执行后在控制台的输出信息
LogUtil.i("CommonUtil:getSDCardPath", lineStr);
String[] temp = TextUtils.split(lineStr, " ");
// 得到的输出的第二个空格后面是路径
String result = temp[1];
File file = new File(result);
if (file.isDirectory() && file.canRead() && file.canWrite()) {
LogUtil.d("directory can read can write:",
file.getAbsolutePath());
// 可读可写的文件夹未必是sdcard,我的手机的sdcard下的Android/obb文件夹也可以得到
sdcardPaths.add(result);
}
// 检查命令是否执行失败。
if (p.waitFor() != 0 && p.exitValue() == 1) {
// p.exitValue()==0表示正常结束,1:非正常结束
LogUtil.e("CommonUtil:getSDCardPath", "命令执行失败!");
}
}
inBr.close();
in.close();
} catch (Exception e) {
LogUtil.e("CommonUtil:getSDCardPath", e.toString());
sdcardPaths.add(Environment.getExternalStorageDirectory()
.getAbsolutePath());
}
optimize(sdcardPaths);
for (Iterator iterator = sdcardPaths.iterator(); iterator.hasNext();) {
String string = (String) iterator.next();
Log.e("清除过后", string);
}
return sdcardPaths;
}
private static void optimize(List<String> sdcaredPaths) {
if (sdcaredPaths.size() == 0) {
return;
}
int index = 0;
while (true) {
if (index >= sdcaredPaths.size() - 1) {
String lastItem = sdcaredPaths.get(sdcaredPaths.size() - 1);
for (int i = sdcaredPaths.size() - 2; i >= 0; i--) {
if (sdcaredPaths.get(i).contains(lastItem)) {
sdcaredPaths.remove(i);
}
}
return;
}
String containsItem = sdcaredPaths.get(index);
for (int i = index + 1; i < sdcaredPaths.size(); i++) {
if (sdcaredPaths.get(i).contains(containsItem)) {
sdcaredPaths.remove(i);
i--;
}
}
index++;
}
}