版权声明:本文为博主原创文章,未经博主允许不得转载。
问题是当我们再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;
- }
这样这个问题就解决了。