Android 8.1 客制化OTG U盤的挂載路徑名稱
有時候項目需要特殊的或者固定的U盤挂載路徑,可以參考下面的辦法修改!
先大概看一下U盤挂載的過程:
Android 8.1預設U盤是沒有挂載到storage目錄下面的,并且檔案管理裡面也看不到U盤,如果需要能在檔案管理裡面看到U盤,參考我另外一篇部落格:Android 8.1 OTG U盤無法顯示在系統檔案管理的修改
1.當U盤插入之後,會先new一個Disk類,其構造函數會傳入一個參數“eventPath”,源檔案:system/vold/Disk.cpp
Disk::Disk(const std::string& eventPath, dev_t device,
const std::string& nickname, int flags) :
mDevice(device), mSize(-1), mNickname(nickname), mFlags(flags), mCreated(
false), mJustPartitioned(false) {
mId = StringPrintf("disk:%u,%u", major(device), minor(device));
mEventPath = eventPath; //這裡将這個參數eventPath,指派給一個類的私有變量mEventPath
mSysPath = StringPrintf("/sys/%s", eventPath.c_str());
mDevPath = StringPrintf("/dev/block/vold/%s", mId.c_str());
CreateDeviceNode(mDevPath, mDevice);
}
類的私有變量“mEventPath”儲存的是每一個U盤的device路徑,例如“/devices/platform/mt_usb/musb-hdrc.0.auto/usb1/1-1/1-1.4/…”,每個插入的U盤的這個路徑是不一樣的,取決于USB HUB和U盤的挂載端口,使用這個變量可以判斷是不是自己需要的U盤。
2.接下來會new PublicVolume類和VolumeBase類,并執行status_t VolumeBase::create(),源檔案:system/vold/VolumeBase.cpp
status_t VolumeBase::create() {
CHECK(!mCreated);
mCreated = true;
status_t res = doCreate();
notifyEvent(ResponseCode::VolumeCreated,
StringPrintf("%d \"%s\" \"%s\"", mType, mDiskId.c_str(), mPartGuid.c_str()));
setState(State::kUnmounted);
return res;
}
此時狀态是“kUnmounted”。
3.然後執行mount操作,源檔案:system/vold/VolumeBase.cpp;system/vold/PublicVolume.cpp
status_t VolumeBase::mount() {
if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
LOG(WARNING) << getId() << " mount requires state unmounted or unmountable";
return -EBUSY;
}
setState(State::kChecking);
status_t res = doMount();
if (res == OK) {
setState(State::kMounted);
} else {
setState(State::kUnmountable);
}
return res;
}
調用PublicVolume的doMount(),并根據是否mount成功,設定狀态“kMounted”和“kUnmountable”,
status_t PublicVolume::doMount() {
// TODO: expand to support mounting other filesystems
readMetadata();
......
// Use UUID as stable name, if available
std::string stableName = getId(); //此處就是得到挂載路徑的名稱,修改名稱主要就是修改這裡
if (!mFsUuid.empty()) {
stableName = mFsUuid;
}
mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());
mFuseDefault = StringPrintf("/mnt/runtime/default/%s", stableName.c_str());
mFuseRead = StringPrintf("/mnt/runtime/read/%s", stableName.c_str());
mFuseWrite = StringPrintf("/mnt/runtime/write/%s", stableName.c_str());
setInternalPath(mRawPath);
if (getMountFlags() & MountFlags::kVisible) { //此處決定是否需要挂載到storage下面
setPath(StringPrintf("/storage/%s", stableName.c_str()));
} else {
setPath(mRawPath);
}
if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
PLOG(ERROR) << getId() << " failed to create mount points";
return -errno;
}
......
return OK;
}
如果永遠隻會挂載一個U盤,那麼直接修改上面代碼裡面的變量“stableName”為自己想要的名稱就可以了;如果會挂HUB,會有多個U盤,那麼繼續看下面。
4.上面說了,mount可能會成功或者失敗,但是不管是否成功,解除安裝U盤的時候都會執行status_t VolumeBase::destroy(),這個是和status_t VolumeBase::create()相對應的,源檔案:system/vold/VolumeBase.cpp
status_t VolumeBase::destroy() {
CHECK(mCreated);
if (mState == State::kMounted) {
unmount();
setState(State::kBadRemoval);
} else {
setState(State::kRemoved);
}
notifyEvent(ResponseCode::VolumeDestroyed);
status_t res = doDestroy();
mCreated = false;
return res;
}
根據mount狀态,如果是“kMounted”,才執行unmount。
5.執行unmount,源檔案:system/vold/VolumeBase.cpp;system/vold/PublicVolume.cpp
status_t VolumeBase::unmount() {
if (mState != State::kMounted) {
LOG(WARNING) << getId() << " unmount requires state mounted";
return -EBUSY;
}
setState(State::kEjecting);
for (const auto& vol : mVolumes) {
if (vol->destroy()) {
LOG(WARNING) << getId() << " failed to destroy " << vol->getId()
<< " stacked above";
}
}
mVolumes.clear();
status_t res = doUnmount();
setState(State::kUnmounted);
return res;
}
調用PublicVolume的doUnmount(),
status_t PublicVolume::doUnmount() {
// Unmount the storage before we kill the FUSE process. If we kill
// the FUSE process first, most file system operations will return
// ENOTCONN until the unmount completes. This is an exotic and unusual
// error code and might cause broken behaviour in applications.
KillProcessesUsingPath(getPath());
ForceUnmount(kAsecPath);
ForceUnmount(mFuseDefault);
ForceUnmount(mFuseRead);
ForceUnmount(mFuseWrite);
ForceUnmount(mRawPath);
if (mFusePid > 0) {
kill(mFusePid, SIGTERM);
TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, 0));
mFusePid = 0;
}
rmdir(mFuseDefault.c_str());
rmdir(mFuseRead.c_str());
rmdir(mFuseWrite.c_str());
rmdir(mRawPath.c_str());
mFuseDefault.clear();
mFuseRead.clear();
mFuseWrite.clear();
mRawPath.clear();
return OK;
}
以上就是需要用到的大緻U盤挂載流程。
我的實作多個U盤定制挂載路徑的辦法就是,在U盤status_t PublicVolume::doMount()的時候,獲得Disk類的私有變量“mEventPath”的值,然後判斷是否是需要特殊名稱的U盤,如果是需要特殊名稱的U盤,使用統一名稱字首+數字編号,例如“USB1 ~ USB10”,總共支援10個U盤使用特殊名稱,每當需要mount,先配置設定一個名稱編号,并使用一個全局變量數組(自己定義)去标記對應的編号是否已經配置設定,已配置設定的編号,有可能mount成功,有可能失敗,是以要在status_t VolumeBase::destroy()裡面去清除已配置設定的編号的标記。
下面是參考代碼:
status_t PublicVolume::doMount() {
// TODO: expand to support mounting other filesystems
readMetadata();
......
#if 0 //注釋掉這裡先
// Use UUID as stable name, if available
std::string stableName = getId();
if (!mFsUuid.empty()) {
stableName = mFsUuid;
}
#endif
/***************************************************************************/
std::string stableName = getId();
unsigned char i;
const char *Path = getEventPath().data();//這是擷取Disk類私有變量mEventPath的方法,可以自己實作
//下面這個判斷是,主要USB控制器下挂載了一個HUB,然後在這個HUB下面挂載的,任何一個端口号大于1的裝置
//在這裡都需要配置設定特殊的名字,下面字元串的含義,是kernel驅動在sysfs下的device路徑,不同平台不一樣
if( strncmp(Path,"/devices/platform/mt_usb/musb-hdrc.0.auto/usb1/1-1/1-1.1",strlen("/devices/platform/mt_usb/musb-hdrc.0.auto/usb1/1-1/1-1.*")) >= 0 )
{
//在數組中查找沒有使用的挂載路徑編号
for(i = 0; i < 10; i++)
{
//flag變量為0,表示沒有使用
if(MountPointFlag[i] == 0) //這個自己定義的數組需要是全局靜态數組,所有程序都使用同一個數組
break;
}
if(i < 10)
{
stableName = StringPrintf("USB%d", (i + 1));
MountPointFlag[i] = 1; //置1,表示已經配置設定
}
}
/***************************************************************************/
//這裡就是U盤挂載路徑,有可能在/mnt/media_rw下,有可能在/storage下
mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());
mFuseDefault = StringPrintf("/mnt/runtime/default/%s", stableName.c_str());
mFuseRead = StringPrintf("/mnt/runtime/read/%s", stableName.c_str());
mFuseWrite = StringPrintf("/mnt/runtime/write/%s", stableName.c_str());
setInternalPath(mRawPath);
if (getMountFlags() & MountFlags::kVisible) { //判斷是要挂載到哪個目錄下面
setPath(StringPrintf("/storage/%s", stableName.c_str()));
} else {
setPath(mRawPath);
}
//上面這裡代碼有去調用setPath(),這裡的路徑名稱會儲存在VolumeBase類的私有變量“mPath”裡
if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
PLOG(ERROR) << getId() << " failed to create mount points";
return -errno;
}
......
return OK;
}
status_t VolumeBase::destroy() {
CHECK(mCreated);
/***************************************************************************/
const std::string Path = getPath();//從VolumeBase類的私有變量“mPath”裡擷取這個裝置的挂載路徑
//我這裡是讓U盤挂載到storage,是以判斷storage下的名稱
if( strncmp(Path.data(),"/storage/USB",strlen("/storage/USB")) == 0 )
{
//得到路徑名稱編号值
unsigned char clear = (unsigned char)atoi(Path.substr(strlen("/storage/USB")).c_str());
//這裡将自己定義的數組的指針傳過來使用,先判斷有沒有傳過來
if(pMountPointFlag!= NULL)
{
if( (clear > 0) && (clear <= 10) )
pMountPointFlag[clear - 1] = 0; //清0,表示已不在使用
}
}
/***************************************************************************/
if (mState == State::kMounted) {
unmount();
setState(State::kBadRemoval);
} else {
setState(State::kRemoved);
}
notifyEvent(ResponseCode::VolumeDestroyed);
status_t res = doDestroy();
mCreated = false;
return res;
}
以上就是修改辦法和參考代碼!
幾點注意:
1.像SD卡讀卡器插上去的時候,會new Disk類,但是并不會執行mount,我一開始是在Disk的構造函數裡直接判斷“mEventPath”的值,并配置設定的名稱編号,這樣導緻SD讀卡器會占用一個編号,并且不會釋放,是以要在mount的時候,需要名稱的時候再配置設定;同樣,另外一種情況也是,執行了status_t VolumeBase::create(),但是沒有mount;
2.在status_t VolumeBase::destroy()裡面去釋放配置設定的編号,而不是在status_t PublicVolume::doUnmount()裡,因為如果mount不成功,并不會執行unmount,這樣會導緻編号無法釋放;
3.有的時候會出現,程序執行了status_t VolumeBase::create(),接着會執行兩次mount,比如,插了空的SD卡讀卡器之後,再插一個損壞的SD卡,就會出現,這樣就要在mount函數裡去判斷,同一個程序,第一次mount已經配置設定了編号,再去mount,就用之前已經配置設定的編号,不要再去新配置設定了,要不然destroy()的時候,隻會清除最後一次mount配置設定的,之前的無法釋放了!