android MountService实现------外设挂载及状态监听

事件起因:

        我们的测试报出一个问题,插着U盘开机(我们的机顶盒),多媒体文件未扫描到(U盘里有视频文件还有音乐文件)。说本地的视频文件列表还有音乐列表也是空的。

我是做应用的,兼顾Framework仓库的维护。负责媒体扫描的是MediaProvider.apk。。。义不容辞,这个问题当然是我来负责解决。


问题分析:

        1.首先,我显示查看MediaProvider.apk所对应的源码实现。其源码实现很简单,下面我做一下简单的说明。

        主要的两个类如下:MediaScannerReceiver.java和MediaScannerService.java。

         MediaScannerReceiver.java源码


    private final static String TAG = "MediaScannerReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG, "onReceive,action:" + action);

        Uri uri = intent.getData();
        if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
            // 收到开机广播,扫描内部存储
            scan(context, MediaProvider.INTERNAL_VOLUME);
        } else {
            if (uri.getScheme().equals("file")) {
                // 处理扫描外设的Intent
                String path = uri.getPath();
                String externalStoragePath = Environment.getExternalStorageDirectory().getPath();

                Log.d(TAG, "action: " + action + " path: " + path);
                if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
                    // 扫描已挂载的外设
                    scan(context, MediaProvider.EXTERNAL_VOLUME);
                } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&
                        path != null && path.startsWith(externalStoragePath + "/")) {
                    scanFile(context, path);
                }
            }
        }
    }
    //扫描指定的Volume
    private void scan(Context context, String volume) {
        Bundle args = new Bundle();
        args.putString("volume", volume);
        context.startService(
                new Intent(context, MediaScannerService.class).putExtras(args));
    }    
    //扫描指定的Path
    private void scanFile(Context context, String path) {
        Bundle args = new Bundle();
        args.putString("filepath", path);
        context.startService(
                new Intent(context, MediaScannerService.class).putExtras(args));
    }    

          从这段代码就可以知道,在收到开机广播和Intent.ACTION_MEDIA_MOUNTED广播都会进行文件的扫描。外设的扫描是接收到Intent.ACTION_MEDIA_MOUNTED广播时进行。

        2.从开机抓的log分析,确实是没有收到Intent.ACTION_MEDIA_MOUNTED广播,但是在终端使用df命令可以看到U盘已经正常挂载了。

        3.外设状态的广播发送都是的MountService里面进行的。所以接下来我们来看一下开机时MountService都做了些什么。

         MountService.java的启动是在SystemServer.jar中进行的,ServiceManager.addService("mount",new MountService(context))。这样就是MountService.java的构造函数就会被回调。

        MountService.java的构造函数中跟今天研究相关的关键的两个处理就是

        1)mContext.registerReceiver(mBroadcastReceiver, filter, null, null);

        2)mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG);
              Thread thread = new Thread(mConnector, VOLD_TAG);
              thread.start();

        操作1)中的广播接收器接收的是Intent.ACTION_BOOT_COMPLETED(开机完成广播)。接收到开机广播后,最要的处理就是针对mVolumeStates中保存的外设路径做外设的挂载动作,挂载中的各种状态通知是在onEvent(int code, String raw, String[] cooked)回调中进行的。onEvent中调用的notifyVolumeStateChange方法会发送外设状态广播通知。所以现在最重要的是mVolumeStates中的路径是何时保存进去的?这就涉及到操作2)中的实现。

       操作2)中的NativeDaemonConnector 是一个implements Runnable类,   当 thread.start()线程启动时,NativeDaemonConnector.java的run()方法就会执行。run()方法中关键的实现是了NativeDaemonConnector.java类中内部方法listenToSocket(),listenToSocket()方法中重要的实现是mCallbacks.onDaemonConnected(),onDaemonConnected()方法调用的是 MountService.java类中的onDaemonConnected()方法。onDaemonConnected()方法中重要的实现是:String[] vols = mConnector.doListCommand("volume list", VoldResponseCode.VolumeListResult); 其功能就是从底层获取当前的外设列表。然后通过调用updatePublicVolumeState(path, state);方法更新mVolumeStates。这样就解决了操作1)中的疑惑。

       4.通过对步骤3的了解,最终定位到在notifyVolumeStateChange方法里调用getVolumeState(String mountPoint)时throw new IllegalArgumentException(),这样notifyVolumeStateChange方法里的外设状态广播通知没有进行。

       5.为什么步骤 4中抛出异常呢?是因为getVolumeState(String mountPoint)里通过mVolumeStates.get(mountPoint);没有获得对应挂载点的状态。即该挂载点不再步骤3中的2)里从底层获取当前的外设列表里。

       6.外设列表是从底层查询的(vold给的),onEvent里面的挂载点也是vold给的。那就是vold提供的这两个不一致。。。不一致我就找对应的负责人沟通了。。。他给了一句“臣妾做不到一致”,讲了一大堆,大概意思就是说有的U盘带分区,有的不带,他那边处理的要兼容的话就做不到那一点。

  7.那怎么处理呢???既然U盘能正常挂载,就是没有通知广播,没有通知广播的原因也是抛出异常引起,那就不抛异常了。。。这样可以解决问题,但是不足之处在于别的地方通过MountService.java的getVolumeState(String mountPoint)查询挂载点状态时,传入真正的挂载点有可能获取不到对应的状态。这也是没有办法的办法了。。。

     





你可能感兴趣的:(Android学习)