博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android SDCard UnMounted 流程分析(三)
阅读量:6000 次
发布时间:2019-06-20

本文共 7444 字,大约阅读时间需要 24 分钟。

 

前篇地址

 

 前一篇讲到SDCard unmout onEvent 发送socket 到框架层,接下来分析框架层得到数据后的流程。

 MoutService

当android 系统启动时,system将MountService 添加到启动服务里面,而MountService 会开启一个线程来运行NativeDaemonConnector,由它来监听vold的消息,代码:

 mConnector = 
new NativeDaemonConnector(
this
"
vold
", MAX_CONTAINERS * 
2, VOLD_TAG);

        mReady = 
false;

        Thread thread = 
new Thread(mConnector, VOLD_TAG);

        thread.start(); 

 该函数运行在MountService的构造函数里面,而NativeDaemonConnector 本身就是继承自Runnable。

NativeDaemonConnector

 Framework与vold 的通信是通过socket来实现的,不过该socket 由 android做了一个封装,LocalSocket 实现的socket功能。

NativeDaecomConnector 位于framework/base/service/java/com/android/server目录下, 监听vold 的消息代码在继承自Runnable对象的run方法里面 :

@Override

    
public 
void run() {

        HandlerThread thread = 
new HandlerThread(TAG + 
"
.CallbackHandler
");

        thread.start();

        mCallbackHandler = 
new Handler(thread.getLooper(), 
this);


        
while (
true) {

            
try {

                
listenToSocket();

            } 
catch (Exception e) {

                Slog.e(TAG, 
"
Error in NativeDaemonConnector
", e);

                SystemClock.sleep(
5000);

            }

        }

    } 

 

NativeDaemonConnector 类实例化了一个LocalSocket来与vold 通信。LocalSocket 里面有一个类LocalSocketImpl,该类部分是通过JNI实现的。

关于socket 内部如何通信,这个不是我们所关心的内容,因为如果要深入进去估计没完没了,有兴趣的朋友可以参考源码进入SocketListener查看:

建立连接 

SocketListener::SocketListener

 

当main初始化CommandListener 后,会为socketName 传入一个叫vold 的字符串 

SocketListener::startListener

 

等待并接收连接请求
SocketListener::runListener

 

获得命令参数
bool FrameworkListener::onDataAvailable

 

dispatchCommand 到相应的命令类,并返回一部分消息给上层
FrameworkListener::dispatchCommand
 

 再回过头看NativeDaemonConnector 的listenToSocket,代码中实例化了一个LocalSocketAddress的实例,并传入一个叫"vold"字符串的socket 名称,这与CommandListener中继承了FrameworkListener时给的"vold"名称是一致的,两个socket名称一致则可以互相进行通讯了,代码如下:

private 
void listenToSocket() throws IOException {

        LocalSocket socket = 
null;

    Slog.w(TAG,String.format(
"
NativeDaemonConnector--->listenToSocket:start
"));

        
try {

            socket = 
new LocalSocket();

            
LocalSocketAddress address = new LocalSocketAddress(mSocket,
                    LocalSocketAddress.Namespace.RESERVED);


            socket.connect(address);


            InputStream inputStream = socket.getInputStream();

            mOutputStream = socket.getOutputStream();


            mCallbacks.onDaemonConnected();


            
byte[] buffer = 
new 
byte[BUFFER_SIZE];

            
int start = 
0;


            
while (
true) {

                
int count = inputStream.read(buffer, start, BUFFER_SIZE - start);

                
if (count < 
0
break;


                
//
 Add our starting point to the count and reset the start.
                count += start;

                start = 
0;


                
for (
int i = 
0; i < count; i++) {

                    
if (buffer[i] == 
0) {

                       
 String event = new String(buffer, start, i - start);//解析socket 的数据并获取event

                        
if (LOCAL_LOGD) Slog.d(TAG, String.format(
"
RCV <- {%s}
"
event));


                        String[] tokens = 
event.split(
"
 
"
2);

                        
try {

                            
int code = Integer.parseInt(tokens[
0]);


                            
if (code >= ResponseCode.UnsolicitedInformational) {

                                mCallbackHandler.sendMessage(

                                        mCallbackHandler.obtainMessage(code, 
event));//发送消息给handler

                            } 
else {

                                
try {

                                    mResponseQueue.put(
event);

                                } 
catch (InterruptedException ex) {

                                    Slog.e(TAG, 
"
Failed to put response onto queue
", ex);

                                }

                            }

                        } 
catch (NumberFormatException nfe) {

                            Slog.w(TAG, String.format(
"
Bad msg (%s)
"
event));

                        }

                        start = i + 
1;

                    }

                }


                
//
 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) {

            Slog.e(TAG, 
"
Communications error
", ex);

            
throw ex;

        } 
finally {

            synchronized (mDaemonLock) {

                
if (mOutputStream != 
null) {

                    
try {

                        mOutputStream.close();

                    } 
catch (IOException e) {

                        Slog.w(TAG, 
"
Failed closing output stream
", e);

                    }

                    mOutputStream = 
null;

                }

            }


            
try {

                
if (socket != 
null) {

                    socket.close();

                }

            } 
catch (IOException ex) {

                Slog.w(TAG, 
"
Failed closing socket
", ex);

            }

        }

    } 

上面代码,通过socket 并event 解析出来,并通handler 发送到handleMessage 中,当handleMessage接收到传过来的消息时,会调用MountService 的onEvent 方法将code和event和sdcard 的状态传递进去。代码如下:

 
public boolean handleMessage(Message msg) {

        String 
event = (String) msg.obj;

        Slog.w(TAG,String.format(
"
NativeDaemonConnector--->handleMessage the event value is 
"+
event));

        
try {

            
if (!
mCallbacks.onEvent(msg.what, eventevent.split(" "))) {

                Slog.w(TAG, String.format(

                        
"
Unhandled event '%s'
"
event));

            }

        } 
catch (Exception e) {

            Slog.e(TAG, String.format(

                    
"
Error handling '%s'
"
event), e);

        }

        
return 
true;

    } 

 

 

 又回到MountService ,在onEvent里面当接收到的code ==VoldResponseCode.VolumeBadRemoval时会调用updatePublicVolumeState,发送unmount改变的广播,代码如下:

else 
if (code == VoldResponseCode.VolumeBadRemoval) {

                
if (DEBUG_EVENTS) Slog.i(TAG, 
"
Sending unmounted event first
");

                
/*
 Send the media unmounted event first 
*/

                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);

                action = Intent.ACTION_MEDIA_UNMOUNTED;


                
if (DEBUG_EVENTS) Slog.i(TAG, 
"
Sending media bad removal
");

                
updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);

                action = Intent.ACTION_MEDIA_BAD_REMOVAL;}

 到这里,进入updatePublicVolumeState看该函数里的主要代码:

synchronized (mListeners) {

            
for (
int i = mListeners.size() -
1; i >= 
0; i--) {

                MountServiceBinderListener bl = mListeners.
get(i);

                
try {

                    Slog.w(TAG,
"
MountService--->updatePublicVolumeState-->bl.mListener.onStorageStateChanged
");

                   
 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);

                }

            }

        }

 并且调用sendStorageIntent 方法将SDCard的Action:android.intent.action.MEDIA_BAD_REMOVAL 和dat:file:///mnt/sdcard 通过这个广播发送出去,代码如下:

 

    
private 
void sendStorageIntent(String action, String path) {
        Intent intent = 
new Intent(action, Uri.parse(
"
file://
" + path));
        
//
 add StorageVolume extra
        intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolumeMap.
get(path));
        Slog.d(TAG, 
"
sendStorageIntent 
" + intent);
        mContext.sendBroadcast(intent);
    }

 

再回到 updatePublicVolumeState ,调用了stateChange 后,将状态为632的标识发送到NativeDaemonConnector 的handlemessage,当NativeDaemonConnector 发现SDCard的状态发送改变时,比如unmount 的时候,从632(VolumeBadRemoval)变到605(VolumeStateChange)到onEvent,当onEvent再次得到请求时,进入判断会直接执行notifyVolumeStateChange 函数,代码如下:

if (code == VoldResponseCode.VolumeStateChange) {

            
/*
             * One of the volumes we're managing has changed state.
             * Format: "NNN Volume <label> <path> state changed
             * from <old_#> (<old_str>) to <new_#> (<new_str>)"
             
*/

            notifyVolumeStateChange(

                    cooked[
2], cooked[
3], Integer.parseInt(cooked[
7]),

                            Integer.parseInt(cooked[
10]));

        } 

 

 notifyStateChange 会调用updatePublicVolumeState通知packageManger SDCard己经unmount.

 

 再回到Vold 

由于vold 启动文件一开始就启动了CommandListener的runcommand由于socket 一直在通讯,当发现值改变后,进入以下代码runCommand 方法里面:

else 
if (!strcmp(argv[
1], 
"
unmount
")) {

        
if (argc < 
3 || argc > 
4 ||

           ((argc == 
4 && strcmp(argv[
3], 
"
force
")) &&

            (argc == 
4 && strcmp(argv[
3], 
"
force_and_revert
")))) {

            cli->sendMsg(ResponseCode::CommandSyntaxError, 
"
Usage: volume unmount <path> [force|force_and_revert]
"
false);

            
return 
0;

        }


        
bool force = 
false;

        
bool revert = 
false;

        
if (argc >= 
4 && !strcmp(argv[
3], 
"
force
")) {

            force = 
true;

        } 
else 
if (argc >= 
4 && !strcmp(argv[
3], 
"
force_and_revert
")) {

            force = 
true;

            revert = 
true;

        }

        rc = 
vm->unmountVolume(argv[
2], force, revert);

    }  

 这时调用VolumeManage的unmoutVolume。该方法来源于Volume 的unmountVol,调用这个函数会unmount 三个挂载点,并同时调用setState通知框架unmount 成功,可以改变UI等一系列动作。

最后总结

MountService: 实现用于管理存储设备的后台服务

StorageManage:访问MountService 接口,并向应用层提供接口

PackageMangeService:是用于管理系统中所有apk,当SDCard发生变化时,向应用层发送消息

NativeDaemonConnector:创建socket实现mountservice 和vold 的通信

 

可以这么说:当vold 捕获到uevent 事件,会将事件消息通知framework,framework 进行判断,然后再下发执行命令。 

 

粗糙图

最后附一张比较粗糙的结构图,时间较急,没仔细画好

 

 本文转自 terry_龙 51CTO博客,原文链接:http://blog.51cto.com/terryblog/817015,如需转载请自行联系原作者

你可能感兴趣的文章
Python学习第二天-编写购物车
查看>>
AD域组策略-只显示指定的控制面板选项配置方法
查看>>
BigTable——针对结构型数据的一种分布式存储系统
查看>>
ip通信基础第二周回顾
查看>>
唯美MACD-完全版
查看>>
Canvas中 drawImage绘制图片不显示
查看>>
MyEclipse新建Server项目
查看>>
Magento错误处理
查看>>
茵茵的第一课
查看>>
Linux实战教学笔记53:开源虚拟化KVM(一)搭建部署与概述
查看>>
PAT 1007
查看>>
USACO习题:Friday the Thirteenth
查看>>
C++ VS2012 内存泄露检测
查看>>
zabbix 批量添加聚合图形
查看>>
北京交通大学第六届新生程序设计竞赛题解
查看>>
求解点关于直线的距离、垂足、对称点公式
查看>>
洛谷 P1577 切绳子【二分答案】
查看>>
用 Google Map 的 Geocoder 接口来反向地址解析
查看>>
在中小型公司如何做好测试——论测试计划的重要性
查看>>
BSS段、数据段、代码段、堆与栈
查看>>