`

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

 
阅读更多

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

图中绿色箭头表示的就是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的构造函数:

view plain
  1. ServiceManager.addService("mount",newMountService(context));
找到MountService的构造函数:
view plain
  1. publicMountService(Contextcontext){
  2. mContext=context;
  3. //XXX:ThiswillgoawaysooninfavorofIMountServiceObserver
  4. mPms=(PackageManagerService)ServiceManager.getService("package");
  5. mContext.registerReceiver(mBroadcastReceiver,
  6. newIntentFilter(Intent.ACTION_BOOT_COMPLETED),null,null);
  7. mHandlerThread=newHandlerThread("MountService");
  8. mHandlerThread.start();
  9. mHandler=newMountServiceHandler(mHandlerThread.getLooper());
  10. //AddOBBActionHandlertoMountServicethread.
  11. mObbActionHandler=newObbActionHandler(mHandlerThread.getLooper());
  12. /*
  13. *Volddoesnotruninthesimulator,sopretendtheconnectorthread
  14. *rananddiditsthing.
  15. */
  16. if("simulator".equals(SystemProperties.get("ro.product.device"))){
  17. mReady=true;
  18. mUmsEnabling=true;
  19. return;
  20. }
  21. /*
  22. *Createtheconnectiontovoldwithamaximumqueueoftwicethe
  23. *amountofcontainerswe'deverexpecttohave.Thiskeepsan
  24. *"aseclist"fromblockingathreadrepeatedly.
  25. */
  26. <spanstyle="color:#000000;">mConnector=newNativeDaemonConnector(this,"vold",
  27. PackageManagerService.MAX_CONTAINERS*2,VOLD_TAG);//通过调用带参数的构造函数生成了一个Runnable对象
  28. mReady=false;
  29. </span><spanstyle="color:#ff0000;"><spanstyle="color:#000000;">Threadthread=newThread(mConnector,VOLD_TAG);</span><spanstyle="color:#000000;">//这里开启了一个新线程,传递了一个Runnable对象
  30. thread.start();</span>
  31. </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卡的挂载等等。

view plain
  1. publicvoidrun(){
  2. while(true){
  3. try{
  4. <spanstyle="color:#ff0000;"><spanstyle="color:#000000;">listenToSocket();</span>
  5. </span>}catch(Exceptione){
  6. Slog.e(TAG,"ErrorinNativeDaemonConnector",e);
  7. SystemClock.sleep(5000);
  8. }
  9. }
  10. }
我们查看listenToSocket()中的代码,如下:
view plain
  1. privatevoidlistenToSocket()throwsIOException{
  2. LocalSocketsocket=null;<spanstyle="color:#000000;">//这些Socket就是用来与底层通信的,接收底层传递上来的关于SD卡挂载的信息</span>
  3. try{
  4. socket=newLocalSocket();
  5. LocalSocketAddressaddress=newLocalSocketAddress(mSocket,
  6. LocalSocketAddress.Namespace.RESERVED);
  7. socket.connect(address);
  8. <spanstyle="color:#000000;">mCallbacks.onDaemonConnected();</span>//主要处理队列中的event方法,后文将接着这里分析。
  9. InputStreaminputStream=socket.getInputStream();
  10. mOutputStream=socket.getOutputStream();<spanstyle="color:#000000;">//同时也可以向底层发出控制命令</span>
  11. byte[]buffer=newbyte[BUFFER_SIZE];
  12. intstart=0;
  13. while(true){
  14. intcount=inputStream.read(buffer,start,BUFFER_SIZE-start);<spanstyle="color:#000000;">//通过inputStream.read来读取Socket中的信息</span>
  15. if(count<0)break;
  16. //Addourstartingpointtothecountandresetthestart.
  17. count+=start;
  18. start=0;
  19. for(inti=0;i<count;i++){//对信息进行处理
  20. if(buffer[i]==0){
  21. Stringevent=newString(buffer,start,i-start);
  22. if(LOCAL_LOGD)Slog.d(TAG,String.format("RCV<-{%s}",event));
  23. String[]tokens=event.split("");
  24. try{
  25. intcode=Integer.parseInt(tokens[0]);
  26. if(code>=ResponseCode.UnsolicitedInformational){
  27. try{
  28. if(!mCallbacks.onEvent(code,event,tokens)){
  29. Slog.w(TAG,String.format(
  30. "Unhandledevent(%s)",event));
  31. }
  32. }catch(Exceptionex){
  33. Slog.e(TAG,String.format(
  34. "Errorhandling'%s'",event),ex);
  35. }
  36. }else{
  37. try{//将系统能够识别的event存入Block队列
  38. mResponseQueue.put(event);
  39. }catch(InterruptedExceptionex){
  40. Slog.e(TAG,"Failedtoputresponseontoqueue",ex);
  41. }
  42. }
  43. }catch(NumberFormatExceptionnfe){
  44. Slog.w(TAG,String.format("Badmsg(%s)",event));
  45. }
  46. start=i+1;
  47. }
  48. }
  49. //Weshouldendattheamountweread.Ifnot,compactthen
  50. //bufferandreadagain.
  51. if(start!=count){
  52. finalintremaining=BUFFER_SIZE-start;
  53. System.arraycopy(buffer,start,buffer,0,remaining);
  54. start=remaining;
  55. }else{
  56. start=0;
  57. }
  58. }
  59. }catch(IOExceptionex){
  60. Slog.e(TAG,"Communicationserror",ex);
  61. throwex;
  62. }finally{
  63. synchronized(this){//发送控制命令完成之后需要关闭流
  64. if(mOutputStream!=null){
  65. try{
  66. mOutputStream.close();
  67. }catch(IOExceptione){
  68. Slog.w(TAG,"Failedclosingoutputstream",e);
  69. }
  70. mOutputStream=null;
  71. }
  72. }
  73. try{//关闭socket
  74. if(socket!=null){
  75. socket.close();
  76. }
  77. }catch(IOExceptionex){
  78. Slog.w(TAG,"Failedclosingsocket",ex);
  79. }
  80. }
  81. }

2.处理者

mCallbacks.onDaemonConnected();

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

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

view plain
  1. publicvoidonDaemonConnected(){
  2. /*
  3. *Sincewe'llbecallingbackintotheNativeDaemonConnector,
  4. *weneedtodoourworkinanewthread.
  5. */
  6. newThread(){
  7. publicvoidrun(){
  8. /**
  9. *DeterminemediastateandUMSdetectionstatus
  10. */
  11. Stringpath=Environment.getExternalStorageDirectory().getPath();//获取系统SD卡挂载路径该路径在Environment中写死了的
  12. Stringstate=Environment.MEDIA_REMOVED;//初始状态默认为MEDIA_REMOVED
  13. try{//该方法可以从Socket中取出处理之后的event消息并放在字符串数组中,该方法中使用的BlockQueue队列是阻塞队列
  14. //如果队列中没有event将会阻塞直到有event之后再开始处理
  15. <spanstyle="color:#ff0000;"><spanstyle="color:#000000;">String[]vols=mConnector.doListCommand(
  16. "volumelist",VoldResponseCode.VolumeListResult);</span>
  17. </span>for(Stringvolstr:vols){
  18. String[]tok=volstr.split("");
  19. //FMT:<label><mountpoint><state>
  20. if(!tok[1].equals(path)){
  21. Slog.w(TAG,String.format(
  22. "Skippingunknownvolume'%s'",tok[1]));
  23. continue;
  24. }
  25. intst=Integer.parseInt(tok[2]);
  26. if(st==VolumeState.NoMedia){
  27. state=Environment.MEDIA_REMOVED;
  28. }elseif(st==VolumeState.Idle){
  29. state=Environment.MEDIA_UNMOUNTED;
  30. }elseif(st==VolumeState.Mounted){//这里我们是SD卡挂载因此会执行此处代码
  31. state=Environment.MEDIA_MOUNTED;
  32. Slog.i(TAG,"Mediaalreadymountedondaemonconnection");
  33. }elseif(st==VolumeState.Shared){
  34. state=Environment.MEDIA_SHARED;
  35. Slog.i(TAG,"Mediasharedondaemonconnection");
  36. }else{
  37. thrownewException(String.format("Unexpectedstate%d",st));
  38. }
  39. }
  40. if(state!=null){//如果state不为空将会执行因为前面我们已经知道state为挂载消息所以这里会执行
  41. if(DEBUG_EVENTS)Slog.i(TAG,"Updatingvalidstate"+state);
  42. updatePublicVolumeState(path,state);
  43. }
  44. }catch(Exceptione){
  45. Slog.e(TAG,"Errorprocessinginitialvolumestate",e);
  46. updatePublicVolumeState(path,Environment.MEDIA_REMOVED);
  47. }
  48. try{
  49. booleanavail=doGetShareMethodAvailable("ums");
  50. notifyShareAvailabilityChange("ums",avail);
  51. }catch(Exceptionex){
  52. Slog.w(TAG,"Failedtogetshareavailability");
  53. }
  54. /*
  55. *Nowthatwe'vedoneourinitialization,release
  56. *thehounds!
  57. */
  58. mReady=true;
  59. }
  60. }.start();
  61. }
在该方法中首先执行:
view plain
  1. String[]vols=mConnector.doListCommand(
  2. "volumelist",VoldResponseCode.VolumeListResult);
继续跟踪doListCommand可以知道:
view plain
  1. publicString[]doListCommand(Stringcmd,intexpectedResponseCode)
  2. throwsNativeDaemonConnectorException{
  3. ArrayList<String>rsp=<spanstyle="color:#000000;">doCommand</span>(cmd);
  4. String[]rdata=newString[rsp.size()-1];
  5. intidx=0;
  6. for(inti=0;i<rsp.size();i++){
  7. Stringline=rsp.get(i);
  8. try{
  9. String[]tok=line.split("");
  10. intcode=Integer.parseInt(tok[0]);
  11. if(code==expectedResponseCode){
  12. rdata[idx++]=line.substring(tok[0].length()+1);
  13. }elseif(code==NativeDaemonConnector.ResponseCode.CommandOkay){
  14. if(LOCAL_LOGD)Slog.d(TAG,String.format("Listterminatedwith{%s}",line));
  15. intlast=rsp.size()-1;
  16. if(i!=last){
  17. Slog.w(TAG,String.format("Recv'd%dlinesafterendoflist{%s}",(last-i),cmd));
  18. for(intj=i;j<=last;j++){
  19. Slog.w(TAG,String.format("ExtraData<%s>",rsp.get(i)));
  20. }
  21. }
  22. returnrdata;
  23. }else{
  24. thrownewNativeDaemonConnectorException(
  25. String.format("Expectedlistresponse%d,butgot%d",
  26. expectedResponseCode,code));
  27. }
  28. }catch(NumberFormatExceptionnfe){
  29. thrownewNativeDaemonConnectorException(
  30. String.format("Errorreadingcode'%s'",line));
  31. }
  32. }
  33. thrownewNativeDaemonConnectorException("Gotanemptyresponse");
  34. }
继续跟踪doCommand:
view plain
  1. publicsynchronizedArrayList<String>doCommand(Stringcmd)
  2. throwsNativeDaemonConnectorException{
  3. mResponseQueue.clear();
  4. <spanstyle="color:#000000;">sendCommand</span>(cmd);//向底层发送之前传递的“volumelist”指令
  5. ArrayList<String>response=newArrayList<String>();
  6. booleancomplete=false;
  7. intcode=-1;
  8. while(!complete){
  9. try{
  10. //TODO-thisshouldnotblockforever
  11. Stringline=mResponseQueue.take();//从队列中取出event
  12. if(LOCAL_LOGD)Slog.d(TAG,String.format("RSP<-{%s}",line));
  13. String[]tokens=line.split("");
  14. try{
  15. code=Integer.parseInt(tokens[0]);
  16. }catch(NumberFormatExceptionnfe){
  17. thrownewNativeDaemonConnectorException(
  18. String.format("Invalidresponsefromdaemon(%s)",line));
  19. }
  20. if((code>=200)&&(code<600)){
  21. complete=true;
  22. }
  23. response.add(line);
  24. }catch(InterruptedExceptionex){
  25. Slog.e(TAG,"Failedtoprocessresponse",ex);
  26. }
  27. }
  28. if(code>=ResponseCode.FailedRangeStart&&
  29. code<=ResponseCode.FailedRangeEnd){
  30. /*
  31. *Note:Theformatofthelastresponseinthiscaseis
  32. *"NNN<errmsg>"
  33. */
  34. thrownewNativeDaemonConnectorException(
  35. code,cmd,response.get(response.size()-1).substring(4));
  36. }
  37. returnresponse;//将经过分析之后复合要求的event放入该ArrayList中并返回<prename="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

view plain
  1. privatevoidupdatePublicVolumeState(Stringpath,Stringstate){
  2. if(!path.equals(Environment.getExternalStorageDirectory().getPath())){
  3. Slog.w(TAG,"Multiplevolumesnotcurrentlysupported");
  4. return;
  5. }
  6. if(mLegacyState.equals(state)){
  7. Slog.w(TAG,String.format("Duplicatestatetransition(%s->%s)",mLegacyState,state));
  8. return;
  9. }
  10. if(Environment.MEDIA_UNMOUNTED.equals(state)){
  11. //Tellthepackagemanagerthemediaisgone.
  12. mPms.updateExternalMediaStatus(false,false);
  13. /*
  14. *SomeOBBsmighthavebeenunmountedwhenthisvolumewas
  15. *unmounted,sosendamessagetothehandlertoletitknowto
  16. *removethosefromthelistofmountedOBBS.
  17. */
  18. mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE,
  19. path));
  20. }<spanstyle="color:#ff0000;"><spanstyle="color:#000000;">elseif(Environment.MEDIA_MOUNTED.equals(state)){
  21. //Tellthepackagemanagerthemediaisavailableforuse.
  22. mPms.updateExternalMediaStatus(true,false);</span>
  23. </span>}
  24. StringoldState=mLegacyState;
  25. mLegacyState=state;
  26. synchronized(mListeners){
  27. for(inti=mListeners.size()-1;i>=0;i--){
  28. MountServiceBinderListenerbl=mListeners.get(i);
  29. try{
  30. bl.mListener.onStorageStateChanged(path,oldState,state);
  31. }catch(RemoteExceptionrex){
  32. Slog.e(TAG,"Listenerdead");
  33. mListeners.remove(i);
  34. }catch(Exceptionex){
  35. Slog.e(TAG,"Listenerfailed",ex);
  36. }
  37. }
  38. }
  39. }
这里首先是执行
view plain
  1. mPms.updateExternalMediaStatus(true,false);
该方法位于PackageManagerService.java中,作用是告诉PackageManager外置media可用。在updateExternalMediaStatus方法中,通知PackageManagerService去更新外置media的状态,这包括了读取SD卡中的内容并识别。通过这个步骤以后,我们打开SD卡才能发现哪些东西是系统已经识别的,哪些东西系统不能识别。

接下来我们看看

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

相关推荐

Global site tag (gtag.js) - Google Analytics