android 内外置SD卡的研究

最近网站后台监控上传的app崩溃记录,最常见的一条就是获取手机sd卡根目录出现崩溃。所以有查找各方面的资料,以及看android api源码。

以前的Android(4.1之前的版本)中,SDcard跟路径通过“/sdcard”或者“/mnt/sdcard”来表示存储卡,而在Jelly Bean系统中修改为了“/storage/sdcard0”,以后可能还会有多个SDcard的情况。

目前为了保持和之前代码的兼容,sdcard路径做了link映射。

为了使您的代码更加健壮并且能够兼容以后的Android版本和新的设备,请通过Environment.getExternalStorageDirectory().getPath()来获取sdcard路径。

1、通用的获取语句

Environment.getExternalStorageDirectory().getPath()
当外置sd卡不存在的情况下,这条语句是获取的内置sd卡的路径。外置sd卡存在,获取是外置sd卡。获取出来的值是/storage/sdcard0

2、那么内置sd卡是怎样的

	public static String getSoftwareStorageDirectory() {
		Map<String,String> sysInfo=System.getenv();
		//获取外置的sd卡
		String sd_defult = sysInfo.get("SECONDARY_STORAGE");
	    return sd_defult;
	}
获取出来的值是/storage/sdcard1,看序号一个是0,一个是1。我在网上看到有人说,这种获取方法,在api23中将不存在,我还没验证,没有相应 的机子测试,以及查看api23的源码。就是这个字段在API 23版本中 SECONDARY_STORAGE 被移除。这种获取方法也是类似于源码获取方式。

调试时候Map<String,String> sysInfo=System.getenv();,这句语句获取出来的值如下:

	{ANDROID_SOCKET_zygote=9, 
			SECONDARY_STORAGE=/storage/sdcard1,
			ANDROID_STORAGE=/storage, ANDROID_BOOTLOGO=1,
			EXTERNAL_STORAGE=/storage/sdcard0,
			ANDROID_ASSETS=/system/app,
			ASEC_MOUNTPOINT=/mnt/asec, 
			PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin,
			LOOP_MOUNTPOINT=/mnt/obb, 
			BOOTCLASSPATH=/system/framework/core.jar:/system/framework/core-junit.jar:
			/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:
			/system/framework/telephony-common.jar:/system/framework/mms-common.jar:
			/system/framework/android.policy.jar:/system/framework/services.jar:
			/system/framework/apache-xml.jar:/system/framework/mediatek-common.jar:
			/system/framework/mediatek-framework.jar:/system/framework/secondary-framework.jar:
			/system/framework/CustomProperties.jar:/system/framework/mediatek-telephony-common.jar:
			/system/framework/mediatek-op.jar, 
			ANDROID_DATA=/data, 
			ANDROID_PROPERTY_WORKSPACE=8,49664,
			ANDROID_ROOT=/system,
			LD_LIBRARY_PATH=/vendor/lib:/system/lib}

api17源码,部分源码Environment

    private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
    private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
    private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
    private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
        public UserEnvironment(int userId) {
            // See storage config details at http://source.android.com/tech/storage/
            String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
            String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
            String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);
            if (TextUtils.isEmpty(rawMediaStorage)) {
                rawMediaStorage = "/data/media";
            }

            if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) {
                // Device has emulated storage; external storage paths should have
                // userId burned into them.
                final String rawUserId = Integer.toString(userId);
                final File emulatedBase = new File(rawEmulatedStorageTarget);
                final File mediaBase = new File(rawMediaStorage);

                // /storage/emulated/0
                mExternalStorage = buildPath(emulatedBase, rawUserId);
                // /data/media/0
                mMediaStorage = buildPath(mediaBase, rawUserId);

            } else {
                // Device has physical external storage; use plain paths.
                if (TextUtils.isEmpty(rawExternalStorage)) {
                    Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default");
                    rawExternalStorage = "/storage/sdcard0";
                }

                // /storage/sdcard0
                mExternalStorage = new File(rawExternalStorage);
                // /data/media
                mMediaStorage = new File(rawMediaStorage);
            }

            mExternalStorageAndroidObb = buildPath(mExternalStorage, DIRECTORY_ANDROID, "obb");
            mExternalStorageAndroidData = buildPath(mExternalStorage, DIRECTORY_ANDROID, "data");
            mExternalStorageAndroidMedia = buildPath(mExternalStorage, DIRECTORY_ANDROID, "media");
        }

在上面的源码当中String sdcardPath = System.getenv("EXTERNAL_STORAGE"); 获取也是外置sd卡。

可是在源码中也是没有看到String extSdcardPath = System.getenv("SECONDARY_STORAGE"); 。SECONDARY_STORAGE这个变量是没有,我也纳闷,怎么也可以使用。这就有点疑问了。

获取外置sd卡是否存在的源码

    /**
     * Gets the current state of the primary "external" storage device.
     * 
     * @see #getExternalStorageDirectory()
     */
    public static String getExternalStorageState() {
        try {
            IMountService mountService = IMountService.Stub.asInterface(ServiceManager
                    .getService("mount"));
            final StorageVolume primary = getPrimaryVolume();
            return mountService.getVolumeState(primary.getPath());
        } catch (RemoteException rex) {
            Log.w(TAG, "Failed to read external storage state; assuming REMOVED: " + rex);
            return Environment.MEDIA_REMOVED;
        }
    }
一些映射关系

    private static StorageVolume getPrimaryVolume() {
        if (sPrimaryVolume == null) {
            synchronized (sLock) {
                if (sPrimaryVolume == null) {
                    try {
                        IMountService mountService = IMountService.Stub.asInterface(ServiceManager
                                .getService("mount"));
                        final StorageVolume[] volumes = mountService.getVolumeList();
                        sPrimaryVolume = StorageManager.getPrimaryVolume(volumes);
                    } catch (Exception e) {
                        Log.e(TAG, "couldn't talk to MountService", e);
                    }
                }
            }
        }
        return sPrimaryVolume;
    }
那么,在android上是怎么判断的。
	/**
	 * getExternalStorageState() returns MEDIA_CHECKING if the media is present and being disk-checked
	 * 要获取SD卡首先要确认SD卡是否装载  
	 * @return
	 */
	public boolean getExternalStorageState(){
		return Environment.getExternalStorageState().equals(Environment.MEDIA_CHECKING); 
	}
判断是都可以读写

	/**
	 * getExternalStorageState() returns MEDIA_MOUNTED 
	 * if the media is present and mounted at its mount point with read/write access. 
	 * 要获取SD卡是否可以读写
	 * @return
	 */
	public boolean getExternalStorageStateMOUNTED(){
		return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 
	}
这里判断读写跟判断是都存在是有歧义的,我测试时候,没有放sd卡,判断读写是可以的,可是外置sd卡根本不存在。写程序时候是不是先要判断存在,再判断能不能读写。

还有一个问题就是,既然外置sd卡不存在时候Environment.getExternalStorageDirectory().getPath()这条语句获取的是内置的sd卡,那么判断是否是内置sd卡的读写呢?

3、网上看到最多获取外置SD卡路径两个方法

方法一

	/**
	 * 获取外置SD卡路径
	 * 
	 * @return 应该就一条记录或空
	 */
	public List getExtSDCardPath() {
		List lResult = new ArrayList();
		try {
			Runtime rt = Runtime.getRuntime();
			Process proc = rt.exec("mount");
			InputStream is = proc.getInputStream();
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			String line;
			//rootfs / rootfs ro,relatime 0 0
			while ((line = br.readLine()) != null) {
				if (line.contains("extSdCard")) {
					String[] arr = line.split(" ");
					String path = arr[1];
					File file = new File(path);
					if (file.isDirectory()) {
						lResult.add(path);
					}
				}
			}
			isr.close();
		} catch (Exception e) {
		}
		return lResult;
	}

方法二

	//获取外置存储卡的根路径,如果没有外置存储卡,则返回null 
	public String getExtSDCardPath2() {
		String sdcard_path = null;
		String sd_default = Environment.getExternalStorageDirectory()
				.getAbsolutePath();
		if (sd_default.endsWith("/")) {
			sd_default = sd_default.substring(0, sd_default.length() - 1);
		}
		// 得到路径
		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);
			//rootfs / rootfs ro,relatime 0 0
			while ((line = br.readLine()) != null) {
				if (line.contains("secure"))
					continue;
				if (line.contains("asec"))
					continue;
				if (line.contains("fat") && line.contains("/mnt/")) {
					String columns[] = line.split(" ");
					if (columns != null && columns.length > 1) {
						if (sd_default.trim().equals(columns[1].trim())) {
							continue;
						}
						sdcard_path = columns[1];
					}
				} else if (line.contains("fuse") && line.contains("/mnt/")) {
					String columns[] = line.split(" ");
					if (columns != null && columns.length > 1) {
						if (sd_default.trim().equals(columns[1].trim())) {
							continue;
						}
						sdcard_path = columns[1];
					}
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return sdcard_path;
	}
我用机子测试时候,获取出来的值都是//rootfs / rootfs ro,relatime 0 0

根本没有想要的。

manifest权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>


最后总结一下

Environment.getExternalStorageDirectory().getPath()是万能的,不管是外置还是内置sd卡都可以获取。

不过app应用来说是需要固定一个路径的。

我现在使用的是

	public String getSoftwareStorageDirectory() {
		String strDirectory;
		Map<String,String> sysInfo=System.getenv();
		String sd_defult = sysInfo.get("SECONDARY_STORAGE");
		if (!CheckDirectoryExists(sd_defult))
		{
			sd_defult = Environment.getExternalStorageDirectory().getPath();
		}
	    return strDirectory;
	}
	
	//判断检出目录是否存在
	private boolean CheckDirectoryExists(String fileName) {
		if (fileName == null || fileName.length()<=0) {
			return false;
		}
		File temFile = new File(fileName);
		File[] childFiles = null;
				
		if (temFile != null)
			childFiles = temFile.listFiles();

		if (childFiles == null) {
			return false;
		}
		
		//目录是否可以写
		File testFile = new File(temFile, "com_testDir");
		if (!testFile.exists()) {
			if (!testFile.mkdir()) {
				return false;
			}
			testFile.delete();
		}
		
		return true;
	}

欢迎大家测试,提供不同机子的测试结果,还有不同源码下是怎样的。
因为有不同厂商,或者用户是刷机,所以有很多奇葩的路径也不足为奇。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值