从所有的Nexus手机和平板都没有MicroSd卡槽,可以知道谷歌并不喜欢在Android设备上使用外部存储,从软件工程师的角度来讲是可以理解的,但是主流消费者明显有更强烈的需求。在Android M系统中,谷歌观念发生了改变,终于提供了官方的方式让用户将应用移动到外部存储上,其新功能名字叫“Adoptable Storage Devices”。
Adoptable Storage Devices使系统获得了更大的存储容量,并且使用户可以自由移动应用和隐私数据到外部存储设备,不在需要第三方工具。用户使用Android设备时,可以主动的选择把外部存储设备格式化为常规移动存储或者系统内部存储。在格式化时系统给了两种选择:(1)Use as portable storage(for moving photos and other media between divices),仅仅作为外部存储设备;(2)Use as internal storage(for storing anything on this device only,including apps and photos),包裹一个加密层后,能够作为内部存储空间存储应用和隐私数据,经过加密后,该存储设备就不能再被别的设备使用。
本节主要介绍从framework层到vold层的过程。主要涉及类如下:
StorageSettings–>StorageWizardInit–>StorageWizardFormatConfirm–>StorageWizardFormatProgress–>MountService–>NativeDaemonConnector–>commandlistener.cpp
3.1 当需要格式化时,用户进入格式化选择界面(StorageWizardInit类),在StorageSettings:: onPreferenceTreeClick()中DiskInitFragment.show(this, R.string.storage_dialog_unmountable, vol.getDiskId())将触发格式化选择界面。相关代码如下:
public static class DiskInitFragment extends DialogFragment {
…
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
…
builder.setPositiveButton(R.string.storage_menu_set_up,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final Intent intent = new Intent(context, StorageWizardInit.class);
intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId);
startActivity(intent);//当用户点击Set Up,转到格式化选择界面
}
});
builder.setNegativeButton(R.string.cancel, null);
return builder.create();
}
}
3.2 从StorageWizardInit::Oncreate()中代码setIllustrationInternal(true)可以知道,谷歌处理时是先默认为内部存储(internal storage)的,用户选择后再覆盖默认类型。通过Intent传递StorageWizardFormatConfirm.EXTRA_FORMAT_PRIVATE参数,告知格式化为外部或者内部存储。相关代码如下:
@Override
public void onNavigateNext() {
if (mRadioExternal.isChecked()) {//用户选择格式化为外部移动存储
if (mVolume != null && mVolume.getType() == VolumeInfo.TYPE_PUBLIC
&& mVolume.getState() != VolumeInfo.STATE_UNMOUNTABLE) {
// Remember that user made decision
mStorage.setVolumeInited(mVolume.getFsUuid(), true);
final Intent intent = new Intent(this, StorageWizardReady.class);
intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
startActivity(intent);
} else {
// Gotta format to get there
final Intent intent = new Intent(this, StorageWizardFormatConfirm.class);
intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
intent.putExtra(StorageWizardFormatConfirm.EXTRA_FORMAT_PRIVATE, false);//false,格式化为外部存储
startActivity(intent);
}
} else if (mRadioInternal.isChecked()) {//用户选择格式化为内部存储
final Intent intent = new Intent(this, StorageWizardFormatConfirm.class);
intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
intent.putExtra(StorageWizardFormatConfirm.EXTRA_FORMAT_PRIVATE, true);//true,格式化为内部存储
startActivity(intent);
}
}
mFormatPrivate = getIntent().getBooleanExtra(EXTRA_FORMAT_PRIVATE, false);
if (mFormatPrivate) {
setHeaderText(R.string.storage_wizard_format_confirm_title);
setBodyText(R.string.storage_wizard_format_confirm_body, mDisk.getDescription());//设置内部存储的相关显示String
} else {
setHeaderText(R.string.storage_wizard_format_confirm_public_title);
setBodyText(R.string.storage_wizard_format_confirm_public_body, mDisk.getDescription());设置外部存储的相关显示String
}
当用户点击Next时,重写onNavigateNext()方法,跳转到格式化进程显示界面。
public void onNavigateNext() {
final Intent intent = new Intent(this, StorageWizardFormatProgress.class);
intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
intent.putExtra(EXTRA_FORMAT_PRIVATE, mFormatPrivate);
intent.putExtra(EXTRA_FORGET_UUID, getIntent().getStringExtra(EXTRA_FORGET_UUID));
startActivity(intent);//跳转并传递mFormatPrivate到格式化进程界面
finishAffinity();
}
public static class PartitionTask extends AsyncTask {
...
protected Exception doInBackground(Void... params) {
final StorageWizardFormatProgress activity = mActivity;
final StorageManager storage = mActivity.mStorage;
try {
if (activity.mFormatPrivate) {
storage.partitionPrivate(activity.mDisk.getId());//内部存储
...
} else {
storage.partitionPublic(activity.mDisk.getId());//外部存储
}
...
}
}
}
3.5 MountService:partitionPrivate(…)中mConnector.execute(“volume”, “partition”, diskId, “private”)发送格式化为内部存储的意图到NativeDaemonConnector, partitionPublic(…)中mConnector.execute(“volume”, “partition”,diskId, “public”)发送格式化为外部存储的意图到NativeDaemonConnector。
3.6 NativeDaemonConnector通过socket将MountSercvice中的指令发送到下层vold中。
3.7 vold层在CommandListener.cpp中Socket监听到FrameWork层发送的指令后,执行相应的操作,这就是Adoptable Storage Devices功能就从FrameWork层到Vold。代码如下:
int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
...
if (cmd == "reset") {
...
} else if (cmd == "partition" && argc > 3) {
// partition [diskId] [public|private|mixed] [ratio]
std::string id(argv[2]);
auto disk = vm->findDisk(id);
if (disk == nullptr) {
return cli->sendMsg(ResponseCode::CommandSyntaxError,"Unknown disk", false);
}
std::string type(argv[3]);
if (type == "public") {
return sendGenericOkFail(cli, disk->partitionPublic());//外部存储
} else if (type == "private") {
return sendGenericOkFail(cli, disk->partitionPrivate());//内部存储
} else if (type == "mixed") {
if (argc < 4) {
return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false);
}
int frac = atoi(argv[4]);
return sendGenericOkFail(cli, disk->partitionMixed(frac));
} else {
return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false);
}
}
...
}