1.4 实体化编程
听说过fastJSON吗?听说过GSON吗?我面试过很多Android开发人员,他们的项目大多不用fastJSON或者GSON这种实体化编程的思路。他们在获取MobileAPI网络请求返回的JSON数据时,使用JSONObject或者JSONArray来承载数据,然后把返回的数据当作一个字典,根据键取出相应的值。
1.4.1 在网络请求中使用实体
如果仅仅是在转换MobileAPI返回的JSON数据时手动取值也就算了,只要能把取到的值填充到一个实体中就成。但是我见过最糟糕的程序是,把JSON数据直接转成JSONObject或者JSONArray,然后就一直使用这样的对象了,甚至将JSONObject从一个Acivity传递到另一个Activity,要知道JSONObject和JSONArray都是不支持序列化的,所以只好将这种对象封装到一个全局变量中,在跳转前设置,在跳转后取出,写一个这样的糟糕示例:
先给出MobileAPI返回的JSON字符串:
{ "weatherinfo":{ "city":"北京", "cityid":"101010100", "temp":"24", "WD":"南风", "WS":"2级", "SD":"74%", "WSE":"2", "time":"17:45", "isRadar":"1", "Radar":"JC_RADAR_AZ9010_JB", "njd":"暂无实况", "qy":"1005" } }
使用JSONObject的编码如下,代码中的result变量就是上面的JSON字符串,更详细的Demo请参见WeatherByJsonObjectActivity:
try { JSONObject jsonResponse = new JSONObject(result); JSONObject weatherinfo = jsonResponse .getJSONObject("weatherinfo"); String city = weatherinfo.getString("city"); int cityId = weatherinfo.getInt("cityid"); tvCity.setText(city); tvCityId.setText(String.valueOf(cityId)); } catch (JSONException e) { e.printStackTrace(); }
这样的写法有以下两个问题:
1)根据key值取value,我们可以认为这是一个字典。同样的功能实现,字典比实体更晦涩难懂,容易产生bug。
2)每次都要手动从JSONObject或者JSONArray中取值,很烦琐。
接下来我们分别使用fastJSON和GSON,介绍一下实体编程的方式,相应的,请在项目中添加对fastJSON和GSON这两个jar的引用,如图1-6所示。
图1-6 在Android项目中添加fastJSON和GSON的jar包
我们使用fastJSON对上述代码进行改造,要事先准备两个实体WeatherEntity和WeatherInfo,用于JSON字符串到实体之间的映射:
WeatherEntity weatherEntity = JSON.parseObject(content, WeatherEntity.class); WeatherInfo weatherInfo = weatherEntity.getWeatherInfo(); if (weatherInfo != null) { tvCity.setText(weatherInfo.getCity()); tvCityId.setText(weatherInfo.getCityid()); }
使用GSON的方式也差不多:
Gson gson = new Gson(); WeatherEntity weatherEntity = gson.fromJson(content, WeatherEntity.class); WeatherInfo weatherInfo = weatherEntity.getWeatherInfo(); if (weatherInfo != null) { tvCity.setText(weatherInfo.getCity()); tvCityId.setText(weatherInfo.getCityid()); }
这里说一件非常狗血的事情,就是在我们使用fastJSON后,App四处起火,主要表现为:
1)加了符号Annotation的实体属性,一使用就崩溃。
2)当有泛型属性时,一使用就崩溃。
在调试的时候没事,可是每次打签名混淆包,就会出现上述问题。我们几个开发人员曾经查到晚上十点半,最后才发现是混淆文件缺了以下两行代码导致的:
-keepattributes Signature // 避免混淆泛型 -keepattributes *Annotation* // 不混淆注解
1.4.2 实体生成器
当使用实体编程的时候,我有个切身感受,就是每次根据JSON字符串去编写一个实体的时候非常麻烦。不仅仅是Android,当我们进行iOS和WindowsPhone编程时,也需要把JSON转换为相应的实体。
创建实体是一件很烦琐的事情,我们需要一个工具,帮助我们自动生成不同开发平台下的实体。于是便有了EntityGenerater这个工具。就像马云说的那样,工具都是懒人发明的。当初我在推进实体化编程的时候,我的iOS团队早已习惯了字典式取数据的方式,对建立实体这种新机制不是很感兴趣,除非我发明一个能够自动生成实体的工具,提高开发效率。于是我就到开源社区找到了一个类似的工具JSON C#Class Generator [1] ,但是它只能生成WindowsPhone的实体,于是我就稍微改造了一下这个工具,让它同时也可以生成Android和iOS实体,如图1-7所示。
图1-7 实体生成器的左边界面
在左边的文本框输出JSON字符串后,点击Load按钮,就会在右边的列表中预览到实体间的层次关系,以及JSON字符串中的字段与JSON实体中的属性之间的对应关系,如图1-8所示。
同时,这个列表还是可以编辑的。我们可以灵活修改要生成的JSON实体的属性名称。点击Generate按钮,就会在C:\JSON目录下生成JSON实体了。
再后来,考虑到iOS团队每次使用实体生成器都要切换到Windows系统,这是一件何其麻烦的事情啊。于是我就又开发了实体生成器的Web版本,这样就能满足所有团队的需要了。
经过我修改的EntityGenerator项目源码,请到我的博客下载,读者可以根据自己的需要定制自己的实体格式 [2] 。
图1-8 实体生成器的右边界面
1.4.3 在页面跳转中使用实体
在一个页面中,数据的来源有两种:
1)调用MobileAPI获取JSON数据。
2)从上一个页面传递过来。
我们上一小节介绍了如何将从MobileAPI请求到的JSON数据转换为实体,接下来,我们看一下Activity之间的数据应该如何传递。
一种偷懒的办法是,设置一个全局变量,在来源页设置全局变量,在目标页接收全局变量。
以下是来源页MainActivity的代码:
Intent intent = new Intent(MainActivity.this, LoginActivity.class); intent.putExtra(AppConstants.Email, "jianqiang.bao@qq.com"); CinemaBean cinema = new CinemaBean(); cinema.setCinemaId("1"); cinema.setCinemaName("星美"); // 使用全局变量的方式传递参数 GlobalVariables.Cinema = cinema; startActivity(intent);
以下是目标页LoginActivity的代码:
CinemaBean cinema = GlobalVariables.Cinema; if (cinema != null) { cinemaName = cinema.getCinemaName(); } else { cinemaName = ""; }
这里的GlobalVariables类是一个全局变量,定义如下:
public class GlobalVariables { public static CinemaBean Cinema; }
我是不建议使用全局变量的。App一旦被切换到后台,当手机内存不足的时候,就会回收这些全局变量,从而当App再次切换回前台时,再继续使用全局变量,就会因为它们为空而崩溃。
如果必须使用全局变量,就一定要把它们序列化到本地。这样即使全局变量为空,也能从本地文件中恢复。在3.5节,我会专门讲解如何解决全局变量导致App崩溃的问题。
本节我们着重研究如何不使用全局变量,而是使用Intent在页面间来传递数据实体的机制。
首先,在来源页MainActivity要这样写:
Intent intent = new Intent(MainActivity.this, LoginNewActivity.class); intent.putExtra(AppConstants.Email, "jianqiang.bao@qq.com"); CinemaBean cinema = new CinemaBean(); cinema.setCinemaId("1"); cinema.setCinemaName("星美"); // 使用 intent 上挂可序列化实体的方式传递参数 intent.putExtra(AppConstants.Cinema, cinema); startActivity(intent);
其次,目标页LoginActivity要这样写:
CinemaBean cinema = (CinemaBean)getIntent() .getSerializableExtra(AppConstants.Cinema); if (cinema != null) { cinemaName = cinema.getCinemaName(); } else { cinemaName = ""; }
这里的CinemaBean要实现Serializable接口,以支持序列化:
public class CinemaBean implements Serializable { private static final long serialVersionUID = 1L; private String cinemaId; private String cinemaName; public CinemaBean() { } public String getCinemaId() { return cinemaId; } public void setCinemaId(String cinemaId) { this.cinemaId = cinemaId; } public String getCinemaName() { return cinemaName; } public void setCinemaName(String cinemaName) { this.cinemaName = cinemaName; } }
[1] 这个工具的地址如下:http://www.xamasoft.com/json-class-generator/
[2] 项目地址如下:http://files.cnblogs.com/Jax/EntityGenerator.zip。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论