这篇文章主要分析一下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的路径在不同平台上可能就会存在差异了。