在网络请求中使用实体

听说过 fastJSON 吗?听说过 GSON 吗?我面试过很多 Android 开发人员,他们的项目大多不用 fastJSON 或者 GSON 这种实体化编程的思路。他们在获取 MobileAPI 网络请求返回的 JSON 数据时,使用 JSONObject 或者 JSONArray 来承载数据,然后把返回的数据当作一个字典,根据键取出相应的值。 

如果仅仅是在转换 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 所示。

我们使用 fastJSON 对上述代码进行改造,要事先准备两个实体 WeatherEntity WeatherInfo,用于JSON 字符串到实体之间的映射:

1-6Android 项目中添加 fastJSONGSON jar

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* // 不混淆注解 

实体生成器

当使用实体编程的时候,我有个切身感受,就是每次根据 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 版本,这样就能满足所有团队的需要了。 

在页面跳转中使用实体

在一个页面中,数据的来源有两种:
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;}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值