基于Android系统OTG功能下将U盘作为数据源的解决方案

一、问题来源

目前市面上多数APP都是基于互联网来读取数据,展示界面,但若在无网络环境,如展厅、政府机构中大多都不连接外网,那么APP的数据只能以静态或内置的方式存储,这些数据(图片、Excel等文档、音视频等)无法刷新和更改,非常不方便后期维护更新;

典型案例:某展厅有一台触摸屏(Android系统),提供该单位所管辖范围下的客户信息,一旦客户信息变更,那么就需要修改APP中的数据,问题来了,该数据是静态写入程序的,触摸屏是不联网的,只能重新提供APP安装包,非常不方便。


二、分析原因

数据来源:

1 内置数据,就是所谓的单机版,静态,不易修改,无法满足后期更新维护的要求,无法满足如上案例更新数据的要求;

2 服务器提供,通过接口获取服务器存储的数据,前提是需要一台服务器,在联网的情况下通过接口把数据提供给APP,达到更新数据的目的,这种也无法满足如上案例无网的情况;

案例分析:本案例在日常生活中经常遇到,如展厅的触摸屏,内容数据更新频率不高,设备不联网,只用来做简单的查询展示作用;

束手无策:怎么办,无法满足案例需求?还好,可采用第三种解决方案,通过中间件提供数据,即无需设备联网,也无需一台服务器提供数据,也可实现更新数据的目的,那就是通过OTG技术。

什么是OTG

OTG是一种USB传输技术,通过OTG转接线,可以让手机直接访问U盘或数码相机等设备中的文件,还可以连接到键盘、鼠标等外接设备;


三、解决方案

非常产品,就需非常手段,笔者已经解释了OTG技术,那么解决方案应该已经清楚了,简单描述就是支持OTG的设备(一般触摸屏和部分手机都支持OTG技术),通过U盘提供数据源给APP程序;

方案描述:首先将修改后的数据源按照一定格式或文件目录存入U盘,APP启动后从U盘读取数据并存入设备内置SD卡,拔掉U盘后,App从SD卡中读取对应数据展示;

数据流向:U盘——SD卡——App程序——用户界面。

附录一张笔者在项目技术方案中的流程图:



四、代码流程

基本思路已如上图很清晰了,下面就是通过代码来一步一步实现了,核心代码如下:

1 权限检测和开源库

涉及权限,注意6.0的动态权限

<!-- 开机启动 -->

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>


<!-- SD卡写权限 -->

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<!-- SD卡读权限 -->

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<!-- 在SDCard中创建与删除文件权限 -->

<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

U盘插入拔出检测

private void registerReceiver() {
        //监听otg插入 拔出
        IntentFilter usbDeviceStateFilter = new IntentFilter();

        usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);

        usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);

        registerReceiver(mUsbReceiver, usbDeviceStateFilter);

        //注册监听自定义广播

        IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);

        registerReceiver(mUsbReceiver, filter);

    }


    private BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {

        public void onReceive(Context context, Intent intent) {

            String action = intent.getAction();

            switch (action) {

                case ACTION_USB_PERMISSION://接受到自定义广播

                    setMsg("接收到自定义广播");

                    UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {  //允许权限申请

                        if (usbDevice != null) {  //Do something

                            setMsg("用户已授权,可以进行读取操作");

                            readDevice(getUsbMass(usbDevice));

                        } else {

                            setMsg("未获取到设备信息");

                        }

                    } else {
                        setMsg("用户未授权,读取失败");
                    }

                    break;

                case UsbManager.ACTION_USB_DEVICE_ATTACHED://接收到存储设备插入广播

                    UsbDevice device_add = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                    if (device_add != null) {

                        setMsg("接收到存储设备插入广播,尝试读取");
                        redDeviceList();
                    }

                    break;

                case UsbManager.ACTION_USB_DEVICE_DETACHED://接收到存储设备拔出广播

                    UsbDevice device_remove = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                    if (device_remove != null) {

                        setMsg("接收到存储设备拔出广播");

                        usbFiles.clear();//清除

                        cFolder = null;

                    }

                    break;

            }

        }

    };
2 读取U盘数据

这里用到开源库:

//Excel解析库

implementation 'com.hynnet:jxl:2.6.12.1'

//U盘

implementation 'com.github.mjdev:libaums:0.5.5'
private void readDevice(UsbMassStorageDevice device) {

        // before interacting with a device you need to call init()!

        try {

            device.init();//初始化

//          Only uses the first partition on the device

            Partition partition = device.getPartitions().get(0);

            FileSystem currentFs = partition.getFileSystem();

//          fileSystem.getVolumeLabel()可以获取到设备的标识

//          通过FileSystem可以获取当前U盘的一些存储信息,包括剩余空间大小,容量等等

//            Log.d(TAG, "Capacity: " + currentFs.getCapacity());

//            Log.d(TAG, "Occupied Space: " + currentFs.getOccupiedSpace());

//            Log.d(TAG, "Free Space: " + currentFs.getFreeSpace());

//            Log.d(TAG, "Chunk size: " + currentFs.getChunkSize());

            UsbFile root = currentFs.getRootDirectory();//获取根目录

            String deviceName = currentFs.getVolumeLabel();//获取设备标签

            setMsg("正在读取U盘" + deviceName);

            cFolder = root;//设置当前文件对象

            addFile2SD();

        } catch (Exception e) {

            e.printStackTrace();

            setMsg("读取失败,异常:" + e.getMessage());

        }

    }
3 写数据到SD卡
private void addFile2SD() {

    try {

        for (UsbFile file : cFolder.listFiles()) {

            //找到文件夹

            if (file.isDirectory() && file.getName().equals(SD_DIR_NAME)) {

                for (UsbFile subfile : file.listFiles()) {

                    if (subfile.isDirectory()) {//image 文件夹

                        for (UsbFile imgfile : subfile.listFiles()) {

                            usbFiles_img_list.add(imgfile);

                            readFile(imgfile,FileUtil.getSavePath(SD_DIR_NAME+File.separator+SD_DIR_IMAGE_NAME));

                        }

                    } else { //em.xls info.png

                        usbFiles_xls_list.add(subfile);

                        readFile(subfile,FileUtil.getSavePath(SD_DIR_NAME));
                    }

                }

                break;

            }

        }


    } catch (IOException e) {

        e.printStackTrace();

        setMsg("读取出错IO异常:" + e.getMessage());

    }

}
4 App读取数据

根据路径从SD卡中取出所需文件,示例

private void loadExcelData() {

        if (!SDUtils.checkSDcard()) {

            Toast.makeText(getBaseContext(), "该设备没有SD卡,请检查!", Toast.LENGTH_LONG).show();

            return;

        }


        String filePath = SDUtils.getSDPath() + File.separator + SDUtils.SD_DIR_NAME +

                File.separator + SDUtils.SD_FILE_NAME;

        Log.w("TAG", "filePath:" + filePath);


        //解析xls
        ExcelDataLoader excelDataLoader = new ExcelDataLoader();

        excelDataLoader.execute(filePath);
    }


    //在异步方法中 调用

    private class ExcelDataLoader extends AsyncTask<String, Integer, ArrayList<ExcelBean>> {

        //运行子主线程,执行耗时操作,防止主线程阻塞,出现ANR

        @Override

        protected ArrayList<ExcelBean> doInBackground(String... strings) {

            return ExcelUtils.parseXlsDataFromSD(strings[0], 0, getBaseContext());

        }



        //运行于主线程,更新进度

        @Override

        protected void onProgressUpdate(Integer... values) {

            super.onProgressUpdate(values);

        }



        //运行于主线程,耗时操作结束后执行该方法

        @Override

        protected void onPostExecute(ArrayList<ExcelBean> excelBeans) {

            super.onPostExecute(excelBeans);



            initData(excelBeans);

        }

    }



    private void initData(ArrayList<ExcelBean> list) {



        Gson gson = new Gson();

        String jsonStr = gson.toJson(list);



        Log.i("TAG", "JSON_INFO:" + jsonStr);

    }

可能有朋友需要看下Excel表格解析成数据模型的代码,代码如下:

 public static ArrayList<ExcelBean> parseXlsDataFromSD(String xlsPath, int index, Context
            context) {
        ArrayList<ExcelBean> excelBeanArrayList = new ArrayList<>();

        File file = new File(xlsPath);

        try {

            InputStream is = new FileInputStream(file);
            Workbook workbook = Workbook.getWorkbook(is);

            int sheetNum = workbook.getNumberOfSheets();

            Log.d(TAG, "the num of sheets is " + sheetNum);

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

                Sheet sheet = workbook.getSheet(i);

                int sheetRows = sheet.getRows();
                int sheetColumns = sheet.getColumns();

                Log.d(TAG, "the name of sheet is  " + sheet.getName());
                Log.d(TAG, "total rows is 行=" + sheetRows);
                Log.d(TAG, "total cols is 列=" + sheetColumns);

                for (int j = 1; j < sheetRows; j++) {
                    ExcelBean excelBean = new ExcelBean();
                    excelBean.setNum(Integer.valueOf(sheet.getCell(0, j).getContents()));
                    excelBean.setType(Integer.valueOf(sheet.getCell(1, j).getContents()));
                    excelBean.setName(sheet.getCell(2,j).getContents());

                    /*
                    * 如果是日期类型,可以使用DateCell对象提供的方法:

                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                        Cell cell = sheet.getCell(j, i);
                        DateCell dateCell = (DateCell) cell;
                        String date = sdf.format(dateCell.getDate());
                          如果是数值类型,可以使用NumberCell对象提供的方法:

                        Cell cell = sheet.getCell(j, i);
                        NumberCell numberCell = (NumberCell)cell;
                        Double numberValue = numberCell.getValue();
                        String number = numberValue.toString();
                    * */

                    Cell cell_lon = sheet.getCell(3, j);
                    NumberCell numberCell_lon = (NumberCell)cell_lon;
                    Double numberValue_lon = numberCell_lon.getValue();
                    excelBean.setLatitude(numberValue_lon);

                    Cell cell_lat = sheet.getCell(4, j);
                    NumberCell numberCell_lat = (NumberCell)cell_lat;
                    Double numberValue_lat = numberCell_lat.getValue();
                    excelBean.setLongitude(numberValue_lat);

                    excelBean.setContent(sheet.getCell(5,j).getContents());
                    excelBean.setImgPath(sheet.getCell(6,j).getContents());


                    Log.d(TAG, "setLongitude=" + sheet.getCell(3,j).getContents());

                    excelBeanArrayList.add(excelBean);
                }
            }


            workbook.close();

        } catch (Exception e) {
            Log.e(TAG, "read error=" + e, e);
        }

        return excelBeanArrayList;
    }
5 用户界面展示

将获取到的数据做进一步处理,展示到用户界面。


五、方案总结

至此案例需求完美解决,通过Android系统的OTG功能,采用U盘作为数据源提供者,实现了无网络、无服务器来定期更新维护App的特殊要求,目前该解决方案已通过真实项目测试,笔者可提供项目源码或技术协助,注意提供技术或源码均不免费,联系方式:[email protected],其他问题可直接留言,感谢大家的支持以及前辈同行们总结的宝贵经验。

附:笔者推荐优质APP:IT面试宝典 

猜你喜欢

转载自blog.csdn.net/mapboo/article/details/80860117