问题是当我们再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; }