Android-Vold, Framework和UI的通訊-大結局(12)

來源:互聯網
上載者:User

作者: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.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.