问题是当我们再setting点击卸载SD卡的时候,这个时候“卸载SD卡”这一项会变成"正在卸载“并且变灰。但是很快又变亮了。而且卸载完后,还可以格式化。对于出现的这两个问题我们进行分析。
我们首先来看setting的代码
在Memory.java中实现了一个StorageEventListener :
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);
for (StorageVolumePreferenceCategory category : mCategories) {
final StorageVolume volume = category.getStorageVolume();
if (volume != null && path.equals(volume.getPath())) {
category.onStorageStateChanged();
break;
}
}
}
};
并且在MountService中注册了
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Context context = getActivity();
mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
mStorageManager = StorageManager.from(context);
mStorageManager.registerListener(mStorageListener);//注册listener
addPreferencesFromResource(R.xml.device_info_memory);
addCategory(StorageVolumePreferenceCategory.buildForInternal(context));
final StorageVolume[] storageVolumes = mStorageManager.getVolumeList();
for (StorageVolume volume : storageVolumes) {
if (!volume.isEmulated()) {
addCategory(StorageVolumePreferenceCategory.buildForPhysical(context, volume));
}
}
setHasOptionsMenu(true);
}
在在Memory.java中的StorageEventListener 调用了onStorageStateChanged函数,再来看看StorageVolumePreferenceCategory
public void onStorageStateChanged() {
init();
measure();
}
private void measure() {
mMeasure.invalidate();
mMeasure.measure();
}
再来看看mMeature,在StorageVolumePreferenceCategory构造函数里赋值
private StorageVolumePreferenceCategory(Context context, StorageVolume volume) {
super(context);
mVolume = volume;
mMeasure = StorageMeasurement.getInstance(context, volume);
mResources = context.getResources();
mStorageManager = StorageManager.from(context);
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
setTitle(volume != null ? volume.getDescription(context)
: context.getText(R.string.internal_storage));
}
而在StorageMeasurement中的measure函数如下
public void measure() {
if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE)) {
mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE);
}
}
再来看看消息处理
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MEASURE: {
if (mCached != null) {
sendExactUpdate(mCached);
break;
}
final Context context = (mContext != null) ? mContext.get() : null;
if (context == null) {
return;
}
synchronized (mLock) {
if (mBound) {
removeMessages(MSG_DISCONNECT);
sendMessage(obtainMessage(MSG_CONNECTED, mDefaultContainer));//继续发送消息
} else {
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
context.bindServiceAsUser(service, mDefContainerConn, Context.BIND_AUTO_CREATE,
UserHandle.OWNER);
}
}
break;
}
case MSG_CONNECTED: {
IMediaContainerService imcs = (IMediaContainerService) msg.obj;
measureApproximateStorage(imcs);//调用measureApproximateStorage
measureExactStorage(imcs);
break;
}
看看measureApproximateStorage函数
private void measureApproximateStorage(IMediaContainerService imcs) {
final String path = mVolume != null ? mVolume.getPath()
: Environment.getDataDirectory().getPath();
try {
final long[] stats = imcs.getFileSystemStats(path);
mTotalSize = stats[0];
mAvailSize = stats[1];
} catch (Exception e) {
Log.w(TAG, "Problem in container service", e);
}
sendInternalApproximateUpdate();
}
而在sendInternalApproximateUpdate函数中调用了receiver.updateApproximate
private void sendInternalApproximateUpdate() {
MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
if (receiver == null) {
return;
}
receiver.updateApproximate(this, mTotalSize, mAvailSize);
}
再来看看这个mReceiver
public void setReceiver(MeasurementReceiver receiver) {
if (mReceiver == null || mReceiver.get() == null) {
mReceiver = new WeakReference<MeasurementReceiver>(receiver);
}
}
StorageVolumePreferenceCategory中的mReceiver
private MeasurementReceiver mReceiver = new MeasurementReceiver() {
@Override
public void updateApproximate(StorageMeasurement meas, long totalSize, long availSize) {
mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE, new long[] {
totalSize, availSize }).sendToTarget();
}
@Override
public void updateDetails(StorageMeasurement meas, MeasurementDetails details) {
mUpdateHandler.obtainMessage(MSG_UI_UPDATE_DETAILS, details).sendToTarget();
}
};
在onResume的时候setReceiver
public void onResume() {
mMeasure.setReceiver(mReceiver);
measure();
}
再看看消息处理:
private Handler mUpdateHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UI_UPDATE_APPROXIMATE: {
final long[] size = (long[]) msg.obj;
updateApproximate(size[0], size[1]);
break;
}
case MSG_UI_UPDATE_DETAILS: {
final MeasurementDetails details = (MeasurementDetails) msg.obj;
updateDetails(details);
break;
}
}
}
};
再看看updateApproximate函数:
public void updateApproximate(long totalSize, long availSize) {
mItemTotal.setSummary(formatSize(totalSize));
mItemAvailable.setSummary(formatSize(availSize));
mTotalSize = totalSize;
final long usedSize = totalSize - availSize;
mUsageBarPreference.clear();
mUsageBarPreference.addEntry(0, usedSize / (float) totalSize, android.graphics.Color.GRAY);
mUsageBarPreference.commit();
updatePreferencesFromState();
}
终于看到我们的主函数updatePreferencesFromState:
private void updatePreferencesFromState() {
// Only update for physical volumes
if (mVolume == null) return;
mMountTogglePreference.setEnabled(true);//先把卸载sd卡,安装sd卡置亮
final String state = mStorageManager.getVolumeState(mVolume.getPath());//获取volume的状态
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
mItemAvailable.setTitle(R.string.memory_available_read_only);
} else {
mItemAvailable.setTitle(R.string.memory_available);
}
if (Environment.MEDIA_MOUNTED.equals(state)//如果当前是挂载的置亮
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
mMountTogglePreference.setEnabled(true);
mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject));
mMountTogglePreference.setSummary(mResources.getString(R.string.sd_eject_summary));
addPreference(mUsageBarPreference);
addPreference(mItemTotal);
addPreference(mItemAvailable);
} else {
if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state)
|| Environment.MEDIA_UNMOUNTABLE.equals(state)) {//如果是卸载,置亮;title变成安装SD卡
mMountTogglePreference.setEnabled(true);
mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
mMountTogglePreference.setSummary(mResources.getString(R.string.sd_mount_summary));
} else {//否则是没有SD卡,置灰
mMountTogglePreference.setEnabled(false);
mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
mMountTogglePreference.setSummary(mResources.getString(R.string.sd_insert_summary));
}
removePreference(mUsageBarPreference);
removePreference(mItemTotal);
removePreference(mItemAvailable);
}
if (mUsbConnected && (UsbManager.USB_FUNCTION_MTP.equals(mUsbFunction) ||
UsbManager.USB_FUNCTION_PTP.equals(mUsbFunction))) {//如果连usb,mtp
mMountTogglePreference.setEnabled(false);//将sd卡那项置灰
if (Environment.MEDIA_MOUNTED.equals(state)
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
mMountTogglePreference.setSummary(
mResources.getString(R.string.mtp_ptp_mode_summary));
}
if (mFormatPreference != null) {
mFormatPreference.setEnabled(false);
mFormatPreference.setSummary(mResources.getString(R.string.mtp_ptp_mode_summary));
}
} else if (mFormatPreference != null) {//最后格式化那项根据sd卡那项来置亮还是置灰。
mFormatPreference.setEnabled(mMountTogglePreference.isEnabled());
mFormatPreference.setSummary(mResources.getString(R.string.sd_format_summary));
}
}
好,看了主函数后,问题来了。
首先当我们点击卸载sd卡后:
@Override
public Dialog onCreateDialog(int id) {
switch (id) {
case DLG_CONFIRM_UNMOUNT:
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.dlg_confirm_unmount_title)
.setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
doUnmount();//点击后调用doUnmount
}})
.setNegativeButton(R.string.cancel, null)
.setMessage(R.string.dlg_confirm_unmount_text)
.create();
case DLG_ERROR_UNMOUNT:
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.dlg_error_unmount_title)
.setNeutralButton(R.string.dlg_ok, null)
.setMessage(R.string.dlg_error_unmount_text)
.create();
}
return null;
}
private void doUnmount() {
// Present a toast here
Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
IMountService mountService = getMountService();
try {
sLastClickedMountToggle.setEnabled(false);//将卸载SD卡那项置灰
sLastClickedMountToggle.setTitle(getString(R.string.sd_ejecting_title));//title改成"正在卸载"
sLastClickedMountToggle.setSummary(getString(R.string.sd_ejecting_summary));
mountService.unmountVolume(sClickedMountPoint, true, false);
} catch (RemoteException e) {
// Informative dialog to user that unmount failed.
showDialogInner(DLG_ERROR_UNMOUNT);
}
}
我们再来看看vold对卸载这个命令怎么处理:
int Volume::unmountVol(bool force, bool revert) {
int i, rc;
int flags = getFlags();
bool providesAsec = (flags & VOL_PROVIDES_ASEC) != 0;
if (getState() != Volume::State_Mounted) {
SLOGE("Volume %s unmount request when not mounted", getLabel());
errno = EINVAL;
return UNMOUNT_NOT_MOUNTED_ERR;
}
setState(Volume::State_Unmounting);//将状态置为unmounting,直接发送。setState中后面会直接发给MountService
usleep(1000 * 1000); // Give the framework some time to react
char service[64];
snprintf(service, 64, "fuse_%s", getLabel());
property_set("ctl.stop", service);
/* Give it a chance to stop. I wish we had a synchronous way to determine this... */
sleep(1);
// TODO: determine failure mode if FUSE times out
if (providesAsec && doUnmount(Volume::SEC_ASECDIR_EXT, force) != 0) {
SLOGE("Failed to unmount secure area on %s (%s)", getMountpoint(), strerror(errno));
goto out_mounted;
}
/* Now that the fuse daemon is dead, unmount it */
if (doUnmount(getFuseMountpoint(), force) != 0) {
SLOGE("Failed to unmount %s (%s)", getFuseMountpoint(), strerror(errno));
goto fail_remount_secure;
}
/* Unmount the real sd card */
if (doUnmount(getMountpoint(), force) != 0) {
SLOGE("Failed to unmount %s (%s)", getMountpoint(), strerror(errno));
goto fail_remount_secure;
}
SLOGI("%s unmounted successfully", getMountpoint());
/* If this is an encrypted volume, and we've been asked to undo
* the crypto mapping, then revert the dm-crypt mapping, and revert
* the device info to the original values.
*/
if (revert && isDecrypted()) {
cryptfs_revert_volume(getLabel());
revertDeviceInfo();
SLOGI("Encrypted volume %s reverted successfully", getMountpoint());
}
setUuid(NULL);
setUserLabel(NULL);
setState(Volume::State_Idle);
mCurrentlyMountedKdev = -1;
return 0;
fail_remount_secure:
if (providesAsec && mountAsecExternal() != 0) {
SLOGE("Failed to remount secure area (%s)", strerror(errno));
goto out_nomedia;
}
out_mounted:
setState(Volume::State_Mounted);
return -1;
out_nomedia:
setState(Volume::State_NoMedia);
return -1;
}
MountService收到vold的消息,先调用onEvent函数,而unmounting属于VolumeStateChange
public boolean onEvent(int code, String raw, String[] cooked) {
if (DEBUG_EVENTS) {
StringBuilder builder = new StringBuilder();
builder.append("onEvent::");
builder.append(" raw= " + raw);
if (cooked != null) {
builder.append(" cooked = " );
for (String str : cooked) {
builder.append(" " + str);
}
}
Slog.i(TAG, builder.toString());
}
if (code == VoldResponseCode.VolumeStateChange) {
/*
* One of the volumes we're managing has changed state.
* Format: "NNN Volume <label> <path> state changed
* from <old_#> (<old_str>) to <new_#> (<new_str>)"
*/
notifyVolumeStateChange(//调用notifyVolumeStateChange函数
cooked[2], cooked[3], Integer.parseInt(cooked[7]),
Integer.parseInt(cooked[10]));
}
再来看看notifyVolumeStateChange函数:
private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
final StorageVolume volume;
final String state;
synchronized (mVolumesLock) {
volume = mVolumesByPath.get(path);
state = getVolumeState(path);
}
if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state);
String action = null;
if (oldState == VolumeState.Shared && newState != oldState) {
if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL);
}
if (newState == VolumeState.Init) {
} else if (newState == VolumeState.NoMedia) {
// NoMedia is handled via Disk Remove events
} else if (newState == VolumeState.Idle) {
/*
* Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
* if we're in the process of enabling UMS
*/
if (!state.equals(
Environment.MEDIA_BAD_REMOVAL) && !state.equals(
Environment.MEDIA_NOFS) && !state.equals(
Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
action = Intent.ACTION_MEDIA_UNMOUNTED;
}
} else if (newState == VolumeState.Pending) {
} else if (newState == VolumeState.Checking) {
if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
updatePublicVolumeState(volume, Environment.MEDIA_CHECKING);
action = Intent.ACTION_MEDIA_CHECKING;
} else if (newState == VolumeState.Mounted) {
if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
action = Intent.ACTION_MEDIA_MOUNTED;
} else if (newState == VolumeState.Unmounting) {//Unmounting的时候没有updatePublicVolumeState
action = Intent.ACTION_MEDIA_EJECT;
} else if (newState == VolumeState.Formatting) {
} else if (newState == VolumeState.Shared) {
if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
/* Send the media unmounted event first */
updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
updatePublicVolumeState(volume, Environment.MEDIA_SHARED);
action = Intent.ACTION_MEDIA_SHARED;
if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
} else if (newState == VolumeState.SharedMnt) {
Slog.e(TAG, "Live shared mounts not supported yet!");
return;
} else {
Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
}
if (action != null) {
sendStorageIntent(action, volume, UserHandle.ALL);//发送广播给应用,但是我们没有使用广播。而是注册的回调
}
}
我们再来看看updatePublicVolumeState函数:
private void updatePublicVolumeState(StorageVolume volume, String state) {
final String path = volume.getPath();
final String oldState;
synchronized (mVolumesLock) {
oldState = mVolumeStates.put(path, state);
volume.setState(state);
}
if (state.equals(oldState)) {
Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
state, state, path));
return;
}
...........
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);
}
}
}
}
现在的现象是,当用户点击卸载sd卡后,updatePreferencesFromState又被调用了,而且经过验证是setting自己调用的,而非MountService中的回调。
private void updatePreferencesFromState() {
// Only update for physical volumes
if (mVolume == null) return;
mMountTogglePreference.setEnabled(true);
final String state = mStorageManager.getVolumeState(mVolume.getPath());
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
mItemAvailable.setTitle(R.string.memory_available_read_only);
} else {
mItemAvailable.setTitle(R.string.memory_available);
}
if (Environment.MEDIA_MOUNTED.equals(state)
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
mMountTogglePreference.setEnabled(true);//去查询sd卡的状态还是mounted,所以又置亮了。所以出现了卸载过程中,还能卸载的情况
mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject));
mMountTogglePreference.setSummary(mResources.getString(R.string.sd_eject_summary));
addPreference(mUsageBarPreference);
addPreference(mItemTotal);
addPreference(mItemAvailable);
} else {
if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state)
|| Environment.MEDIA_UNMOUNTABLE.equals(state)) {
mMountTogglePreference.setEnabled(true);
mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
mMountTogglePreference.setSummary(mResources.getString(R.string.sd_mount_summary));
} else {
mMountTogglePreference.setEnabled(false);
mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
mMountTogglePreference.setSummary(mResources.getString(R.string.sd_insert_summary));
}
removePreference(mUsageBarPreference);
removePreference(mItemTotal);
removePreference(mItemAvailable);
}
if (mUsbConnected && (UsbManager.USB_FUNCTION_MTP.equals(mUsbFunction) ||
UsbManager.USB_FUNCTION_PTP.equals(mUsbFunction))) {
//mMountTogglePreference.setEnabled(false);
if (Environment.MEDIA_MOUNTED.equals(state)
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
mMountTogglePreference.setSummary(
mResources.getString(R.string.mtp_ptp_mode_summary));
}
if (mFormatPreference != null) {
mFormatPreference.setEnabled(false);
mFormatPreference.setSummary(mResources.getString(R.string.mtp_ptp_mode_summary));
}
} else if (mFormatPreference != null) {
mFormatPreference.setEnabled(mMountTogglePreference.isEnabled());//卸载完后,格式化应该置灰。这边卸载完,变成"安装SD卡"的titiel并且也是亮的,所以格式化也是亮的
mFormatPreference.setSummary(mResources.getString(R.string.sd_format_summary));
}
}
至于这样的两种情况,在MountService的notifyVolumeStateChange函数中增加一个Unmounting状态,在Environment.java中增加Environment.MEDIA_UNMOUNTING
} else if (newState == VolumeState.Unmounting) {
action = Intent.ACTION_MEDIA_EJECT;
Log.d(TAG,"Unmounting");
updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTING);
}
然后在Setting增加如下,在最前面增加一个Memory.STATE_UNMOUNTING状态,如果是这个状态,将sd卡状态和格式化这两项都置灰。
private void updatePreferencesFromState() {
if (mVolume == null) {
return;
}
mMountTogglePreference.setEnabled(true);
final String state = mStorageManager.getVolumeState(mVolume.getPath());
if (mVolume == null || (state != null && state.equals(Memory.STATE_UNMOUNTING))) {
Log.i(TAG, "state return");
mMountTogglePreference.setEnabled(false);
mFormatPreference.setEnabled(false);
mMountTogglePreference.setTitle(mResources.getString(R.string.sd_ejecting_title));
mMountTogglePreference.setSummary(mResources.getString(R.string.sd_ejecting_summary));
return;
}
这样这个问题就解决了。