Android将Excel表格解析成json文件

来源:互联网 发布:淘宝联盟qq群推广步揍 编辑:程序博客网 时间:2024/05/21 01:48

Android TV开发,设置模块一般是一个菜单树,也即多级联动菜单,我们可以做成配置项文件,这样做的好处是,不需要根据每个客户需求去控制菜单列表里的菜单项显示隐藏,减少项目维护成本。

那么这个配置项怎么去设计比较好呢?因为需求是产品控制的,我们希望配置项文件由产品来编写和维护,对于产品来说,我们让他去编写json配置文件或者xml文件,有点为难人,术业有专攻,他们更喜欢可视化操作,简单、直观,于是我们想到了Excel表格,因为每次测试的buglist或者产品的功能需求矩阵都是以Excel表形式发给我们的。

按道理来说,我们拿到Menu Tree Excel表,程序去解析这个表格,构建菜单实体类就行了,为什么要再解析成json文件呢?因为每次解析Excel表格获取数据,效率肯定很低,目前知道的Java读取Excel的工具有jxl跟poi方式,两个都有数据量大小限制,并且解析大量数据会很耗时,这个会影响UI绘制,所以我们只希望应用首次安装解析一次产品推送在系统中的Excel表格,然后转成json文件,并保存在系统级别目录中, 以后应用每次启动去解析json配置项获取数据就好。

解析Excel,这里采用jxl,是综合了jxl和poi优缺点以及具体的需求来抉择的。jxl稍微轻量级点,但是不支持xlsx格式,所以只能用 Microsoft Office 2007及之前版本,poi功能强大点,但是要引入的库比较多,使用起来也稍复杂,我们解析Excel表操作一般只会走一次,除非产品变更了需求,会再解析一次,大部分时候,我们程序都是做json转菜单实体类操作。

下面就开始我们的实践吧!

这里写图片描述

步骤一:定义菜单实体类

SettingMenuBean

public class SettingMenuBean implements Parcelable {    public String menuId; //菜单id    public String parentId; //菜单父级id    public String menuName; //菜单名    public String menuIcon; //菜单图标    public String type; //菜单显示样式    public List<SettingMenuBean> menus = new ArrayList<>(); //子菜单    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeString(this.menuId);        dest.writeString(this.parentId);        dest.writeString(this.menuName);        dest.writeString(this.menuIcon);        dest.writeString(this.type);        dest.writeList(this.menus);    }    public SettingMenuBean() {    }    protected SettingMenuBean(Parcel in) {        this.menuId = in.readString();        this.parentId = in.readString();        this.menuName = in.readString();        this.menuIcon = in.readString();        this.type = in.readString();        this.menus = new ArrayList<>();        in.readList(this.menus, SettingMenuBean.class.getClassLoader());    }    public static final Parcelable.Creator<SettingMenuBean> CREATOR = new Parcelable.Creator<SettingMenuBean>() {        @Override        public SettingMenuBean createFromParcel(Parcel source) {            return new SettingMenuBean(source);        }        @Override        public SettingMenuBean[] newArray(int size) {            return new SettingMenuBean[size];        }    };}

因为菜单包含子菜单,存在递归关系,为了方便解析,我们还需定义一个菜单列表实体类

SettingMenuListBean

public class SettingMenuListBean {    private List<SettingMenuBean> menuList;    public List<SettingMenuBean> getMenuList() {        return menuList;    }    public void setMenuList(List<SettingMenuBean> menuList) {        this.menuList = menuList;    }}

步骤二:读取excel表格,并转换成json字符串, 这里分别使用jxl和gson工具

    public static String generateJson(Context context) {        String filePath = context.getFilesDir().getPath() + "/setting_config.xls";        File readFile = new File(filePath);        if (!readFile.exists()) {            return null;        }        List<SettingMenuBean> allMenus = new ArrayList<>();        try {            FileInputStream inStream = new FileInputStream(readFile);            Workbook wb = Workbook.getWorkbook(inStream);            Sheet sheet = wb.getSheet(0);            int row = sheet.getRows();            for (int i = 1; i < row; i++) {                Cell menuId = sheet.getCell(0, i);                Cell parentId = sheet.getCell(1, i);                Cell menuName = sheet.getCell(2, i);                Cell menuIcon = sheet.getCell(3, i);                Cell type = sheet.getCell(4, i);                SettingMenuBean menuBean = new SettingMenuBean();                menuBean.menuId = menuId.getContents();                menuBean.parentId = parentId.getContents();                menuBean.menuName = menuName.getContents();                if (!TextUtils.isEmpty(menuIcon.getContents())) {                    menuBean.menuIcon = menuIcon.getContents();                }                if (!TextUtils.isEmpty(type.getContents())) {                    menuBean.type = type.getContents();                }                allMenus.add(menuBean);            }            wb.close();        } catch (Exception e) {            e.printStackTrace();        }        List<SettingMenuBean> finalMenus = new ArrayList<>();        for (SettingMenuBean menuBean : allMenus) {            if (!TextUtils.isEmpty(menuBean.parentId)) {                menuBean.menus = getSubMenuByPid(menuBean.menuId, allMenus);                if ("0".equals(menuBean.parentId)) { // 一级菜单                    finalMenus.add(menuBean);                }            }        }        SettingMenuListBean menus = new SettingMenuListBean();        menus.setMenuList(finalMenus);        Gson gson = new Gson();        return gson.toJson(menus);    }
    /**     * 根据parentId获取子菜单     *     */    private static List<SettingMenuBean> getSubMenuByPid(@NonNull String pId, List<SettingMenuBean> allMenus) {        List<SettingMenuBean> list = new ArrayList<>();        for (SettingMenuBean menuBean : allMenus) {            if (pId.equals(menuBean.parentId)) {                list.add(menuBean);            }        }        return list;    }

附:Excel表里面某一列有跨几行的场景处理办法

        try {            FileInputStream inStream = new FileInputStream(readFile);            Workbook wb = Workbook.getWorkbook(inStream);            Sheet sheet = wb.getSheet(0);            int row = sheet.getRows();            Range[] rangeCell = sheet.getMergedCells();            for (int i = 0; i < row; i++) {                String name = getData(0, i, sheet, rangeCell);                String school = getData(1, i, sheet, rangeCell);                String major = getData(2, i, sheet, rangeCell);                Person person = new Person();                person.setName(name);                person.setSchool(school);                person.setMajor(major);                persons.add(person);            }            wb.close();        } catch (Exception e) {            e.printStackTrace();        }
    private static String getData(int column, int row, Sheet sheet, Range[] rangeCell) {        String str = sheet.getCell(column, row).getContents();        for (Range r : rangeCell) {            if (row > r.getTopLeft().getRow()                    && row <= r.getBottomRight().getRow()                    && column >= r.getTopLeft().getColumn()                    && column <= r.getBottomRight().getColumn()) {                str = sheet.getCell(r.getTopLeft().getColumn(),                        r.getTopLeft().getRow()).getContents();            }        }        return str;    }

步骤三:将生成的json配置文件保存在指定的系统目录中(最好跟表格保存在一个目录中,方便json文件和Excel文件数据同步)

    public static void saveJsonFile(String jsonStr, String filePath) {        FileOutputStream outStream = null;        boolean dirMake = false;        boolean fileMake = false;        try {            File file = new File(filePath);            if (!file.exists()) {                File dir = new File(file.getParent());                dirMake = dir.mkdirs();                fileMake = file.createNewFile();            }            if (dirMake && fileMake) {                outStream = new FileOutputStream(file);                if (!TextUtils.isEmpty(jsonStr)) {                    outStream.write(jsonStr.getBytes());                }                outStream.flush();            }        } catch (IOException e) {            Log.d(TAG, "saveJsonFile: IOException");        } finally {            try {                if (outStream != null) {                    outStream.close();                }            } catch (IOException e) {                Log.d(TAG, "saveJsonFile: IOException");            }        }    }

最终生成的json配置文件(菜单实体类中的menuId和parentId只作为解析Excel表格表示菜单层级关系用,实际json配置项,不需要这两个字段,我们获取子菜单,可以直接”subMenuList = menu.menus”,所以需要手动去掉这两项)

{    "menus": [        {            "menuName": "@STR_PICTURE",            "menuIcon": "@picture_bg_selecctor",            "type": "SUBMENU",            "menus": [                {                    "menuName": "@STR_PICTURE_PRESET",                    "type": "SELECT"                },                {                    "menuName": "@STR_BACKLIGHT",                    "type": "SEEKBAR"                }            ]        },        {            "menuName": "@STR_SOUND",            "menuIcon": "@voice_bg_selector",            "type": "SUBMENU",            "menus": [                {                    "menuName": "@STR_SOUND_PRESET",                    "type": "SUBMENU",                    "menus": [                        {                            "menuName": "@STR_SOUND_MODE",                            "type": "POPVIEW"                        }                    ]                },                {                    "menuName": "@STR_TV_PLACEMENT",                    "type": "POPVIEW"                }            ]        }    ]}

update(2017/08/22)

1、配置文件中菜单icon和name加“@”标识,表明这是资源名,考虑到多语言翻译问题,配置项不直接使用菜单名。

2、添加资源名映射到xml资源id方法

    /**     * @param context     * @param name    @STR_PICTURE     * @param type    string     * @return R.string.STR_PICTURE     */    public static int getResourceId(Context context, String name, String type) {        if (!TextUtils.isEmpty(name)) {            name = name.replaceAll("@", "");        }        try {            return context.getResources().getIdentifier(name, type, context.getPackageName());        } catch (Exception e) {            return 0;        }    }

3、增加读取配置项文件的方法

      public static String readJsonFile(String filePath) {        String str;        FileInputStream inStream = null;        try {            File readFile = new File(filePath);            if (!readFile.exists()) {                return null;            }            inStream = new FileInputStream(readFile);            ByteArrayOutputStream stream = new ByteArrayOutputStream();            byte[] buffer = new byte[1024];            int length;            while ((length = inStream.read(buffer)) != -1) {                stream.write(buffer, 0, length);            }            str = stream.toString();            stream.close();            return str;        } catch (IOException e) {            return null;        } finally {            try {                if (inStream != null) {                    inStream.close();                }            } catch (IOException e) {                Log.d(TAG, "readJsonFile: IOException");            }        }    }

3、增加动态申请SD卡读写权限方法

AndroidManifest.xml

    <!-- SD卡写权限 -->    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>    <!-- SD卡读权限 -->    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Java代码

    private static final int REQUEST_EXTERNAL_STORAGE = 1;    private static String[] PERMISSIONS_STORAGE = {            "android.permission.READ_EXTERNAL_STORAGE",            "android.permission.WRITE_EXTERNAL_STORAGE"};    public static void verifyStoragePermissions(Context ctx) {        try {            //检测是否有写的权限            int permission = ActivityCompat.checkSelfPermission(ctx,                    "android.permission.WRITE_EXTERNAL_STORAGE");            if (permission != PackageManager.PERMISSION_GRANTED) {                // 没有写的权限,去申请写的权限,会弹出对话框                ActivityCompat.requestPermissions((Activity) ctx, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);            }        } catch (Exception e) {            e.printStackTrace();        }    }

4、 格式化时间显示

    /**     * 从时间(毫秒)中提取出时间(时:分:秒)     *     */    public static String getTimeFromMillisecond(long millisecond) {        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());        Date date = new Date(millisecond);        return simpleDateFormat.format(date);    }