【輸出文件】 Android MountService 原始碼分析
Android 儲存裝置管理框架
在android之VOLD程序啟動原始碼分析一文中介紹了儲存裝置的管控中心Vold程序,Vold屬於native後臺程序,通過netlink方式接收kernel的uevent訊息,並通過socket方式將uevent訊息傳送給MountService,同時實時接收MountService的命令訊息,MountService,Vold,Kernel三者的關係如下圖所示:
android之VOLD程序啟動原始碼分析一文中介紹了NetlinkManager模組在啟動過程中,建立了一個socket監聽執行緒,用於監聽kernel傳送過來的uevent訊息
MountService作為Android的Java服務之一,在SystemServer程序啟動的第二階段建立並註冊到ServiceManager中,同時長駐於SystemServer程序中,
[java] view plaincopy
MountService mountService = null; if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) { try { /* * NotificationManagerService is dependant on MountService, * so we must start MountService first. */ Slog.i(TAG, "Mount Service"); mountService = new MountService(context); //註冊到ServiceManager中 ServiceManager.addService("mount", mountService); } catch (Throwable e) { reportWtf("starting Mount Service", e); } }
MountService各個類關係圖:
構造MountService物件例項:
[java] view plaincopy
-
public MountService(Context context) { mContext = context; //從xml中讀取儲存裝置列表 readStorageList(); if (mPrimaryVolume != null) { mExternalStoragePath = mPrimaryVolume.getPath(); mEmulateExternalStorage = mPrimaryVolume.isEmulated(); if (mEmulateExternalStorage) { Slog.d(TAG, "using emulated external storage"); mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED); } } //add mount state for inernal storage in NAND if (Environment.getSecondStorageType() == Environment.SECOND_STORAGE_TYPE_NAND) { mVolumeStates.put(Environment.getSecondStorageDirectory().getPath(), Environment.MEDIA_MOUNTED); } // 查詢PackageManagerService服務 mPms = (PackageManagerService) ServiceManager.getService("package"); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BOOT_COMPLETED); // don't bother monitoring USB if mass storage is not supported on our primary volume if (mPrimaryVolume != null && mPrimaryVolume.allowMassStorage()) { filter.addAction(UsbManager.ACTION_USB_STATE); } //註冊開機完成及USB狀態變化廣播接收器 mContext.registerReceiver(mBroadcastReceiver, filter, null, null); //建立並啟動一個帶訊息迴圈的MountService工作執行緒 mHandlerThread = new HandlerThread("MountService"); mHandlerThread.start(); //為MountService工作執行緒建立一個Handler mHandler = new MountServiceHandler(mHandlerThread.getLooper()); //為MountService工作執行緒建立一個ObbActionHandler mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper()); /* * Create the connection to vold with a maximum queue of twice the * amount of containers we'd ever expect to have. This keeps an * "asec list" from blocking a thread repeatedly. */ mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25); //建立並啟動一個socket連線監聽執行緒 Thread thread = new Thread(mConnector, VOLD_TAG); thread.start(); // Add ourself to the Watchdog monitors if enabled. if (WATCHDOG_ENABLE) { Watchdog.getInstance().addMonitor(this); } }
在開始構造MountService前,首先讀取frameworks/base/core/res/res/xml/storage_list.xml檔案,該檔案以XML方式儲存了所有儲存裝置的引數,檔案內容如下所示:
[html] view plaincopy
<StorageList xmlns:android="http://schemas.android.com/apk/res/android">
<!-- removable is not set in nosdcard product -->
<storage android:mountPoint="/mnt/sdcard"
android:storageDescription="@string/storage_usb"
android:primary="true" />
</StorageList>
該檔案的讀取這裡不在介紹,讀者自行研究,使用XML解析器讀取該XML的檔案內容,根據讀取到的儲存裝置引數來構造StorageVolume物件,並將構造的所有StorageVolume物件存放到列表mVolumes中。通過原始碼清晰地知道,在構造MountService時,註冊了一個廣播接收器,用於接收開機完成廣播及USB狀態廣播,當開機完成時自動掛載儲存裝置,在大容量裝置儲存有效情況下,當USB狀態變化也自動地掛載儲存裝置。建立了兩個工作執行緒,MountService執行緒用於訊息迴圈處理,為什麼要開啟一個非同步訊息處理執行緒呢?我們知道大量的Java Service駐留在SystemServer程序中,如果所有的服務訊息都發送到SystemServer的主執行緒中處理的話,主執行緒的負荷很重,訊息不能及時得到處理,因此需為每一個Service開啟一個訊息處理執行緒,專門處理本Service的訊息。如下圖所示:
從上圖可以清晰地看出SystemServer主執行緒啟動MountService服務,該服務啟動時會建立一個MountService帶有訊息迴圈的工作執行緒,用於處理MountServiceHandle和ObbActionHandler分發過來的訊息;同時建立一個用於連線Vold服務端socket的VoldConnector執行緒,該執行緒在進入閉環執行前會建立一個帶有訊息迴圈的VoldConnector.CallbackHandler執行緒,用於處理native層的Vold程序傳送過來的uevent事件訊息;然後向服務端Vold傳送連線請求,得到socket連線後,從該socket中迴圈讀取資料以接收來之服務端Vold的uevent訊息,當讀取的資料長度為0時,向服務端重新發起連線,如此迴圈,保證客戶端MountService與服務端Vold一直保持正常連線。當成功連線到服務端Vold時,VoldConnector執行緒會建立一個MountService#onDaemonConnected執行緒,用於處理本次連線請求響應。
[java] view plaincopy
-
mHandlerThread = new HandlerThread("MountService"); mHandlerThread.start(); mHandler = new MountServiceHandler(mHandlerThread.getLooper()); // Add OBB Action Handler to MountService thread. mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
MountServiceHandler訊息處理:
[java] view plaincopy
public void handleMessage(Message msg) {
switch (msg.what) {
case H_UNMOUNT_PM_UPDATE: {
UnmountCallBack ucb = (UnmountCallBack) msg.obj;
mForceUnmounts.add(ucb);
// Register only if needed.
if (!mUpdatingStatus) {
if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
mUpdatingStatus = true;
mPms.updateExternalMediaStatus(false, true);
}
break;
}
case H_UNMOUNT_PM_DONE: {
mUpdatingStatus = false;
int size = mForceUnmounts.size();
int sizeArr[] = new int[size];
int sizeArrN = 0;
// Kill processes holding references first
ActivityManagerService ams = (ActivityManagerService)
ServiceManager.getService("activity");
for (int i = 0; i < size; i++) {
UnmountCallBack ucb = mForceUnmounts.get(i);
String path = ucb.path;
boolean done = false;
if (!ucb.force) {
done = true;
} else {
int pids[] = getStorageUsers(path);
if (pids == null || pids.length == 0) {
done = true;
} else {
// Eliminate system process here?
ams.killPids(pids, "unmount media", true);
// Confirm if file references have been freed.
pids = getStorageUsers(path);
if (pids == null || pids.length == 0) {
done = true;
}
}
}
if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
// Retry again
Slog.i(TAG, "Retrying to kill storage users again");
mHandler.sendMessageDelayed(mHandler.obtainMessage(H_UNMOUNT_PM_DONE,ucb.retries++),RETRY_UNMOUNT_DELAY);
} else {
if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
Slog.i(TAG, "Failed to unmount media inspite of " +
MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
}
sizeArr[sizeArrN++] = i;
mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,ucb));
}
}
// Remove already processed elements from list.
for (int i = (sizeArrN-1); i >= 0; i--) {
mForceUnmounts.remove(sizeArr[i]);
}
break;
}
case H_UNMOUNT_MS: {
UnmountCallBack ucb = (UnmountCallBack) msg.obj;
ucb.handleFinished();
break;
}
}
}
MountServiceHandler分別對H_UNMOUNT_PM_UPDATE,H_UNMOUNT_PM_DONE,H_UNMOUNT_MS
[java] view plaincopy
Vold作為儲存裝置的管控中心,需要接收來自上層MountService的操作命令,MountService駐留在SystemServer程序中,和Vold作為兩個不同的程序,它們之間的通訊方式採用的是socket通訊,在android之VOLD程序啟動原始碼分析一文中介紹了CommandListener模組啟動了一個socket監聽執行緒,用於專門接收來之上層MountService的連線請求[p1] 。而在MountService這端,同樣啟動了VoldConnector socket連線執行緒,用於迴圈連線服務端,保證連線不被中斷,當成功連線Vold時,迴圈從服務端讀取資料。MountService按照指定格式向Vold傳送命令,由於傳送的命令比較多,這裡不做一一接收,只對其中的mount命令的傳送流程進行介紹:
從以上的時序圖可以看出,MountService對命令的傳送首先是呼叫makeCommand來組合成指定格式的命令[p2] ,然後直接寫入到Vold socket即可,整個命令傳送流程比較簡單。
[java] view plaincopy
-
public int mountVolume(String path) { //許可權檢驗 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); return doMountVolume(path); }
呼叫doMountVolume函式來掛載儲存裝置:
[java] view plaincopy
-
private int doMountVolume(String path) { int rc = StorageResultCode.OperationSucceeded; try { //命令交給NativeDaemonConnector去傳送 mConnector.execute("volume", "mount", path); } catch (NativeDaemonConnectorException e) { //捕獲命令傳送的異常,根據異常碼來決定傳送失敗的原因 String action = null; int code = e.getCode(); if (code == VoldResponseCode.OpFailedNoMedia) { /* * Attempt to mount but no media inserted */ rc = StorageResultCode.OperationFailedNoMedia; } else if (code == VoldResponseCode.OpFailedMediaBlank) { if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs"); /* * Media is blank or does not contain a supported filesystem */ updatePublicVolumeState(path, Environment.MEDIA_NOFS); action = Intent.ACTION_MEDIA_NOFS; rc = StorageResultCode.OperationFailedMediaBlank; } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt"); /* * Volume consistency check failed */ updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE); action = Intent.ACTION_MEDIA_UNMOUNTABLE; rc = StorageResultCode.OperationFailedMediaCorrupt; } else { rc = StorageResultCode.OperationFailedInternalError; } /* * Send broadcast intent (if required for the failure) */ if (action != null) { sendStorageIntent(action, path); } } return rc; }
NativeDaemonConnector命令傳送:
[java] view plaincopy
-
public NativeDaemonEvent execute(String cmd, Object... args) throws NativeDaemonConnectorException { //使用executeForList函式來發送命令和命令引數,並返回一組NativeDaemonEvent事件 final NativeDaemonEvent[] events = executeForList(cmd, args); if (events.length != 1) { throw new NativeDaemonConnectorException("Expected exactly one response, but received " + events.length); } return events[0]; }
呼叫executeForList來發送命令和命令引數,並在這裡設定超時時間:
[java] view plaincopy
-
public NativeDaemonEvent[] executeForList(String cmd, Object... args) throws NativeDaemonConnectorException { //設定超時時間:DEFAULT_TIMEOUT = 1 * 60 * 1000 return execute(DEFAULT_TIMEOUT, cmd, args); }
真正命令傳送:
[java] view plaincopy
-
public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args) throws NativeDaemonConnectorException { final ArrayList<NativeDaemonEvent> events = Lists.newArrayList(); final int sequenceNumber = mSequenceNumber.incrementAndGet(); final StringBuilder cmdBuilder = new StringBuilder(Integer.toString(sequenceNumber)).append(' '); //傳送起始時間 final long startTime = SystemClock.elapsedRealtime(); //命令組合 makeCommand(cmdBuilder, cmd, args); final String logCmd = cmdBuilder.toString(); /* includes cmdNum, cmd, args */ log("SND -> {" + logCmd + "}"); //SND -> {8 volume mount /storage/sdcard1} cmdBuilder.append('\0'); final String sentCmd = cmdBuilder.toString(); /* logCmd + \0 */ synchronized (mDaemonLock) { if (mOutputStream == null) { throw new NativeDaemonConnectorException("missing output stream"); } else { try { //向socket中寫入命令 mOutputStream.write(sentCmd.getBytes(Charsets.UTF_8)); } catch (IOException e) { throw new NativeDaemonConnectorException("problem sending command", e); } } } NativeDaemonEvent event = null; do { event = mResponseQueue.remove(sequenceNumber, timeout, sentCmd); if (event == null) { loge("timed-out waiting for response to " + logCmd); throw new NativeDaemonFailureException(logCmd, event); } log("RMV <- {" + event + "}"); events.add(event); } while (event.isClassContinue()); //傳送結束時間 final long endTime = SystemClock.elapsedRealtime(); if (endTime - startTime > WARN_EXECUTE_DELAY_MS) { loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)"); } if (event.isClassClientError()) { throw new NativeDaemonArgumentException(logCmd, event); } if (event.isClassServerError()) { throw new NativeDaemonFailureException(logCmd, event); } return events.toArray(new NativeDaemonEvent[events.size()]); }
MountService訊息接收流程[p3]
MountService需要接收兩種型別的訊息:
1)當外部儲存裝置發生熱插拔時,kernel將通過netlink方式通知Vold,Vold程序經過一系列處理後最終還是要叫uevent事件訊息傳送給MountService,Vold傳送uevent的過程已經在android之VOLD程序啟動原始碼分析一文中詳細介紹了;
2)當MountService向Vold傳送命令後,將接收到Vold的響應訊息;
無論是何種型別的訊息,MountService都是通過VoldConnector執行緒來迴圈接收Vold的請求,整個訊息接收流程如下:
[java] view plaincopy
-
public void run() { //建立並啟動VoldConnector.CallbackHandler執行緒,用於處理native層的Vold程序傳送過來的uevent事件訊息 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler"); thread.start(); //為VoldConnector.CallbackHandler執行緒建立一個Handler,用於向該執行緒分發訊息 mCallbackHandler = new Handler(thread.getLooper(), this); //進入閉環socket連線模式 while (true) { try { listenToSocket(); } catch (Exception e) { loge("Error in NativeDaemonConnector: " + e); SystemClock.sleep(5000); } } }
連線服務端Socket,並讀取資料:
[java] view plaincopy
-
private void listenToSocket() throws IOException { LocalSocket socket = null; try { //建立Vold socket socket = new LocalSocket(); LocalSocketAddress address = new LocalSocketAddress(mSocket,LocalSocketAddress.Namespace.RESERVED); //向服務端發起連線請求 socket.connect(address); //從連線的socket中得到輸入輸出流 InputStream inputStream = socket.getInputStream(); synchronized (mDaemonLock) { mOutputStream = socket.getOutputStream(); } //對本次連線請求做一些回撥處理 mCallbacks.onDaemonConnected(); //定義buffer byte[] buffer = new byte[BUFFER_SIZE]; int start = 0; //進入閉環資料讀取模式 while (true) { int count = inputStream.read(buffer, start, BUFFER_SIZE - start); //當讀取的資料長度小於0時,表示連線已斷開,跳出迴圈,重新向服務端發起新的連線請求 if (count < 0) { loge("got " + count + " reading with start = " + start); break; } // Add our starting point to the count and reset the start. count += start; start = 0; //解析讀取到的資料,得到NativeDaemonEvent for (int i = 0; i < count; i++) { if (buffer[i] == 0) { final String rawEvent = new String(buffer, start, i - start, Charsets.UTF_8); //RCV <- {632 Volume sdcard /storage/sdcard1 bad removal (179:1)} log("RCV <- {" + rawEvent + "}"); try { final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(rawEvent); //如果命令碼code >= 600 && code < 700 if (event.isClassUnsolicited()) { //將讀取到的事件傳送到VoldConnector.CallbackHandler執行緒中處理 mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage( event.getCode(), event.getRawEvent())); //否則將改事件新增到響應佇列中 } else { mResponseQueue.add(event.getCmdNumber(), event); } } catch (IllegalArgumentException e) { log("Problem parsing message: " + rawEvent + " - " + e); } start = i + 1; } } if (start == 0) { final String rawEvent = new String(buffer, start, count, Charsets.UTF_8); log("RCV incomplete <- {" + rawEvent + "}"); } // We should end at the amount we read. If not, compact then // buffer and read again. if (start != count) { final int remaining = BUFFER_SIZE - start; System.arraycopy(buffer, start, buffer, 0, remaining); start = remaining; } else { start = 0; } } } catch (IOException ex) { loge("Communications error: " + ex); throw ex; } finally { synchronized (mDaemonLock) { if (mOutputStream != null) { try { loge("closing stream for " + mSocket); mOutputStream.close(); } catch (IOException e) { loge("Failed closing output stream: " + e); } mOutputStream = null; } } try { if (socket != null) { socket.close(); } } catch (IOException ex) { loge("Failed closing socket: " + ex); } } }
[java] view plaincopy
-
public void onDaemonConnected() { //建立一個工作執行緒 new Thread("MountService#onDaemonConnected") { @Override public void run() { /** * Determine media state and UMS detection status */ try { //向vold查詢所有的儲存裝置 final String[] vols = NativeDaemonEvent.filterMessageList( mConnector.executeForList("volume", "list"), VoldResponseCode.VolumeListResult); //判斷儲存裝置狀態 for (String volstr : vols) { String[] tok = volstr.split(" "); // FMT: <label> <mountpoint> <state> String path = tok[1]; String state = Environment.MEDIA_REMOVED; int st = Integer.parseInt(tok[2]); if (st == VolumeState.NoMedia) { state = Environment.MEDIA_REMOVED; } else if (st == VolumeState.Idle) { state = Environment.MEDIA_UNMOUNTED; } else if (st == VolumeState.Mounted) { state = Environment.MEDIA_MOUNTED; Slog.i(TAG, "Media already mounted on daemon connection"); } else if (st == VolumeState.Shared) { state = Environment.MEDIA_SHARED; Slog.i(TAG, "Media shared on daemon connection"); } else { throw new Exception(String.format("Unexpected state %d", st)); } if (state != null) { if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state); //更新Volume狀態 updatePublicVolumeState(path, state); } } } catch (Exception e) { Slog.e(TAG, "Error processing initial volume state", e); updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED); } /* * Now that we've done our initialization, release * the hounds! */ mConnectedSignal.countDown(); mConnectedSignal = null; // 使用PackageManagerService掃描外邊儲存裝置上的APK資訊 mPms.scanAvailableAsecs(); // Notify people waiting for ASECs to be scanned that it's done. mAsecsScanned.countDown(); mAsecsScanned = null; } }.start(); }
儲存裝置狀態更新:
[java] view plaincopy
-
private boolean updatePublicVolumeState(String path, String state) { String oldState; synchronized(mVolumeStates) { oldState = mVolumeStates.put(path, state); } if (state.equals(oldState)) { Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",state, state, path)); return false; } Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")"); if (path.equals(mExternalStoragePath)) { // Update state on PackageManager, but only of real events if (!mEmulateExternalStorage) { if (Environment.MEDIA_UNMOUNTED.equals(state)) { mPms.updateExternalMediaStatus(false, false); /* * Some OBBs might have been unmounted when this volume was * unmounted, so send a message to the handler to let it know to * remove those from the list of mounted OBBS. */ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE, path)); } else if (Environment.MEDIA_MOUNTED.equals(state)) { mPms.updateExternalMediaStatus(true, false); } } } synchronized (mListeners) { for (int i = mListeners.size() -1; i >= 0; i--) { MountServiceBinderListener bl = mListeners.get(i); try { //呼叫已註冊的MountServiceBinderListener來通知儲存裝置狀態改變 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); } } } return true; }
MountServiceBinderListener的註冊過程:
[java] view plaincopy
-
public void registerListener(IMountServiceListener listener) { synchronized (mListeners) { MountServiceBinderListener bl = new MountServiceBinderListener(listener); try { listener.asBinder().linkToDeath(bl, 0); mListeners.add(bl); } catch (RemoteException rex) { Slog.e(TAG, "Failed to link to listener death"); } } }
使用StorageManager的內部類MountServiceBinderListener物件來構造MountService的內部類MountServiceBinderListener物件,並新增到MountService的成員變數mListeners列表中。StorageManager的內部類MountServiceBinderListener定義如下:
[java] view plaincopy
private c