电表 × Android:Java 转 Kotlin

随便写写。

距离电表的第一个正式版发布已经过去了快五个月,而距离最开始写电表过去了九个月。这应该是我写了这么多年的代码以来,坚持时间最长的项目了吧。

目前 Android 版本的开发差不多趋于稳定,也没什么要再加入的新功能了。所以就想着,在一边考虑 iOS 版本怎么做的时候,一边来对 Android 开发进行一些总结。因此打算写一个系列文章,记录一下在电表的 Android 版本开发中遇到的一些问题。

这个系列的文章没有什么固定的顺序,一般不会由浅入深什么的,可能哪天发现有一个值得记录下来的地方就随手写了。也可能会写一些在别人看来非常简单的东西,甚至可能会针对一些问题给出不那么完美甚至根本就是错误的解决方案。但不管怎么样至少是我开始学习 Android 开发以来一些自己的见解。如果有人无意中看到这些文章,并且给予批评指正或者意见建议,我当然万分感激。

另外就是,所有文章中列出的代码应该都是来自在电表中我实际写过的,结合实际项目来考虑一些事情的话应该比纯理论要更容易理解。

除了这一篇之外,系列中之后的文章都以 Kotlin 为主要语言。

下面进入这个系列的第一篇。

Java 转 Kotlin 有啥好说的

也许有人会说,Android Studio 里 Java 转 Kotlin 不就是 Ctrl + Shift + Alt + K 一组快捷键的事嘛,有啥好写的。确实,四个键一按,完事儿。

但是,Android Studio 的自动转换会留下一些小问题,虽然功能上不影响,但是从编码角度考虑,却是不那么符合规范的。这些小问题就需要我们手动来修改。

有哪些要手动改呢

我只讲我遇到的。

文本分割

我们知道,Java 中可以直接对 String 调用其成员方法 split 实现文本的分割。在电表中,在学校开放的情况下,可以查询单科目的期末成绩详情。这时学校服务器会返回一组 Json 数据,其中有一个字段的值是类似于 总评成绩 = 期末成绩*0.4 + 平时测试*0.1 + 课堂表现*0.15 + 口语展示*0.1 + 平时作业*0.25 这样的。而在电表中最终呈现出来的是下面这样的:

总分构成:
期末成绩: 40.0%
平时测试: 10.0%
课堂表现: 15.0%
口语展示: 10.0%
平时作业: 25.0%

百分比是根据原始数据中的小数转换得到的。这就需要我们对原始数据进行分割处理之后再从 String 转为 double。电表中的处理逻辑如下:

for (String c : components.split("\n")) {
    String componentsName = c.split("\\*")[0];
    String componentsRatio = Double.parseDouble(c.split("\\*")[1]) * 100 + "%";
    sb.append(componentsName)
            .append(": ")
            .append(componentsRatio)
            .append("\n");
}

顺便一提,这里用了 StringBuilder 的链式调用,好处是写起来更简单,看起来更直观。

而在经过 Android Studio 的 Java 转 Kotlin 过程之后,这段代码变成了:

for (c in components.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
    val componentsName = c.split("\\*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
    val componentsRatio = (java.lang.Double.parseDouble(c.split("\\*".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1]) * 100).toString() + "%"
    sb.append(componentsName)
            .append(": ")
            .append(componentsRatio)
            .append("\n")
}

乍一看好像没啥问题,也能跑起来。但仔细一看,我只是想根据星号分割一下,为什么会给我整出来正则表达式啊。这种就属于将简单问题复杂化了。于是我们手动修改如下:

for (c in components.split("\n")) {
    val componentsName = c.split("*")[0]
    val componentsRatio = (java.lang.Double.parseDouble(c.split("*")[1]) * 100).toString() + "%"
    sb.append(componentsName)
            .append(": ")
            .append(componentsRatio)
            .append("\n")
}

split 函数本身就可以接受一个字符串作为参数,并返回一个数组,所以我们没有要大动干戈先把字符串转为正则表达式再把匹配结果转回数组。

getset

Kotlin 里字段相较于 Java 来说,其本身已经实现了 gettersetter,所以我们无需再手动去写 getXXXsetXXX 方法。这一点在 Android Studio 的 Java 转 Kotlin 的过程中也能得到体现。如果检测到我们调用了 getXXXsetXXX,会自动精简。

举个例子,在 Activity 中我们难免会遇到需要对 Action Bar 操作的时候,比如设置标题、显示返回按钮等。在 Java 中,我们需要这么写:

ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
    actionBar.setDisplayHomeAsUpEnabled(true);
    actionBar.setTitle("成绩查询");
}

而在 Kotlin 中,得益于字段的优化和可空类型,上面的代码可以精简为两行:

supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.title = "成绩查询"

另外是 Gson,这是常用的用来序列化和反序列化 Json 的一个库。考虑这么一段在登录学校信建处的一个系统时返回的 Json 数据:

{
    "e": 0,
    "m": "操作成功",
    "d": {}
}

在不编写实体类,手动使用 Gson 解析时,我们写出的代码是这样:

JsonParser.parseString(data)
        .getAsJsonObject()
        .get("e")
        .getAsInt();

在经过 Kotlin 转换之后,变成了:

JsonParser.parseString(data)
        .asJsonObject["e"]
        .asInt

如果使用过 .NET 中的 Newtonsoft Json(或者叫 Json.NET),可能会觉得这种写法有点眼熟。是的,在 C# 中,我们可以这么写:

JObject.Parse(data)["e"].ToObject<int>();

Kotlin 中这么处理本身没什么问题,就是 Gson 本身是 Java 编写的,所以看起来比较奇怪而已——当然我期待哪天可以像 Newtonsoft Json 这么写,那样就太舒服了。

到这里画风还算正常。但前几天我翻代码的时候,看到一个比较难受的地方。目前 Android 开发中常用 Retrofit 进行网络请求,于是我们需要写请求的接口以提供给 Retrofit 使用。电表中利用信建处的系统获取校园网余量部分的请求接口如下:

@GET("srun/wap/srun/index")
Observable<String> getNetworkInfo();

在转为 Kotlin 之后变成了:

@get:GET("srun/wap/srun/index")
val networkInfo: Observable<String>

因为原来的方法以 get 开头,所以被转换成了一个字段,还贴心地加上了 @get 注解方便生成 Java getter。但是接口里并非所有的方法都以 get 开头,这么放在源文件里看起来是比较混乱的。因此我们手动恢复成方法:

@GET("srun/wap/srun/index")
fun getNetworkInfo(): Observable<String>

匿名类

在 Android 开发中,如果使用了 RxJava,就不可避免地会写很多 Observer。在 Java 中我们可以利用匿名类来减少变量的创建。考虑电表中获取一言时 RxJava 的 Observer:

Api.INSTANCE.getHitokotoApi()
        .get(categories[hitokotoType - 1])
        .compose(Transformer.switchSchedulers())
        .subscribe(new CommonObserver<Hitokoto>() {
            @Override
            protected void onError(String errorMsg) {
                Toast.makeText(parentActivity, "获取一言失败: " + errorMsg, Toast.LENGTH_SHORT).show();
                }

            @Override
            protected void onSuccess(Hitokoto data) {
                hitokoto.getContent().set(data.getHitokoto() + "\n\n——" + data.getFrom());
            }
});

这个在自动转换成 Kotlin 时是会出错的。由于 Kotlin 完全舍弃了 new 关键字,Android Studio 并不能正确处理此时 CommonObserver 这个匿名类,因此我们在自动转换之后,还需要手动修正,加上 object 关键字:

Api.hitokotoApi.get(categories[hitokotoType - 1])
        .compose(Transformer.switchSchedulers())
        .subscribe(object : CommonObserver<Hitokoto>() {
            override fun onError(errorMsg: String) {
                liveData.value = "厚德、求真、砺学、笃行。\n\n——西安电子科技大学校训"
            }

            override fun onSuccess(data: Hitokoto) {
                liveData.value = "${data.hitokoto}\n\n——${data.from}"
            }
})

总结

基本上我在电表的开发过程中遇到的 Java 转 Kotlin 的问题就这几个。总的来说都不是什么大问题(如果完全人工转换那就不会出错了),挺好解决的。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇