返回介绍

1.4 实体化编程

发布于 2024-08-17 23:46:12 字数 5939 浏览 0 评论 0 收藏 0

听说过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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文