作者:gzshun. 原創作品,轉載請標明出處!
來源:http://blog.csdn.net/gzshun
寫了好幾篇文章,花兒都快謝了,終於輪到Framework發揮領導的身份了,Framework作為介面與vold之間的橋樑,使用者是Framework的大爺,那麼Framework就是Vold的大哥大,Framework發出一個命令,Vold不敢不遵從,只能照著Framework的意思照辦,千萬不能出差錯,不然Android就要丟大臉了,登不上三國爭霸的舞台,iOS VS Android VS WP7?
在Framework裡面,有一個目錄是用來存放一些Java的系統服務,這些都在後台跑著,在:/android-2.2r2/frameworks/base/services/java/com/android/server目錄下,比較重要的是這兩個源檔案:MountService.java和NativeDaemonConnector.java。
這裡先列出在Vold中,VolumeCmd類處理的一些磁碟操作命令,這些命令均是有Framework下發的:
1.volume list:Framework先得到系統目前存在幾個Volume對象,需要擷取到這些對象的標籤;
2.volume debug:設定USB偵錯模式
3.volume mount sdcard:掛載SD卡
4.volume unmount force:卸載SD卡
5.volume format sdcard:格式化SD卡
6.volume share sdcard ums:開啟SD卡的OTG功能(大型存放區),也就是串連電腦
7.volume unshare sdcard ums:關閉SD卡的OTG功能(大型存放區)
8.volume shared sdcard ums:擷取目前OTG的開啟狀態,就是是否串連電腦的狀態。
以下分別列出每個命令的下發函數,對Java不熟,但看得懂程式的流程,真是慚愧啊。
一、Framework磁碟管理服務的開啟?
在NativeDaemonConnector服務裡面,開始監聽底層Vold發送過來的磁碟熱插拔事件的狀態資訊,當收到底層廣播上來的狀態,調用MountService服務中的onDaemonConnected函數進行處理,當然這是開機第一次去擷取資訊的,也就是下發"volume list"命令。
public void run() { while (true) { try { /*開始監聽底層廣播資訊*/ listenToSocket(); } catch (Exception e) { Slog.e(TAG, "Error in NativeDaemonConnector", e); SystemClock.sleep(5000); } }}private void listenToSocket() throws IOException { /*函數太長,以下是執行順序*/ //串連SOCKET socket.connect(address); -> /*調用該函數來處理下發"volume list"命令的反饋結果*/ mCallbacks.onDaemonConnected(); -> /*處理磁碟的狀態*/ mCallbacks.onEvent(code, event, tokens);}
以上兩個比較重要的函數在MountService當中,處理相當多的內容,源碼太長。
下發volume list命令,Framework收到反饋值,將調用onDaemonConnected函數擷取到了磁碟的標籤,掛載點與狀態,然後調用doGetShareMethodAvailable函數判斷現在是否串連OTG,若串連OTG,那麼調用doShareUnshareVolume函數下發otg串連命令(volume share sdcard ums)。
二、Vold與Framework如何通訊?
onEvent主要是處理狀態資訊的解析,將每一種狀態進行判斷,並調用相應的操作函數。比如此時vold發送一個VolumeDiskInserted狀態,意味著系統插入一個磁碟,於是onEvent就調用doMountVolume掛載函數進行下發命令(volume mount sdcard)。
在系統使用當中,使用者可能會插入,移除,掛載,卸載,格式化磁碟,那麼這兒多狀態如何告訴Framework呢?之前已經說過,vold使用了setState函數來廣播磁碟的狀態訊息,使得Framework能夠及時地判斷下發什麼命令與操作。該函數在Volume.cpp源檔案中,先貼出setState源碼看看:
void Volume::setState(int state) { char msg[255]; int oldState = mState; if (oldState == state) { SLOGW("Duplicate state (%d)\n", state); return; } mState = state; SLOGD("Volume %s state changing %d (%s) -> %d (%s)", mLabel, oldState, stateToStr(oldState), mState, stateToStr(mState)); snprintf(msg, sizeof(msg), "Volume %s %s state changed from %d (%s) to %d (%s)", getLabel(), getMountpoint(), oldState, stateToStr(oldState), mState, stateToStr(mState)); mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeStateChange, msg, false);}
可以看到,setState函數將磁碟的label,mountpoint,oldstate,statestr,newstate,statestr訊息通知給Framework,這樣Framework就知道vold中SD卡的新舊狀態。然後調用通過SocketListener類繼承下來的sendBroadcast函數廣播訊息,反饋碼是ResponseCode::VolumeStateChange,代表狀態改變的訊息。
這裡可能有個疑問,vold廣播這麼多訊息,Framework是如何分清哪條訊息是代表哪一類的反饋訊息呢?
廣播訊息在開頭使用了ResponseCode類提供的一些狀態反饋碼,每一類訊息都用一個反饋碼,這樣Framework的MountService服務能夠很快的判斷出類型。之前的文章說過了,這裡列出幾個重要的反饋碼:
static const int VolumeStateChange = 605;//磁碟狀態改變的反饋碼static const int ShareAvailabilityChange = 620;//OTG狀態改變的反饋碼static const int VolumeDiskInserted = 630;//插入磁碟的反饋碼static const int VolumeDiskRemoved = 631;//移除磁碟的反饋碼static const int VolumeBadRemoval = 632;//沒有安全刪除地移除磁碟的反饋碼。
setState函數只負責磁碟狀態改變的廣播,其他插入磁碟或者移除磁碟的都是直接調用sendBroadcast函數來廣播,這裡貼出插拔事件的廣播函數:
void DirectVolume::broadcastDiskAdded(){ setState(Volume::State_Idle); char msg[255]; snprintf(msg, sizeof(msg), "Volume %s %s disk inserted (%d:%d)", getLabel(), getMountpoint(), mDiskMajor, mDiskMinor); mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeDiskInserted, msg, false);}
三、下發操作命令?
volume mount sdcard:
public int mountVolume(String path) { validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); return doMountVolume(path);}
volume unmount force:
public void unmountVolume(String path, boolean force) { validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); String volState = getVolumeState(path); if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path + " force = " + force); if (Environment.MEDIA_UNMOUNTED.equals(volState) || Environment.MEDIA_REMOVED.equals(volState) || Environment.MEDIA_SHARED.equals(volState) || Environment.MEDIA_UNMOUNTABLE.equals(volState)) { // Media already unmounted or cannot be unmounted. // TODO return valid return code when adding observer call back. return; } UnmountCallBack ucb = new UnmountCallBack(path, force); mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));}
volume format sdcard:
public int formatVolume(String path) { validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); waitForReady(); return doFormatVolume(path);}
volume share sdcard ums
volume unshare sdcard ums:
private void doShareUnshareVolume(String path, String method, boolean enable) { // TODO: Add support for multiple share methods if (!method.equals("ums")) { throw new IllegalArgumentException(String.format("Method %s not supported", method)); } try { mConnector.doCommand(String.format( "volume %sshare %s %s", (enable ? "" : "un"), path, method)); } catch (NativeDaemonConnectorException e) { Slog.e(TAG, "Failed to share/unshare", e); }}
volume shared sdcard ums:
private boolean doGetVolumeShared(String path, String method) { String cmd = String.format("volume shared %s %s", path, method); ArrayList<String> rsp; try { rsp = mConnector.doCommand(cmd); } catch (NativeDaemonConnectorException ex) { Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); return false; } for (String line : rsp) { String[] tok = line.split(" "); if (tok.length < 3) { Slog.e(TAG, "Malformed response to volume shared " + path + " " + method + " command"); return false; } int code; try { code = Integer.parseInt(tok[0]); } catch (NumberFormatException nfe) { Slog.e(TAG, String.format("Error parsing code %s", tok[0])); return false; } if (code == VoldResponseCode.ShareEnabledResult) { return "enabled".equals(tok[2]); } else { Slog.e(TAG, String.format("Unexpected response code %d", code)); return false; } } Slog.e(TAG, "Got an empty response"); return false;}
在Framework中的磁碟管理部分,也涉及到很多代碼,源碼太多,只貼出比較重要的功能模組代碼。
四、UI的處理
從vold走到了Framework,最後一層就是UI,是使用者操作磁碟的介面。有Android手機的哥們兒都知道,在設定裡面可以掛載,卸載與格式化SD卡。當然這圖形介面不是咱擅長的,需要Java的功底與XML,那邊主要就是介面的實現。
UI的源碼路徑是:/android-2.2r2/packages/apps/Settings/src/com/android/settings/deviceinfo/Memory.java
我們可以發現,在Android手機的設定介面,或者首頁面,只要插入SD卡或者移除SD卡,都會有相應的提示,
1.這些磁碟狀態是如何與UI通訊的呢?
在MountService服務中,每次改變狀態,都會調用updatePublicVolumeState函數,從意思上看,可以理解成:更新一個公用的磁碟狀態。這個函數就是起到了這麼一個作用,這會設定到Environment類中的一個變數中,這樣UI就能夠取到磁碟的狀態。
2.UI是如何調用Framework中的函數的?
前面有提到過,UI想要調用Framework中的MountService服務中的函數,比如以註冊的方式來得到叫用作業磁碟函數的許可權。以下貼出UI中,Memory.java源碼中註冊MountService磁碟操作函數的方法:
private synchronized IMountService getMountService() { if (mMountService == null) { /*調用了getService函數來註冊"mount"服務的操作許可權*/ IBinder service = ServiceManager.getService("mount"); if (service != null) { mMountService = IMountService.Stub.asInterface(service); } else { Log.e(TAG, "Can't get mount service"); } } return mMountService;}
來看一個掛載SD卡的操作函數,就知道如何來調用Framework系統服務的函數:
private void mount() { /*先註冊*/ IMountService mountService = getMountService(); try { if (mountService != null) { /*掛載SD卡*/ mountService.mountVolume(Environment.getExternalStorageDirectory().toString()); } else { Log.e(TAG, "Mount service is null, can't mount"); } } catch (RemoteException ex) { }}
3.UI擷取SD卡的目前狀態
在第一點已經解釋過了,這裡再仔細的說明下:
SD卡的狀態是這樣擷取到的:
Vold (setState)-->
MountService (onEvent)-->
MountService (updatePublicVolumeState)-->
UI (getExternalStorageState)
Vold調用setState函數廣播SD卡的狀態,Framework的MountService服務通過onEvent函數收到該狀態訊息,調用updatePublicVolumeState函數設定到Environment中的一個變數中,UI再通過Environment.getExternalStorageState函數擷取到最新狀態,於是UI調用updateMemoryStatus函數將最新狀態設定到介面,這樣使用者就能看到狀態的改變。
(使用者都會發現,掛載或卸載SD卡,狀態由掛載變為卸載需要一小段時間,這中間就是經過這些處理來得到的,包括上層發送命令->底層解析命令調用相應函數->操作完成後發送操作結果->最後將SD卡的最新狀態廣播給Framework,並設定到UI。這中間涉及到很多東西,再加上Java虛擬機器的速度,於是介面就有一個停頓的時間)。
以下貼出UI狀態的更新代碼:
private void updateMemoryStatus() { String status = Environment.getExternalStorageState(); String readOnly = ""; if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) { status = Environment.MEDIA_MOUNTED; readOnly = mRes.getString(R.string.read_only); } mSdFormat.setEnabled(false); if (status.equals(Environment.MEDIA_MOUNTED)) { try { File path = Environment.getExternalStorageDirectory(); StatFs stat = new StatFs(path.getPath()); long blockSize = stat.getBlockSize(); long totalBlocks = stat.getBlockCount(); long availableBlocks = stat.getAvailableBlocks(); mSdSize.setSummary(formatSize(totalBlocks * blockSize)); mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly); mSdMountToggle.setEnabled(true); mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject)); mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary)); } catch (IllegalArgumentException e) { // this can occur if the SD card is removed, but we haven't received the // ACTION_MEDIA_REMOVED Intent yet. status = Environment.MEDIA_REMOVED; } } else { mSdSize.setSummary(mRes.getString(R.string.sd_unavailable)); mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable)); if (status.equals(Environment.MEDIA_UNMOUNTED) || status.equals(Environment.MEDIA_NOFS) || status.equals(Environment.MEDIA_UNMOUNTABLE) ) { mSdFormat.setEnabled(true); mSdMountToggle.setEnabled(true); mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount)); mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary)); } else { mSdMountToggle.setEnabled(false); mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount)); mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary)); } } File path = Environment.getDataDirectory(); StatFs stat = new StatFs(path.getPath()); long blockSize = stat.getBlockSize(); long availableBlocks = stat.getAvailableBlocks(); findPreference("memory_internal_avail").setSummary(formatSize(availableBlocks * blockSize));}
五、好聚好散
終於將Android系統磁碟管理的這部分給over了,現在我深深地敬佩系統設計人員,他們都是非凡的人才,再次感謝Google對Android社區的貢獻。先不說Google有沒有對Android的開源,或者一些專利的事,但在我眼裡,Google是非常強大的公司,向他學習,沒錯的。
三爭IT天下: Google VS Microsoft VS Apple.
三分智能領域:Android VS WP7 VS iOS.