Android 2.3 SD卡挂载流程浅析(五)

时间:2021-12-13 18:21:43
     前面四篇博文: 《Android 2.3 SD卡挂载流程浅析(一)》《Android 2.3 SD卡挂载流程浅析(二)》《Android 2.3 SD卡挂载流程浅析(三)》《Android 2.3 SD卡挂载流程浅析(四)》主要是对SD卡的挂载流程从底到上的一个分析,本文将继续接着 《Android 2.3 SD卡挂载流程浅析(四)》文章分析,前文主要分析了C/C++的一些代码,本文将主要分析Java代码。废话不多说,依然上这张老图:

Android 2.3 SD卡挂载流程浅析(五)

       图中绿色箭头表示的就是SD卡挂载消息从底向上传递的一个流程。本文主要是分析红色箭头的传递了,因为现在消息要在上层反应出来,这里是从VolumeManager开始分析,我们把从Mount SD/USB到VolumeManager之间的流程总体当作Vold来讲,也就是Vold向上层反馈SD卡挂载的消息。

       上文我们分析到,SD卡被doMount方法执行挂载了,该消息由setState方法将消息传递到上层,setState是通过发送一个广播,这里所说的广播不是Android中的BroadCast,这里实际山是Socket,上层负责监听这个Socket,并解析其中的内容。我们需要从MountService.java开始查找。这里我要解释以下为什么要从这里开始找,不是说一开始我就知道这个类里面有我们需要的东西,这是在查找SD卡挂载过程的时候,通过不同的线索联系起来的。那我们先来看看MountService吧。

       MountService

       位于AndroidSourcecode2.3/frameworks/base/services/java/com/android/server/MountService.java

       MountService是一个服务类,通过ServiceManager注册为系统服务,对外部存储设备提供管理和查询等服务,在外部存储设备状态发生改变的时候发出相应的通知给注册了该服务的应用程序。MountService相当于一个中间桥梁,负责接收Vold的消息并传递给上层应用。这里就不详细阐述MountService是如何启动的了,对于Android服务这一块,我将另写后续的文章分析。

      MountService在SystemServer.java(AndroidSourcecode2.3/frameworks/base/services/java/com/android/server/SystemServer.java)中启动,在启动的时候调用MountService的构造函数:

[java] view plaincopyprint?
  1. ServiceManager.addService("mount"new MountService(context));  
      找到MountService的构造函数:

[java] view plaincopyprint?
  1.     public MountService(Context context) {  
  2.         mContext = context;  
  3.   
  4.         // XXX: This will go away soon in favor of IMountServiceObserver  
  5.         mPms = (PackageManagerService) ServiceManager.getService("package");  
  6.   
  7.         mContext.registerReceiver(mBroadcastReceiver,  
  8.                 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), nullnull);  
  9.   
  10.         mHandlerThread = new HandlerThread("MountService");  
  11.         mHandlerThread.start();  
  12.         mHandler = new MountServiceHandler(mHandlerThread.getLooper());  
  13.   
  14.         // Add OBB Action Handler to MountService thread.  
  15.         mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());  
  16.   
  17.         /* 
  18.          * Vold does not run in the simulator, so pretend the connector thread 
  19.          * ran and did its thing. 
  20.          */  
  21.         if ("simulator".equals(SystemProperties.get("ro.product.device"))) {  
  22.             mReady = true;  
  23.             mUmsEnabling = true;  
  24.             return;  
  25.         }  
  26.   
  27.         /* 
  28.          * Create the connection to vold with a maximum queue of twice the 
  29.          * amount of containers we'd ever expect to have. This keeps an 
  30.          * "asec list" from blocking a thread repeatedly. 
  31.          */  
  32.         <span style="color:#000000;">mConnector = new NativeDaemonConnector(this"vold",  
  33.                 PackageManagerService.MAX_CONTAINERS * 2, VOLD_TAG);//通过调用带参数的构造函数生成了一个Runnable对象  
  34.         mReady = false;  
  35.         </span><span style="color:#ff0000;"><span style="color:#000000;">Thread thread = new Thread(mConnector, VOLD_TAG);</span><span style="color:#000000;">//这里开启了一个新线程,传递了一个Runnable对象  
  36.         thread.start();</span>  
  37. </span>    }  
       这里我们重点关注最后两句,这两句的意思我相信有一点java基础的人都知道吧,对,没错,就是开启一个新线程,我继续跟踪这个传进来的Runnable对象mConnector,查看NativeDaemonConnector.java后可以知道,该类实现了Runnable接口,同时也覆写了Runnable中的run()方法,在该方法中有一个死循环,主要负责监听来自Vold的Socket消息,这是一个阻塞方法。

       1.监听者

       listenToSocket();

       //代码路径:AndroidSourcecode2.3/frameworks/base/services/java/com/android/server/NativeDaemonConnector.java/run()方法中

       //该方法负责监听来自Vold的Socket消息,这些消息包括SD卡的插入,SD的检测,SD卡的挂载等等。

[java] view plaincopyprint?
  1.     public void run() {  
  2.   
  3.         while (true) {  
  4.             try {  
  5.                 <span style="color:#ff0000;"><span style="color:#000000;">listenToSocket();</span>  
  6. </span>            } catch (Exception e) {  
  7.                 Slog.e(TAG, "Error in NativeDaemonConnector", e);  
  8.                 SystemClock.sleep(5000);  
  9.             }  
  10.         }  
  11.     }  
       我们查看listenToSocket()中的代码,如下:

[java] view plaincopyprint?
  1. private void listenToSocket() throws IOException {  
  2.     LocalSocket socket = null;<span style="color:#000000;">//这些Socket就是用来与底层通信的,接收底层传递上来的关于SD卡挂载的信息</span>  
  3.   
  4.     try {  
  5.         socket = new LocalSocket();  
  6.         LocalSocketAddress address = new LocalSocketAddress(mSocket,  
  7.                 LocalSocketAddress.Namespace.RESERVED);  
  8.   
  9.         socket.connect(address);  
  10.         <span style="color:#000000;">mCallbacks.onDaemonConnected();</span>//主要处理队列中的event方法,后文将接着这里分析。  
  11.   
  12.         InputStream inputStream = socket.getInputStream();  
  13.         mOutputStream = socket.getOutputStream();<span style="color:#000000;">//同时也可以向底层发出控制命令</span>  
  14.   
  15.         byte[] buffer = new byte[BUFFER_SIZE];  
  16.         int start = 0;  
  17.   
  18.         while (true) {  
  19.             int count = inputStream.read(buffer, start, BUFFER_SIZE - start);<span style="color:#000000;">//通过inputStream.read来读取Socket中的信息</span>  
  20.             if (count < 0break;  
  21.   
  22.             // Add our starting point to the count and reset the start.  
  23.             count += start;  
  24.             start = 0;  
  25.   
  26.             for (int i = 0; i < count; i++) {//对信息进行处理  
  27.                 if (buffer[i] == 0) {  
  28.                     String event = new String(buffer, start, i - start);  
  29.                     if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));  
  30.   
  31.                     String[] tokens = event.split(" ");  
  32.                     try {  
  33.                         int code = Integer.parseInt(tokens[0]);  
  34.   
  35.                         if (code >= ResponseCode.UnsolicitedInformational) {  
  36.                             try {  
  37.                                 if (!mCallbacks.onEvent(code, event, tokens)) {  
  38.                                     Slog.w(TAG, String.format(  
  39.                                             "Unhandled event (%s)", event));  
  40.                                 }  
  41.                             } catch (Exception ex) {  
  42.                                 Slog.e(TAG, String.format(  
  43.                                         "Error handling '%s'", event), ex);  
  44.                             }  
  45.                         } else {  
  46.                             try {//将系统能够识别的event存入Block队列  
  47.                                 mResponseQueue.put(event);  
  48.                             } catch (InterruptedException ex) {  
  49.                                 Slog.e(TAG, "Failed to put response onto queue", ex);  
  50.                             }  
  51.                         }  
  52.                     } catch (NumberFormatException nfe) {  
  53.                         Slog.w(TAG, String.format("Bad msg (%s)", event));  
  54.                     }  
  55.                     start = i + 1;  
  56.                 }  
  57.             }  
  58.   
  59.             // We should end at the amount we read. If not, compact then  
  60.             // buffer and read again.   
  61.             if (start != count) {  
  62.                 final int remaining = BUFFER_SIZE - start;  
  63.                 System.arraycopy(buffer, start, buffer, 0, remaining);  
  64.                 start = remaining;  
  65.             } else {  
  66.                 start = 0;  
  67.             }  
  68.         }  
  69.     } catch (IOException ex) {  
  70.         Slog.e(TAG, "Communications error", ex);  
  71.         throw ex;  
  72.     } finally {  
  73.         synchronized (this) {//发送控制命令完成之后需要关闭流  
  74.             if (mOutputStream != null) {  
  75.                 try {  
  76.                     mOutputStream.close();  
  77.                 } catch (IOException e) {  
  78.                     Slog.w(TAG, "Failed closing output stream", e);  
  79.                 }  
  80.                 mOutputStream = null;  
  81.             }  
  82.         }  
  83.   
  84.         try {//关闭socket  
  85.             if (socket != null) {  
  86.                 socket.close();  
  87.             }  
  88.         } catch (IOException ex) {  
  89.             Slog.w(TAG, "Failed closing socket", ex);  
  90.         }  
  91.     }  
  92. }  

      2.处理者

      mCallbacks.onDaemonConnected();

          //代码路径:AndroidSourcecode2.3/frameworks/base/services/java/com/android/server/MountService.java

      //因为MountService实现了INativeDaemonConnectorCallbacks接口并覆写了其中的方法,因此这里会调用MountService中的onDaemonConnected()方法。该方法完成了对挂载消息的处理

[java] view plaincopyprint?
  1.     public void onDaemonConnected() {  
  2.         /* 
  3.          * Since we'll be calling back into the NativeDaemonConnector, 
  4.          * we need to do our work in a new thread. 
  5.          */  
  6.         new Thread() {  
  7.             public void run() {  
  8.                 /** 
  9.                  * Determine media state and UMS detection status 
  10.                  */  
  11.                 String path = Environment.getExternalStorageDirectory().getPath();//获取系统SD卡挂载路径该路径在Environment中写死了的  
  12.                 String state = Environment.MEDIA_REMOVED;//初始状态默认为MEDIA_REMOVED  
  13.   
  14.                 try {//该方法可以从Socket中取出处理之后的event消息并放在字符串数组中,该方法中使用的BlockQueue队列是阻塞队列  
  15.                     //如果队列中没有event将会阻塞直到有event之后再开始处理  
  16.                     <span style="color:#ff0000;"><span style="color:#000000;">String[] vols = mConnector.doListCommand(  
  17.                         "volume list", VoldResponseCode.VolumeListResult);</span>  
  18. </span>                    for (String volstr : vols) {  
  19.                         String[] tok = volstr.split(" ");  
  20.                         // FMT: <label> <mountpoint> <state>  
  21.                         if (!tok[1].equals(path)) {  
  22.                             Slog.w(TAG, String.format(  
  23.                                     "Skipping unknown volume '%s'",tok[1]));  
  24.                             continue;  
  25.                         }  
  26.                         int st = Integer.parseInt(tok[2]);  
  27.                         if (st == VolumeState.NoMedia) {  
  28.                             state = Environment.MEDIA_REMOVED;  
  29.                         } else if (st == VolumeState.Idle) {  
  30.                             state = Environment.MEDIA_UNMOUNTED;  
  31.                         } else if (st == VolumeState.Mounted) {//这里我们是SD卡挂载 因此会执行此处代码  
  32.                             state = Environment.MEDIA_MOUNTED;  
  33.                             Slog.i(TAG, "Media already mounted on daemon connection");  
  34.                         } else if (st == VolumeState.Shared) {  
  35.                             state = Environment.MEDIA_SHARED;  
  36.                             Slog.i(TAG, "Media shared on daemon connection");  
  37.                         } else {  
  38.                             throw new Exception(String.format("Unexpected state %d", st));  
  39.                         }  
  40.                     }  
  41.                     if (state != null) {//如果state不为空将会执行 因为前面我们已经知道state为挂载消息 所以这里会执行  
  42.                         if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);  
  43.                         updatePublicVolumeState(path, state);  
  44.                     }  
  45.                 } catch (Exception e) {  
  46.                     Slog.e(TAG, "Error processing initial volume state", e);  
  47.                     updatePublicVolumeState(path, Environment.MEDIA_REMOVED);  
  48.                 }  
  49.   
  50.                 try {  
  51.                     boolean avail = doGetShareMethodAvailable("ums");  
  52.                     notifyShareAvailabilityChange("ums", avail);  
  53.                 } catch (Exception ex) {  
  54.                     Slog.w(TAG, "Failed to get share availability");  
  55.                 }  
  56.                 /* 
  57.                  * Now that we've done our initialization, release  
  58.                  * the hounds! 
  59.                  */  
  60.                 mReady = true;  
  61.             }  
  62.         }.start();  
  63.     }  
       在该方法中首先执行:

[java] view plaincopyprint?
  1. String[] vols = mConnector.doListCommand(  
  2.                         "volume list", VoldResponseCode.VolumeListResult);  
       继续跟踪doListCommand可以知道:

[java] view plaincopyprint?
  1. public String[] doListCommand(String cmd, int expectedResponseCode)  
  2.         throws NativeDaemonConnectorException {  
  3.   
  4.     ArrayList<String> rsp = <span style="color:#000000;">doCommand</span>(cmd);  
  5.     String[] rdata = new String[rsp.size()-1];  
  6.     int idx = 0;  
  7.   
  8.     for (int i = 0; i < rsp.size(); i++) {  
  9.         String line = rsp.get(i);  
  10.         try {  
  11.             String[] tok = line.split(" ");  
  12.             int code = Integer.parseInt(tok[0]);  
  13.             if (code == expectedResponseCode) {  
  14.                 rdata[idx++] = line.substring(tok[0].length() + 1);  
  15.             } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {  
  16.                 if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));  
  17.                 int last = rsp.size() -1;  
  18.                 if (i != last) {  
  19.                     Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));  
  20.                     for (int j = i; j <= last ; j++) {  
  21.                         Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));  
  22.                     }  
  23.                 }  
  24.                 return rdata;  
  25.             } else {  
  26.                 throw new NativeDaemonConnectorException(  
  27.                         String.format("Expected list response %d, but got %d",  
  28.                                 expectedResponseCode, code));  
  29.             }  
  30.         } catch (NumberFormatException nfe) {  
  31.             throw new NativeDaemonConnectorException(  
  32.                     String.format("Error reading code '%s'", line));  
  33.         }  
  34.     }  
  35.     throw new NativeDaemonConnectorException("Got an empty response");  
  36. }  
       继续跟踪doCommand:

[java] view plaincopyprint?
  1. public synchronized ArrayList<String> doCommand(String cmd)  
  2.         throws NativeDaemonConnectorException  {  
  3.     mResponseQueue.clear();  
  4.     <span style="color:#000000;">sendCommand</span>(cmd);//向底层发送之前传递的“volume list”指令  
  5.   
  6.     ArrayList<String> response = new ArrayList<String>();  
  7.     boolean complete = false;  
  8.     int code = -1;  
  9.   
  10.     while (!complete) {  
  11.         try {  
  12.             // TODO - this should not block forever  
  13.             String line = mResponseQueue.take();//从队列中取出event  
  14.             if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));  
  15.             String[] tokens = line.split(" ");  
  16.             try {  
  17.                 code = Integer.parseInt(tokens[0]);  
  18.             } catch (NumberFormatException nfe) {  
  19.                 throw new NativeDaemonConnectorException(  
  20.                         String.format("Invalid response from daemon (%s)", line));  
  21.             }  
  22.   
  23.             if ((code >= 200) && (code < 600)) {  
  24.                 complete = true;  
  25.             }  
  26.             response.add(line);  
  27.         } catch (InterruptedException ex) {  
  28.             Slog.e(TAG, "Failed to process response", ex);  
  29.         }  
  30.     }  
  31.   
  32.     if (code >= ResponseCode.FailedRangeStart &&  
  33.             code <= ResponseCode.FailedRangeEnd) {  
  34.         /* 
  35.          * Note: The format of the last response in this case is 
  36.          *        "NNN <errmsg>" 
  37.          */  
  38.         throw new NativeDaemonConnectorException(  
  39.                 code, cmd, response.get(response.size()-1).substring(4));  
  40.     }  
  41.     return response;//将经过分析之后复合要求的event放入该ArrayList中并返回<pre name="code" class="java">}  

       
 返回doListCommand方法中,大致信息是从ArrayList中取出之前存入的符合要求的event,然后对这些event进行拆分,并截取其中的前部分存放在rdata这个字符串数组中返回。 

       这里继续返回到onDaemonConnected()新开的线程中,接着往下走,对返回的这个字符串数组再次进行分拆并分析,从代码中可以知道,这些字符串中存储了SD卡的挂载路径以及目前的状态信息。因为我们从之前的分析中可以知道,我们的SD卡已经挂载成功了,因此这里的状态是state = Environment.MEDIA_MOUNTED然后执行updatePublicVolumeState(path, state);方法。

      3.中转站

      private void updatePublicVolumeState(String path, String state)

      //代码路径:AndroidSourcecode2.3/frameworks/base/services/java/com/android/server/MountService.java

[java] view plaincopyprint?
  1. private void updatePublicVolumeState(String path, String state) {  
  2.     if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {  
  3.         Slog.w(TAG, "Multiple volumes not currently supported");  
  4.         return;  
  5.     }  
  6.   
  7.     if (mLegacyState.equals(state)) {  
  8.         Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));  
  9.         return;  
  10.     }  
  11.   
  12.     if (Environment.MEDIA_UNMOUNTED.equals(state)) {  
  13.         // Tell the package manager the media is gone.  
  14.         mPms.updateExternalMediaStatus(falsefalse);  
  15.   
  16.         /* 
  17.          * Some OBBs might have been unmounted when this volume was 
  18.          * unmounted, so send a message to the handler to let it know to 
  19.          * remove those from the list of mounted OBBS. 
  20.          */  
  21.         mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE,  
  22.                 path));  
  23.     }<span style="color:#ff0000;"> <span style="color:#000000;">else if (Environment.MEDIA_MOUNTED.equals(state)) {  
  24.         // Tell the package manager the media is available for use.  
  25.         mPms.updateExternalMediaStatus(truefalse);</span>  
  26.     </span>}  
  27.   
  28.     String oldState = mLegacyState;  
  29.     mLegacyState = state;  
  30.   
  31.     synchronized (mListeners) {  
  32.         for (int i = mListeners.size() -1; i >= 0; i--) {  
  33.             MountServiceBinderListener bl = mListeners.get(i);  
  34.             try {  
  35.                 bl.mListener.onStorageStateChanged(path, oldState, state);  
  36.             } catch (RemoteException rex) {  
  37.                 Slog.e(TAG, "Listener dead");  
  38.                 mListeners.remove(i);  
  39.             } catch (Exception ex) {  
  40.                 Slog.e(TAG, "Listener failed", ex);  
  41.             }  
  42.         }  
  43.     }  
  44. }  
       这里首先是执行

[java] view plaincopyprint?
  1. mPms.updateExternalMediaStatus(truefalse);  
       该方法位于PackageManagerService.java中,作用是告诉PackageManager外置media可用。在updateExternalMediaStatus方法中,通知PackageManagerService去更新外置media的状态,这包括了读取SD卡中的内容并识别。通过这个步骤以后,我们打开SD卡才能发现哪些东西是系统已经识别的,哪些东西系统不能识别。

       接下来我们看看

[java] view plaincopyprint?
  1. synchronized (mListeners) {  
  2.     for (int i = mListeners.size() -1; i >= 0; i--) {  
  3.         MountServiceBinderListener bl = mListeners.get(i);  
  4.         try {  
  5.             bl.mListener.onStorageStateChanged(path, oldState, state);  
  6.         } catch (RemoteException rex) {  
  7.             Slog.e(TAG, "Listener dead");  
  8.             mListeners.remove(i);  
  9.         } catch (Exception ex) {  
  10.             Slog.e(TAG, "Listener failed", ex);  
  11.         }  
  12.     }  
  13. }  
      这是一个同步块,最重要的一句代码是 [java] view plaincopyprint?
  1. bl.mListener.onStorageStateChanged(path, oldState, state);  
      分析到这里,如果不去了解StorageManager和MountService关系的话,后面是没有办法分析下去的。我们的目的是从底层SD卡的挂载信息如何传递到上层的"设置-存储-SD卡"这个界面中,从而理清一条从底向上的线路。如果对于这一块也有疑问的朋友,希望能够真的去看看源码,并逐步自己一步一步的跟踪看看,有的时候真的有的复杂,很多机制不懂更多的是没听过的机制,但是只要自己想要弄清楚,那么就坚持下去吧。一开始我插入SD卡系统居然有时候识别不到,我就跟踪上层的源码,结果发现解决不了问题,那么就跟踪下去吧,不会的就一边查资料一边问别人,同时一边做记录,这些记录一方面可以帮助自己整理学习的资料,另一方面可以帮助也遇到同样问题的朋友。所以在此写下这些自己的拙见,错误百出但初衷是单纯的。

      说了这么多废话,下一篇文章将继续分析SD卡挂载消息是如何在"设置"中显示出来的。