Android系统内置sdcard和扩展存储路径是怎么来的?

 

这篇文章主要分析一下Android L上面MountService获取sdcard路径(/storage/emulated/0,/storage/sdcard0,/sdcard)到底是怎么来的。为什么不同的厂商提供的安卓设备,APK中获取到的内置或外置存储路径不一致?

 

首先,仔细查看logcat的信息,抓关键log:


03-04 19:56:20.735 I/SystemServer( 404): Mount Service
03-04 19:56:20.788 D/MountService( 404): got storage path: /storage/sdcard0 description: .................. primary: false removable: false emulated: true mtpReserve: 100 allowMassStorage: false maxFileSize: 0
03-04 19:56:20.789 D/MountService( 404): addVolumeLocked() StorageVolume:
03-04 19:56:20.789 D/MountService( 404): mStorageId=65537 mPath=/storage/emulated/0 mDescriptionId=17040805
03-04 19:56:20.789 D/MountService( 404): mPrimary=true mRemovable=false mEmulated=true mMtpReserveSpace=100
03-04 19:56:20.789 D/MountService( 404): mAllowMassStorage=false mMaxFileSize=0 mOwner=UserHandle{0} mUuid=null
03-04 19:56:20.789 D/MountService( 404): mUserLabel=null mState=null
03-04 19:56:20.789 D/MountService( 404): got storage path: /storage/sdcard1 description: SD... primary: false removable: true emulated: false mtpReserve: 0 allowMassStorage: false maxFileSize: 4294967296
03-04 19:56:20.789 D/MountService( 404): addVolumeLocked() StorageVolume:
03-04 19:56:20.789 D/MountService( 404): mStorageId=0 mPath=/storage/sdcard1 mDescriptionId=17040806 mPrimary=false
03-04 19:56:20.789 D/MountService( 404): mRemovable=true mEmulated=false mMtpReserveSpace=0 mAllowMassStorage=false
03-04 19:56:20.789 D/MountService( 404): mMaxFileSize=4294967296 mOwner=null mUuid=null mUserLabel=null mState=null
03-04 19:56:20.790 D/MountService( 404): got storage path: /storage/usbdisk1 description: USB......... primary: false removable: true emulated: false mtpReserve: 0 allowMassStorage: false maxFileSize: 4294967296
03-04 19:56:20.790 D/MountService( 404): addVolumeLocked() StorageVolume:
03-04 19:56:20.790 D/MountService( 404): mStorageId=0 mPath=/storage/usbdisk1 mDescriptionId=17040807
03-04 19:56:20.790 D/MountService( 404): mPrimary=false mRemovable=true mEmulated=false mMtpReserveSpace=0
03-04 19:56:20.790 D/MountService( 404): mAllowMassStorage=false mMaxFileSize=4294967296 mOwner=null mUuid=null
03-04 19:56:20.790 D/MountService( 404): mUserLabel=null mState=null

分析log可以大概知道,MountService启动之后,会获取当前存储卷的列表。

依据log来跟源码,搜索MountService或者打印部分的关键字

$ find frameworks/ -type f -name "*MountService*"

frameworks/base/services/core/java/com/android/server/MountService.java

 

    public MountService(Context context) {
        sSelf = this;

        mContext = context;

        synchronized (mVolumesLock) {
            readStorageListLocked();
        }

        // XXX: This will go away soon in favor of IMountServiceObserver
        mPms = (PackageManagerService) ServiceManager.getService("package");

        HandlerThread hthread = new HandlerThread(TAG);
        hthread.start();
        mHandler = new MountServiceHandler(hthread.getLooper());

        // Watch for user changes
        final IntentFilter userFilter = new IntentFilter();
        userFilter.addAction(Intent.ACTION_USER_ADDED);
        userFilter.addAction(Intent.ACTION_USER_REMOVED);
        mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);

        // Watch for USB changes on primary volume
        final StorageVolume primary = getPrimaryPhysicalVolume();
        if (primary != null && primary.allowMassStorage()) {
            mContext.registerReceiver(
                    mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
        }

        // Add OBB Action Handler to MountService thread.
        mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());

        // Initialize the last-fstrim tracking if necessary
        File dataDir = Environment.getDataDirectory();
        File systemDir = new File(dataDir, "system");
        mLastMaintenanceFile = new File(systemDir, LAST_FSTRIM_FILE);
        if (!mLastMaintenanceFile.exists()) {
            // Not setting mLastMaintenance here means that we will force an
            // fstrim during reboot following the OTA that installs this code.
            try {
                (new FileOutputStream(mLastMaintenanceFile)).close();
            } catch (IOException e) {
                Slog.e(TAG, "Unable to create fstrim record " + mLastMaintenanceFile.getPath());
            }
        } else {
            mLastMaintenance = mLastMaintenanceFile.lastModified();
        }

        /*
         * Create the connection to vold with a maximum queue of twice the
         * amount of containers we'd ever expect to have. This keeps an
         * "asec list" from blocking a thread repeatedly.
         */
        mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
                null);

        Thread thread = new Thread(mConnector, VOLD_TAG);
        thread.start();

        // Add ourself to the Watchdog monitors if enabled.
        if (WATCHDOG_ENABLE) {
            Watchdog.getInstance().addMonitor(this);
        }
    }

这里会调用readStorageListLocked来遍历,/storage/sdcard0卷对应的emulated为true,

    // Storage list XML tags
    private static final String TAG_STORAGE_LIST = "StorageList";
    private static final String TAG_STORAGE = "storage";

    private void readStorageListLocked() {
        mVolumes.clear();
        mVolumeStates.clear();

        Resources resources = mContext.getResources();

        int id = com.android.internal.R.xml.storage_list;
        XmlResourceParser parser = resources.getXml(id);
        AttributeSet attrs = Xml.asAttributeSet(parser);

        try {
            XmlUtils.beginDocument(parser, TAG_STORAGE_LIST);
            while (true) {
                XmlUtils.nextElement(parser);

                String element = parser.getName();
                if (element == null) break;

                if (TAG_STORAGE.equals(element)) {
                    TypedArray a = resources.obtainAttributes(attrs,
                            com.android.internal.R.styleable.Storage);

                    String path = a.getString(
                            com.android.internal.R.styleable.Storage_mountPoint);
                    int descriptionId = a.getResourceId(
                            com.android.internal.R.styleable.Storage_storageDescription, -1);
                    CharSequence description = a.getText(
                            com.android.internal.R.styleable.Storage_storageDescription);
                    boolean primary = a.getBoolean(
                            com.android.internal.R.styleable.Storage_primary, false);
                    boolean removable = a.getBoolean(
                            com.android.internal.R.styleable.Storage_removable, false);
                    boolean emulated = a.getBoolean(
                            com.android.internal.R.styleable.Storage_emulated, false);
                    int mtpReserve = a.getInt(
                            com.android.internal.R.styleable.Storage_mtpReserve, 0);
                    boolean allowMassStorage = a.getBoolean(
                            com.android.internal.R.styleable.Storage_allowMassStorage, false);
                    // resource parser does not support longs, so XML value is in megabytes
                    long maxFileSize = a.getInt(
                            com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L;


                    Slog.d(TAG, "got storage path: " + path + " description: " + description +
                            " primary: " + primary + " removable: " + removable +
                            " emulated: " + emulated + " mtpReserve: " + mtpReserve +
                            " allowMassStorage: " + allowMassStorage +
                            " maxFileSize: " + maxFileSize);

                    if (emulated) {
                        // For devices with emulated storage, we create separate
                        // volumes for each known user.
                        // 这里构造了一个不带path参数的临时StorageVolume对象mEmulatedTemplate
                        mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false,
                                true, mtpReserve, false, maxFileSize, null);

                        final UserManagerService userManager = UserManagerService.getInstance();
                        for (UserInfo user : userManager.getUsers(false)) {
                            createEmulatedVolumeForUserLocked(user.getUserHandle());
                        }

                    } else {
                        if (path == null || description == null) {
                            Slog.e(TAG, "Missing storage path or description in readStorageList");
                        } else {
                            final StorageVolume volume = new StorageVolume(new File(path),
                                    descriptionId, primary, removable, emulated, mtpReserve,
                                    allowMassStorage, maxFileSize, null);
                            addVolumeLocked(volume);

                            // Until we hear otherwise, treat as unmounted
                            mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
                            volume.setState(Environment.MEDIA_UNMOUNTED);
                        }
                    }

                    a.recycle();
                }
            }
        } catch (XmlPullParserException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // Compute storage ID for each physical volume; emulated storage is
            // always 0 when defined.
            int index = isExternalStorageEmulated() ? 1 : 0;
            for (StorageVolume volume : mVolumes) {
                if (!volume.isEmulated()) {
                    volume.setStorageId(index++);
                }
            }
            parser.close();
        }
    }

针对/storage/sdcard0先构造一个不带path路径的StorageVolume对象,然后调用createEmulatedVolumeForUserLocked方法

 

    private void createEmulatedVolumeForUserLocked(UserHandle user) {
        if (mEmulatedTemplate == null) {
            throw new IllegalStateException("Missing emulated volume multi-user template");
        }

        final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier());
        final File path = userEnv.getExternalStorageDirectory(); // 这里emulated storage的路径取自UserEnvironment.getExternalStorageDirectory方法
        final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user);
        volume.setStorageId(0);
        addVolumeLocked(volume);

        if (mSystemReady) {
            updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
        } else {
            // Place stub status for early callers to find
            mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
            volume.setState(Environment.MEDIA_MOUNTED);
        }
    }

真正填充emulated storage的路径的位置在createEmulatedVolumeForUserLocked中,其路径又来自UserEnvironment.getExternalStorageDirectory方法,所以下面需要搜索一下class UserEnvironment

 

$ grep -rnHI "UserEnvironment" frameworks/base/core/java/android/os/

frameworks/base/core/java/android/os/Environment.java:83: public static class UserEnvironment {

frameworks/base/core/java/android/os/Environment.java:93: public UserEnvironment(int userId) {

 

public class Environment {
    private static final String TAG = "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";
    private static final String ENV_SECONDARY_STORAGE = "SECONDARY_STORAGE";
    private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
    private static final String ENV_OEM_ROOT = "OEM_ROOT";
    private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";

    public static final String DIR_ANDROID = "Android";
    private static final String DIR_DATA = "data";
    private static final String DIR_MEDIA = "media";
    private static final String DIR_OBB = "obb";
    private static final String DIR_FILES = "files";
    private static final String DIR_CACHE = "cache";

    /** {@hide} */
    @Deprecated
    public static final String DIRECTORY_ANDROID = DIR_ANDROID;

    private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
    private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem");
    private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor");
    private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");


    public static class UserEnvironment {
        // TODO: generalize further to create package-specific environment

        /** External storage dirs, as visible to vold */
        private final File[] mExternalDirsForVold;
        /** External storage dirs, as visible to apps */
        private final File[] mExternalDirsForApp;
        /** Primary emulated storage dir for direct access */
        private final File mEmulatedDirForDirect;

        public UserEnvironment(int userId) {
            // See storage config details at http://source.android.com/tech/storage/
            String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
            String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE);
            String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);

            String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);
            if (TextUtils.isEmpty(rawMediaStorage)) {
                rawMediaStorage = "/data/media";
            }

            ArrayList<File> externalForVold = Lists.newArrayList();
            ArrayList<File> externalForApp = Lists.newArrayList();

            if (!TextUtils.isEmpty(rawEmulatedTarget)) { // EMULATED_STORAGE_TARGET环境变量有在init.rc中设置,EMULATED_STORAGE_TARGET=/storage/emulated
                // Device has emulated storage; external storage paths should have
                // userId burned into them.
                final String rawUserId = Integer.toString(userId);
                final File emulatedSourceBase = new File(rawEmulatedSource);
                final File emulatedTargetBase = new File(rawEmulatedTarget);
                final File mediaBase = new File(rawMediaStorage);

                // /storage/emulated/0
                externalForVold.add(buildPath(emulatedSourceBase, rawUserId));
                externalForApp.add(buildPath(emulatedTargetBase, rawUserId));
                // /data/media/0
                mEmulatedDirForDirect = buildPath(mediaBase, rawUserId);


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

                // /storage/sdcard0
                externalForVold.add(new File(rawExternalStorage));
                externalForApp.add(new File(rawExternalStorage));
                // /data/media
                mEmulatedDirForDirect = new File(rawMediaStorage);
            }

            // Splice in any secondary storage paths, but only for owner
            final String rawSecondaryStorage = System.getenv(ENV_SECONDARY_STORAGE); // SECONDARY_STORAGE=/storage/sdcard1,即外置sd卡路径
            if (!TextUtils.isEmpty(rawSecondaryStorage) && userId == UserHandle.USER_OWNER) {
                for (String secondaryPath : rawSecondaryStorage.split(":")) {
                    externalForVold.add(new File(secondaryPath));
                    externalForApp.add(new File(secondaryPath));
                }
            }

            mExternalDirsForVold = externalForVold.toArray(new File[externalForVold.size()]);
            mExternalDirsForApp = externalForApp.toArray(new File[externalForApp.size()]);
        }

        @Deprecated
        public File getExternalStorageDirectory() {
            return mExternalDirsForApp[0];
        }

 

可以看到这里getExternalStorageDirectory返回的路径是mExternalDirsForApp[0],而mExternalDirsForApp最大有可能存在两个路径,external storage和secondary storage,secondary storage没有很多流程,即外置sd卡,直接通过环境变量SECONDARY_STORAGE来定义。而external storage稍微麻烦一点,它会依据两个环境变量来确定,流程如上代码所示,说明如下:

1.如果有设置EMULATED_STORAGE_TARGET,则优先返回EMULATED_STORAGE_TARGET的路径,例如:EMULATED_STORAGE_TARGET=/storage/emulated,则返回/storage/emulated/0(根据注释我们知道这个叫做emulated storage);

2.如果没有设置EMULATED_STORAGE_TARGET,则检查EXTERNAL_STORAGE是否设置,如果设置,则返回EXTERNAL_STORAGE的路径,例如:EXTERNAL_STORAGE=/sdcard,则返回/sdcard(根据注释我们知道这个叫做physical external storage);

3.如果EMULATED_STORAGE_TARGET和EXTERNAL_STORAGE都没有设置,则默认返回/storage/sdcard0路径。

 

所以不同的厂商可以在init.rc(包括init.xxx.rc)中定义这几个环境变量,来根据实际情况改变内置sdcard的路径,我们在APP中通过Environment.getExternalStorageDirectory()获取到的内置sdcard的路径在不同平台上可能就会存在差异了。

 

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值