数天前我将我java开发的工程,全部转换成了kotlin形式的工程。如果你也想做,本身也有一定的java开发安卓程序的功底。本文将比较适合你。
创建kotlin工程,拷贝类文件xml文件等核心文件到工程目录下,形成一个kotlin底子的java代码组成的工程,然后通过ctrl +shift +alt +k 快捷代码逐个转换为kotlin。转换的结果相对比较成功。将转换中遇到的问题总结如下。
1. 循环,改成filter map的使用
那些只是听说过kotlin的小白,在转换前,请参考:最适合Android程序员的kotlin笔记——集合操作
很多普通的操作,AS可以帮我们自动转,但是唯独集合(AS工具的薄弱环节)这里可以优化的比较多。所以建议熟悉。
2. 字符串非空判断改成kotlin形式。
快捷键转换后,还是equal("")形式,改成纯正的kotlin风格:
一下方法为kotlin特有:
.isEmpty()
.isBlank()
.isEmptyOrNull()
.isNullOrBlank()
类似的,点出来,自己看用那个合适。
3. 不必要的bindview
原来的:
@BindView(R.id.ad_image)
ImageView ad_image;
@BindView(R.id.countdownProgressView)
TextView countdownProgressView;
问题: 类似此类的代码,转换后还会存在。
解决办法: 转换前,对齐控件的变量名和id名。转换后,直接删掉。
你会惊奇的发现,.kt 文件直接引用了xml文件。代码中可以直接使用id来对view进行操作了。
4.丑陋的equals
if (!StringUtils.equals(password, password2)) {
toast(resources.getString(R.string.two_password_input_error))
return
}
改成等号,不等号。
java中的 equals ,kotlin中可用 == 替代,java中的比较对象的引用,kotlin中,用===替代。
5. 容易错转的map
在进行此类型的转换和修正前,请百度一下kotlin map集合操作。
以下是原java代码:
private String getTypemane(int id) {
String aa = "";
List<Map<Integer, String>> typelist = listDataSaveUtil.getDataList();
Map<Integer, String> map = typelist.get(0);
for (Map.Entry<Integer, String> str : map.entrySet()) {
if (String.valueOf(id).equals(str.getKey() + "")) {
aa = str.getValue();
}
}
return aa;
}
ktlin错误转换为:
private fun getTypemane(id: Int): String {
var aa = ""
val typelist = listDataSaveUtil!!.getDataList<Map<Int, String>>()
val map = typelist[0]
for ((key, value) in map) {
if (id == key) {
aa = value
}
}
return aa
}
运行报错,不能将int 转number,我给他改成了:
private fun getTypemane(id: Int): String {
var aa = ""
val typelist = listDataSaveUtil!!.getDataList<Map<Int, String>>()
val map = typelist[0]
if(map.contains(id)){
aa=map.getValue(id)
}
return aa
}
6.不够清爽的if else 转换
java代码:
private void isShowRv(AllTypeBean body) {
if (getAreaList(body).size() == 1) {
mRvArea.setVisibility(View.GONE);
} else {
mRvArea.setVisibility(View.VISIBLE);
}
if (body.getClassX().size() == 1) {
mRvStyle.setVisibility(View.GONE);
} else {
mRvStyle.setVisibility(View.VISIBLE);
}
if (body.getType().size() == 1) {
mRvAllTvStyle.setVisibility(View.GONE);
} else {
mRvAllTvStyle.setVisibility(View.VISIBLE);
}
if (body.getYear().size() == 1) {
mRvYear.setVisibility(View.GONE);
} else {
mRvYear.setVisibility(View.VISIBLE);
}
}
使用快捷键转换,怎么结果还是一堆if?!!
还是难看,能否用when(kotlin最擅长的)改造下,动手,效果如下:
when {
getAreaList(body!!).size == 1 -> mRvArea!!.visibility = View.GONE
getAreaList(body!!).size != 1 -> mRvArea!!.visibility = View.VISIBLE
body!!.classX!!.size == 1 -> mRvStyle!!.visibility = View.GONE
body.classX!!.size != 1 -> mRvStyle!!.visibility = View.VISIBLE
body!!.type!!.size == 1 -> mRvAllTvStyle!!.visibility = View.GONE
body.type!!.size != 1 -> mRvAllTvStyle!!.visibility = View.VISIBLE
body!!.year!!.size == 1 -> mRvYear!!.visibility = View.GONE
body.year!!.size != 1 -> mRvYear!!.visibility = View.VISIBLE
}
运行,程序业务逻辑出错!!!!,断点研究发现,代码只走到when中第二行,就break了。后面没走。好傻!生手!
正确的写法:如下:
when {
getAreaList(body!!).size == 1 -> mRvArea!!.visibility = View.GONE
getAreaList(body!!).size != 1 -> mRvArea!!.visibility = View.VISIBLE
}
when {
body!!.classX!!.size == 1 -> mRvStyle!!.visibility = View.GONE
body.classX!!.size != 1 -> mRvStyle!!.visibility = View.VISIBLE
}
when {
body!!.type!!.size == 1 -> mRvAllTvStyle!!.visibility = View.GONE
body.type!!.size != 1 -> mRvAllTvStyle!!.visibility = View.VISIBLE
}
when {
body!!.year!!.size == 1 -> mRvYear!!.visibility = View.GONE
body.year!!.size != 1 -> mRvYear!!.visibility = View.VISIBLE
}
一堆when,不爽。能否再简化下,聪明的你一定想到了,还是用回if else
if在 java中是语句,kotlin中是表达式(表达式有值,语句不返回值)
改!:
mRvArea!!.visibility = if (getAreaList(body!!).size == 1) View.GONE else View.VISIBLE
mRvStyle!!.visibility = if (body!!.classX!!.size == 1) View.GONE else View.VISIBLE
mRvAllTvStyle!!.visibility = if (body!!.type!!.size == 1) View.GONE else View.VISIBLE
mRvYear!!.visibility = if (body!!.year!!.size == 1) View.GONE else View.VISIBLE
总结: when结构,替换的时候,典型的场景应该是switch case 那种(一旦某一个条件满足,直接跳出函数体)
普通的if根据需求,千万别像我一样转错了。但是如果是连续的else if结构,也可以替换。
if (customView != null) {
hideCustomView()
} else if (wv_web_view!!.canGoBack()) {
wv_web_view!!.goBack()
} else {
finish()
}
转成如下:
when{
customView != null-> hideCustomView()
wv_web_view!!.canGoBack()-> wv_web_view!!.goBack()
else->finish()
}
还有这种单if的垃圾代码,都很适合转when
注意这种单if和上面的单if的不同,这正是我这个条目想强调的部分。
if (VipType == HdVipType) {
userMessage!!.vip!!.isHdvip = true
}
if (VipType == DlanVipType) {
userMessage!!.vip!!.isDlanvip = true
}
改动秘诀:
看是否能转换为else if 能转换成else if的 ,在转when,遵循这个原则。
7、漏失的toString()
例子:
private Map<Integer, String> getIntegerStringMap(HomeTypeBean body) {
Map<Integer, String> map = new HashMap<>();
for (HomeTypeBean.TypeBean categoryBean : body.getType()) {
map.put(categoryBean.getType_id(), categoryBean.getType_name());
}
return map;
}
转换Kotlin后:
private fun getIntegerStringMap(body: HomeTypeBean): Map<Int, String> {
val map = HashMap<Int, String>()
for (categoryBean in body.type!!) {
map[categoryBean.type_id] = categoryBean.type_name
}
return map
}
编译器出现报错将for循环中map一行,下划线标红。
private fun getIntegerStringMap(body: HomeTypeBean): Map<Int, String> {
val map = HashMap<Int, String>()
for (categoryBean in body.type!!) {
map[categoryBean.type_id] = categoryBean.type_name.toString()
}
return map
}
原来是没有添加toString(); 这种情况,as有报错,并有提示。但此处Studio给出的提示不易懂,注意!
8. 集合的转换不够彻底:
例子java代码:
private List<String> bornTypeList(HomeTypeBean body) {
List<String> typeList = new ArrayList<>();
for (HomeTypeBean.TypeBean categoryBean : body.getType()) {
typeList.add(categoryBean.getType_name());
}
return typeList;
}
as工具转换为kotlin代码后:
private fun bornTypeList(body: HomeTypeBean): List<String> {
val typeList = ArrayList<String>()
for (categoryBean in body.type!!) {
typeList.add(categoryBean.type_name.toString())
}
return typeList
}
太丑了,羞于使用,优化,代码如下:
private fun bornTypeList(body: HomeTypeBean): List<String> {
return body.type!!.map { it.type_name.toString() }
}
9. 失灵的onclick
使用bindview的条件下,如下转换后的kotlin代码,出现业务问题,点击事件失灵:
@OnClick(R.id.searcher_view, R.id.play_history_image, R.id.all)
fun onClick(v: View) {
//搜索
if (v === searcher_view) {
ActivityUtils.startActivity(Intent(activity, SearcherActivity::class.java))
return
}
//播放记录
if (v === play_history_image) {
ActivityUtils.startActivity(PlayHistoryActivity::class.java)
return
}
//全部
if (v === all) {
val intent = Intent(activity, AllTypeActivity::class.java)
intent.putExtra(ConstantUtils.TYPE_NAME, videoType)
intent.putExtra(ConstantUtils.TYPE_ID, typeId)
ActivityUtils.startActivity(intent)
}
}
10. 无法优雅转换的单例
在java中我有如下代码:
public class DataBeanModel {
private SparseArray<List<PlayVosBean.VodsBean.DataBean>> listHashMap = new SparseArray<>();
private static class SingletonHolder {
private static final DataBeanModel INSTANCE = new DataBeanModel();
}
private DataBeanModel() {
}
public static final DataBeanModel getInstance() {
return SingletonHolder.INSTANCE;
}
public void clearAll(){
listHashMap.clear();
}
public void putValue(int Page, ArrayList<PlayVosBean.VodsBean.DataBean> dataBeans){
listHashMap.put(Page, dataBeans);
}
public List<PlayVosBean.VodsBean.DataBean> getSetByPage(int position){
return listHashMap.get(position);
}
public SparseArray<List<PlayVosBean.VodsBean.DataBean>> getAll(){
return listHashMap;
}
}
as工具转换后:
class DataBeanModel private constructor() {
val all = SparseArray<List<PlayVosBean.VodsBean.DataBean>>()
private object SingletonHolder {
private val INSTANCE = DataBeanModel()
}
fun clearAll() {
all.clear()
}
fun putValue(Page: Int, dataBeans: ArrayList<PlayVosBean.VodsBean.DataBean>) {
all.put(Page, dataBeans)
}
fun getSetByPage(position: Int): List<PlayVosBean.VodsBean.DataBean> {
return all.get(position)
}
companion object {
val instance: DataBeanModel
get() = SingletonHolder.INSTANCE
}
}
已经非常臃肿,浓浓的java式kotlin,怎么办?
正确的做法:
- 注释和单例相关的方法:
private static class SingletonHolder {
private static final DataBeanModel INSTANCE = new DataBeanModel();
}
private DataBeanModel() {
}
public static final DataBeanModel getInstance() {
return SingletonHolder.INSTANCE;
}
- 按下ctrl shift alt k 转换成kotlin,将类关键字class 替换为Object
- 删除注释部分
object DataBeanModel {
val all = SparseArray<List<PlayVosBean.VodsBean.DataBean>>()
fun clearAll() {
all.clear()
}
fun putValue(Page: Int, dataBeans: ArrayList<PlayVosBean.VodsBean.DataBean>) {
all.put(Page, dataBeans)
}
fun getSetByPage(position: Int): List<PlayVosBean.VodsBean.DataBean> {
return all.get(position)
}
}
- 调用的部分,改为(中间加INSTANCE):
DataBeanModel.INSTANCE.clearAll();
值得注意的是,当我在运行业务逻辑的时候,提示我,getSetByPage 这个方法: return all.get(position)
这个代码,里面all已经强制要求为空。导致我程序报错。不知道all为什么会为空。回滚吧。
10. 无法正确转换+=1
我有如下java代码:
newHost = appMessageBean.getHostUrlList().get(hostPosition += 1);
直接as转换后,在代码下面直接爆红。
解決方案:在外面写hostPosition+=1, 然后再用as工具转换,正确代码。
hostPosition = hostPosition + 1
newHost = appMessageBean!!.hostUrlList!![hostPosition]
11. 变异的trim方法:
我在java中的trim方法,kotlin转换为了如下形式:
result.addProperty("mobile", etPhone!!.text.toString().trim { it <= ' ' })
注意:kotlin的trim()方法意思发生了变异,是指去除前后的空格。和java中的trim方法等价的就是:.trim { it <= ' ' } 转换并没有发生错误,慢慢学习吧。编译器的快捷键转换是正确的。这个给编译器点个赞。
12. 不够优雅的工具类
kotlin中,方法不一定会写在类内部。操作方法,
- 工具类去掉类的包裹,直接全部都是方法
- 类文件的第一行,指明名字即可,如下:
@file:JvmName("AesUtils")
package com.play.myapplication.helper
- java文件中该怎么用怎么用
- 如果是在kotlin文件中,直接写方法即可,前面的“类点”也给省略了
13. 错误转换的get 方法。
默认as的转换工具,会对,get 开头的方法,进行重新梳理。
有以下java代码:
/**
* 标题栏id
*/
protected abstract int getTitleBarId();
/**
* 引入布局
*/
protected abstract int getLayoutId();
用来子类复写,父类回调。结果AS自动转换:
/**
* 标题栏id
*/
protected abstract val titleBarId: Int
/**
* 引入布局
*/
protected abstract val layoutId: Int
荒唐呀,我这个是一个父类方法,子类需要复写。这时候就会导致更多的转换异常,解决办法,在java阶段,改成重命名成其他(比如got)开头的方法。然后再用工具转换。
注意不要用get set开头,除非确定是当成成员处理。
13. 不够优雅的弱引用。
有弱引用代码,转换后,累赘不够优雅,并且
弱引用使用处,如下的位置,还会爆红。
weakReference.get().setAppMessageBeanFromSP()
添加双感叹号。
weakReference.get()!!.setAppMessageBeanFromSP()
爆红解决,单仍不够优雅。
解决方案:
采用委托机制处理
第一步,创建weak类:
class Weak<T : Any>(initializer: () -> T?) {
var weakReference = WeakReference<T?>(initializer())
constructor() : this({
null
})
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return weakReference.get()
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
weakReference = WeakReference(value)
}
}
第二步
生命的地方这样写,
var act:MyActivity? by Weak()
引用的地方:
act.setAppMessageBeanFromSP()
14. 意外的问号
OkGoUtils.postRequest(ConstantUtils.getUrlConstant().getaskvip, JsonObject.toString(), new JsonCallBack<UserandVip>(UserandVip.class, this) {
@Override
public void onSuccess(Response<UserandVip> response) {
}
)}
转换完之后,给我添加一个:
OkGoUtils.postRequest<UserandVip>(
ConstantUtils.getUrlConstant().getaskvip,
JsonObject.toString(),
object : JsonCallBack<UserandVip**?**>(
*UserandVip::class.java, this*
) {
*override fun onSuccess*(response: Response<UserandVip>) {
super.onSuccess(response)
}
)}
很奇怪,导致斜体的部分都在飘红。
去掉问号就可以了。这里没必要if null
15. 错误转换的json文件:重要#############巨坑巨坑#################
之前我们项目中存在一个签到的问题,原来里面是用json转换的,字段是signin , 使用kotlin转换后,kotlin自动会为boolean类型的字段加上is,变成如上图所示,结果倒是json变换的时候,请求返回的bean中,接收不到新的数据singin=true。获取的isSignin=false。破坏了原来代码的逻辑。大罪大罪!!!!!!!!!!!!!!!!!转换方法要坑满才能明白过来呀
16. 点击事件
源代码:
btn_stop_dlan.setOnClickListener(this::onClick);
btn_iv_dlan_full.setOnClickListener(this::onClick);
btn_iv_add_volume.setOnClickListener(this::onClick);
btn_iv_delete_volume.setOnClickListener(this::onClick);
btn_ad_x.setOnClickListener(this::onClick);
iv_pause_ad.setOnClickListener(this::onClick);
btn_video_tv.setOnClickListener(this::onClick);
btn_video_hd.setOnClickListener(this::onClick);
btn_video_next.setOnClickListener(this::onClick);
btn_video_skip.setOnClickListener(this::onClick);
rl_close_father.setOnClickListener(this::onClick);
rl_pause_father.setOnClickListener(this::onClick);
rl_close_father.setOnClickListener(this::onClick);
btn_video_start.setOnClickListener(this::onClick);
转换后的代码:
但是下面报红线
btn_stop_dlan.setOnClickListener(OnClickListener { v: View ->
onClick(
v
)
})
btn_iv_dlan_full.setOnClickListener(OnClickListener { v: View ->
onClick(
v
)
})
btn_iv_add_volume.setOnClickListener(OnClickListener { v: View ->
onClick(
v
)
})
btn_iv_delete_volume.setOnClickListener(OnClickListener { v: View ->
onClick(
v
)
})
btn_ad_x.setOnClickListener(OnClickListener { v: View ->
onClick(
v
)
})
17. 缺失的hanlder类型
java代码(减缩版)
private Handler mDialogHandler;
public void initTimerDialog() {
btn_btn_finish_0.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDialogHandler.removeCallbacksAndMessages(null);
}
});
};
快捷键转换(减缩版)
private var mDialogHandler: Handler? = null
fun initTimerDialog() {
btn_btn_finish_0!!.setOnClickListener(object : OnClickListener {
override fun onClick(v: View) {
mDialogHandler.removeCallbacksAndMessages(null)
}
})
}
转换后:此行报错!
mDialogHandler.removeCallbacksAndMessages(null)
订正:
(mDialogHandler as Handler).removeCallbacksAndMessages(null)
Alt+enter中有,只要合理选择就行。
第二个坑:
简化后的java代码如下:
public Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
String s = msg.obj.toString();
runCount();
}
};
转换后,第一行,handler名字这里报错:
怎办?看了alt+enter的提示,看不懂。
解决方案就是:
不用用handler做名字,换个hanlderOne啥的,handler这个名字好像已经被占用了。
18.IDE 自带的插件转换 Java 代码, 项目上线后后台空指针Error增加(重要#############巨坑巨坑#################)
IDE 里面的插件 "Covert Java File To Kotlin File" 早已被大家熟知,要是不知道的小伙伴,赶紧写个 Java 文件,尝试点击 Android Studio 工具栏的 Code 下面的 "Convert Java File To Kotlin File"。
这样的方式足够地快,但却会出现很多很多的 !!,这是由于 Kotlin 的 null safety 特性。这是 Kotlin 在 Android 开发中的很牛逼的一大特性,想必不少小伙伴都被此 Android 的 NullPointException 困扰许久。我们直接转换 Java 文件造成的各种 !! ,其实也就意味着你可能存在潜在的未处理的 KotlinNullPointException。
19. 资源id和object对象的变量名重名
if (FilmAllModel.INSTANCE.getTotal() == 1)
转换为kotlin
if(total==1)
此处直接报错,阅读提示,发现,我们的一个资源id也叫total,重名导致,报错。
20. 不变的枚举
枚举对开发者非常友好。数量有限的元素,描述性的名字,因此大幅提高了代码的可读性。另外他还支持多态。由于这些原因枚举在代码中被广泛使用。——摘自作者:《Android 高性能编程》(西班牙)([恩里克·洛佩斯·马尼亚斯](意)([迪戈·格兰奇尼]
正如摘要所言,枚举很好。但我不推荐枚举,缘由有两个:
- 枚举通常会占用两倍于静态常亮的内存,在朱凯翻译的android delveop官方性能优化里已经不推荐(Enums usually request twice as much memory as static constants)
- 枚举在有几个成员,就分别会被转换为几个对象, 并且分贝为他们的name 参数分配String 。。。——《Adnroid 高性能编程》也正因为如此,以简化著称的kotlin语言,少见的为enum添加一个class ,比java的创建还多一个关键字。就是因为他本质是一个类,而且还不是一个省心的class ,是class里面还有class。
在安卓高性能编程这本书里,作者推荐,用静态常量代替枚举类的成员,将枚举类前面的enum,换成class,回归一个正常的类。
说了这么多有什么用呢? 因为kotlin自动转换,枚举类还是枚举类,若要转换的优雅,还得自己操作。
怎么办呢?
- 我们在转换的时候也可以遵循这个步骤:
- 先将类转换 2 . 再转kotlin。
java代码:
public enum EventType {
LOG_MINE,
UD_HEADER,
INVISIBLE_RED_DOT,
}
kotlin转换:
object EventType {
const val LOG_MINE = 0
const val UD_HEADER = 1
const val INVISIBLE_RED_DOT = 2
}
Tips:安卓版本后来,在形如上面代码的简单枚举,在proguard的时候,已经转换成了静态常量,所以性能无影响。
参考链接:https://www.jianshu.com/p/3b0c10c08227
https://www.jianshu.com/p/c7f9444e6d12
更新日期:2020.05.13