Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >用 Kotlin 写 Android ,难道只有环境搭建这么简单?

用 Kotlin 写 Android ,难道只有环境搭建这么简单?

原创
作者头像
bennyhuo
修改于 2017-08-07 08:29:51
修改于 2017-08-07 08:29:51
8.2K0
举报
文章被收录于专栏:BennyhuoBennyhuo

从这周开始,每周一的文章推送将连载 Kotlin Android 开发的文章,大家有关心的题目也可以直接反馈给我,这样也可以帮助我提高后续文章的针对性。

1. 千里之行,始于 Hello World

话说我们入坑 Kotlin 之后,要怎样才能把它运用到 Android 开发当中呢?我们作为有经验的开发人员,大家都知道 Android 现在基本上都用 gradle 构建,gradle 构建过程中只要加入 Kotlin 代码编译的相关配置,那么 Kotlin 的代码运用到 Android 的问题就解决了。

这个问题有何难呢?Kotlin 团队早就帮我们把这个问题解决了,只要大家在 gradle 配置中加入:

代码语言:txt
AI代码解释
复制
apply plugin: 'kotlin-android'

就可以了,这与我们在普通 Java 虚拟机的程序的插件不太一样,其他的都差不多,比如我们需要在 buildScript 当中添加的 dependencies 与普通 Java虚拟机程序毫无二致:

代码语言:txt
AI代码解释
复制
buildscript {
    ext.kotlin_version = '1.0.6'//版本号根据实际情况选择
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

当然,我们还要在应用的 dependencies 当中添加 Kotlin 标准库:

代码语言:txt
AI代码解释
复制
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

有了这些,你的 Kotlin 代码就可以跑在 Android 上面了!当然,你的代码写在 src/main/java 或是 src/main/kotlin 下都是可以的。这不重要了,我觉得把 Java 和 Kotlin 代码混着写就可以了,没必要分开,嗯,你最好不要感觉到他们是两个不同的语言,就酱紫。

代码语言:txt
AI代码解释
复制
package net.println.kotlinandroiddemo

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.TextView

class MainActivity : AppCompatActivity() {

    private lateinit var textView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView = findViewById(R.id.hello) as TextView
        textView.text = "Hello World"
    }
}

我们定义一个 TextView 的成员,由于我们只能在 onCreate 当中初始化这个成员,所以我们只好用 lateinit 来修饰它。当然,如果你不怕麻烦,你也可以选择 TextView? ,然后给这个成员初始化为 null。

接着我们就用最基本的写法 findViewById、类型强转拿到这个 textView 的引用,然后 setText。

运行自然是没有问题的。

不过,不过!我如果就写这么点儿就想糊弄过去这一周的文章,番茄鸡蛋砸过来估计够我吃一年的西红柿炒鸡蛋了吧(我~就~知~道~,我这一年不用愁吃的了!)

2. Anko 已经超神

要说用 Kotlin 写 Android,Anko 谁人不知谁人不晓,简直到了超神的地步。好好,咱们不吹牛了,赶紧把它老人家请出来:

代码语言:txt
AI代码解释
复制
compile 'org.jetbrains.anko:anko-sdk15:0.9' // sdk19, sdk21, sdk23 are also available
compile 'org.jetbrains.anko:anko-support-v4:0.9' // In case you need support-v4 bindings
compile 'org.jetbrains.anko:anko-appcompat-v7:0.9' // For appcompat-v7 bindings

稍微提一句 anko-sdk 的版本选择:

  • org.jetbrains.anko:anko-sdk15 : 15 <= minSdkVersion < 19
  • org.jetbrains.anko:anko-sdk19 : 19 <= minSdkVersion < 21

  • org.jetbrains.anko:anko-sdk21 : 21 <= minSdkVersion < 23
  • org.jetbrains.anko:anko-sdk23 : 23 <= minSdkVersion

当然除了这些之外,anko 还对 cardview、recyclerview等等做了支持,大家可以按需添加,详细可以参考 Github - Anko

另外,也建议大家用变量的形式定义 anko 库的版本,比如:

代码语言:txt
AI代码解释
复制
ext.anko_version = "0.9"
...

compile "org.jetbrains.anko:anko-sdk15:$anko_version" // sdk19, sdk21, sdk23 are also available
compile "org.jetbrains.anko:anko-support-v4:$anko_version" // In case you need support-v4 bindings
compile "org.jetbrains.anko:anko-appcompat-v7:$anko_version" // For appcompat-v7 bindings

好,有了 Anko 我们能干什么呢?

代码语言:txt
AI代码解释
复制
textView = find(R.id.hello)

还记得findViewById么?变成 find 了,而且强转也没有了,是不是很有趣?你一定有疑问,Anko 究竟干了啥,一下子省了这么多事儿,我们跳进去看看 find 的真面目:

代码语言:txt
AI代码解释
复制
inline fun <reified T : View> View.find(id: Int): T = findViewById(id) as T
inline fun <reified T : View> Activity.find(id: Int): T = findViewById(id) as T
inline fun <reified T : View> Fragment.find(id: Int): T = view?.findViewById(id) as T

首先它是个扩展方法,我们暂时只用到了 Activity 的扩展版本,实际上 View、Fragment 都有这个扩展方法;其次,它是个 inline 方法,并且还用到了 reified 泛型参数,我们本来应该这么写:

代码语言:txt
AI代码解释
复制
textView = find<TextView>(R.id.hello)

由于泛型参数的类型可以很容易的推导出来,所以我们再使用 find 的时候不需要显式的注明。

说到这里,其实还是有问题没有说清楚的,reified 究竟用来做什么?其实我们就算不写 inline 和 reified 泛型,这个方法照样是可以用的:

代码语言:txt
AI代码解释
复制
fun <T : View> Activity.myFind(id: Int): T = findViewById(id) as T
textView = myFind(R.id.hello)

不过呢,这地方用 inline 就省了一次函数调用,并且 reified 也可以消除 IDE 的类型检查提示,所以既然可以,为什么不呢?

当然,用 Anko 的好处不可能就这么点儿,我们今天先按住不说,谁好奇的话可以先自己去看看(我~就~知~道~,你们肯定忍不住!!)~

3. 不要 findViewById

作为第一篇介绍 Kotlin 写 Android 的文章,绝对不能少的就是 kotlin-android-extensions 插件了。在 gradle 当中加配置:

代码语言:txt
AI代码解释
复制
apply plugin: 'kotlin-android-extensions'

之后,我们只需要在 Activity 的代码当中直接使用在布局中定义的 id 为 hello 的这个 textView,于是:

代码语言:txt
AI代码解释
复制
import android.os.Bundle
import android.support.v7.app.AppCompatActivity

//这个包会自动导入
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //直接使用 hello,hello 实际上是这个view 在布局当中的id
        hello.text = "Hello World"
    }
}

只要布局添加一个 View,在 Activity、View、Fragment 中其实都可以直接用 id 来引用这个 view,超级爽~

所以,你们不准备问下这是为什么吗?为什么可以这样做呢?

其实要回答这个问题也不难,首先 Android Studio 要能够从 IDE 的层面索引到 hello 这个 View,需要 Kotlin 的 IDE 插件的支持(别问我啥是 IDE 插件,你们用 Kotlin 的第一天肯定都装过);其次,在编译的时候,编译器能够找到 hello 这个变量,那么还需要 Kotlin 的 gradle 插件支持(我们刚刚好像 apply 了个什么 plugin 来着?)。知道了这两点,我们就要有的放矢了~

“啊!” 那边的 Kotlin 源码一声惨叫。。。

前方高能。。我们讨论的源码主要在 plugins 目录下的 android-extensions-compiler 和 android-extensions-idea 两个模块当中。

如果让大家自己实现一套机制来完成上面的功能,大家肯定会想,我首先得解析一下 XML 布局文件吧,并把里面的 View 存起来,这样方便后面的查找。我告诉大家,Kotlin 也是这么干的!

AndroidXmlVisitor.kt

代码语言:txt
AI代码解释
复制
override fun visitXmlTag(tag: XmlTag?) {
    ...

    val idAttribute = tag?.getAttribute(AndroidConst.ID_ATTRIBUTE)
    if (idAttribute != null) {
        val idAttributeValue = idAttribute.value
        if (idAttributeValue != null) {
            val xmlType = tag?.getAttribute(AndroidConst.CLASS_ATTRIBUTE_NO_NAMESPACE)?.value ?: localName
            val name = androidIdToName(idAttributeValue)
            if (name != null) elementCallback(name, xmlType, idAttribute)
        }
    }
    tag?.acceptChildren(this)
}

这是遍历 XML 标签的代码,典型的访问者模式对吧。如果拿到这个标签,它有 android:id 这个属性,那么小样儿,你别走,老实交代你的 id 是什么!举个例子,如果这个标签是这样的:

代码语言:txt
AI代码解释
复制
<Button
    android:id="@+id/login"
    ... />

那么,name 就是 login 了,既然 name 不为空,那么调用 elementCallback,其实就是把它记录了下来。

IDEAndroidLayoutXmlFileManager.kt

代码语言:txt
AI代码解释
复制
override fun doExtractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidResource> {
    val widgets = arrayListOf<AndroidResource>()

    //注意到这里的 Lambda 表达式就是前面的 elementCallback
    val visitor = AndroidXmlVisitor { id, widgetType, attribute ->
        widgets += parseAndroidResource(id, widgetType, attribute.valueElement)
    }

    files.forEach { it.accept(visitor) }

    //返回所有带 id 的 view
    return widgets
}

接着想既然我们找到了所有的布局带有 id 的 view,那么我们总得想办法让 Activity 它们找到这些 view 才行对吧,而我们发现其实在引用它们的时候总是要导入一个包,包名叫做:

代码语言:txt
AI代码解释
复制
kotlinx.android.synthetic.main.<布局文件名>.*

几个意思?Kotlin 编译器为我们创建了一个包?

AndroidPackageFragmentProviderExtension.kt

代码语言:txt
AI代码解释
复制
...
createPackageFragment(packageFqName, false)
createPackageFragment(packageFqName + ".view", true)
...

注意到,这里的 packageFqName 其实就是我们前面提到的

代码语言:txt
AI代码解释
复制
kotlinx.android.synthetic.main.<布局文件名>

不对呀,怎么创建了两个包呢?其实第二个多了个 .view ,我们在 Activity 当中 导入的包是第一个,但如果是我们用父 view 引用子 view 时,用的是第二个:

代码语言:txt
AI代码解释
复制
...
import kotlinx.android.synthetic.main.activity_main.view.*

class OverlayManager(context: Context){
    init {
        val view = LayoutInflater.from(context).inflate(R.layout.activity_main, null)
        view.hello.text = "HelloWorld"
        ...
    }

    ...
}

好,我们现在知道了,IntelliJ 居然已经通过解析 XML 帮我们偷偷搞出了这么两个虚拟的包,这样我们在代码当中能够引用到这个包就很容易解释了。

这时候可能还会有人比较疑惑点击了 Activity 的 hello 之后如何跳转到 XML 的,这个大家阅读一下 AndroidGotoDeclarationHandler 的源码就会很容易的看到答案。

费了这么多篇幅,其实我们只是做好了表面文章。上面的一切其实都是障眼法,别管怎么说,这两个包都是虚拟的,编译的时候该怎么办?

其实编译就简单多了,碰到这样的引用,比如前面的 hello,直接生成 findViewById 的字节码就可以了,我们把 hello.text = "HelloWorld" 的字节码贴出来给大家看:

代码语言:txt
AI代码解释
复制
L2
LINENUMBER 12 L2
ALOAD 0
GETSTATIC net/println/kotlinandroiddemo/R$id.hello : I
INVOKEVIRTUAL net/println/kotlinandroiddemo/MainActivity._$_findCachedViewById (I)Landroid/view/View;
CHECKCAST android/widget/TextView
LDC "Hello World"
CHECKCAST java/lang/CharSequence
INVOKEVIRTUAL android/widget/TextView.setText (Ljava/lang/CharSequence;)V

这个是怎么做到的?请大家阅读 AndroidExpressionCodegenExtension.kt,

代码语言:txt
AI代码解释
复制
...
//GETSTATIC net/println/kotlinandroiddemo/R$id.hello : I
v.getstatic(packageName.replace(".", "/") + "/R\$id", resourceId.name, "I")

//INVOKEVIRTUAL net/println/kotlinandroiddemo/MainActivity._$_findCachedViewById (I)Landroid/view/View;
v.invokevirtual(declarationDescriptorType.internalName, 
    CACHED_FIND_VIEW_BY_ID_METHOD_NAME, 
    "(I)Landroid/view/View;", false)
...

好,到这里,想必大家才能对 Android 的 HelloWorld 代码有一个彻底的理解。

4. 小结

虽然是 HelloWorld,但要想搞清楚其中的所有秘密,并没有那么简单,很多时候,阅读 Kotlin 源码几乎成了唯一的途径。

谢谢大家的关注和支持~如果有什么问题可以联系我~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
死锁与死锁避免算法
死锁(Deadlock)是在多任务环境中的一种资源竞争问题,其中两个或多个进程(线程)互相等待对方持有的资源,导致所有进程都无法继续执行。死锁是一种非常棘手的问题,因为它会导致系统无法正常运行。
恋喵大鲤鱼
2024/02/29
6960
死锁与死锁避免算法
写给大忙人看的死锁详解
计算机系统中有很多独占性的资源,在同一时刻只能每个资源只能由一个进程使用,我们之前经常提到过打印机,这就是一个独占性的资源,同一时刻不能有两个打印机同时输出结果,否则会引起文件系统的瘫痪。所以,操作系统具有授权一个进程单独访问资源的能力。
用户1564362
2020/06/30
8490
写给大忙人看的死锁详解
软考高级架构师:死锁的条件和预防概念和例题
死锁是指多个进程在运行过程中因争夺资源而造成的一种僵局。要发生死锁,以下四个条件必须同时满足:
明明如月学长
2024/05/24
2110
软考高级架构师:死锁的条件和预防概念和例题
操作系统死锁处理--09
显然,生产者等待mutex锁的释放,而消费者却在等待生产者生产出来资源后,将自己唤醒.
大忽悠爱学习
2022/08/23
3010
操作系统死锁处理--09
操作系统面试题集合
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
测试小兵
2019/11/21
6790
操作系统面试题集合
面试整理学习专题2:操作系统
并行指两个或者多个事件同一时刻发生,并发是两个或者多个事件在同一时间间隔发生; 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件(如单核CPU轮转时间片)。
程序员洲洲
2024/06/07
1290
面试整理学习专题2:操作系统
死锁的四个必要条件和解决办法_半暖的博客_活锁和死锁的概念
原理: 当一组进程中的每个进程都在等待某个事件发生,而只有这组进程中的其他进程才能触发该事件,这就称这组进程发生了死锁。
全栈程序员站长
2022/08/03
11.7K0
死锁的四个必要条件和解决办法_半暖的博客_活锁和死锁的概念
开发成长之路(21)-- 不可不知的操作系统知识(1)
最下面是硬件系统;最上面是使用计算机的人,即各种各样的用户;人与硬件系统之间是软件系统。系统软件是最靠近硬件的一层,其次是支撑软件和应用软件。
看、未来
2021/09/18
5140
死锁以及如何解决
死锁是指两个或多个进程(或线程)因竞争资源而陷入相互等待的永久阻塞状态,若无外部干预,无法继续推进 。例如,线程A持有资源X并等待资源Y,而线程B持有资源Y并等待资源X,形成循环等待的僵局。
麦辣鸡腿堡
2025/03/18
2710
操作系统中的死锁现象
在多任务操作系统中,为了提高资源利用率和系统吞吐量,我们常常会同时运行多个进程。然而,这种并发执行的方式也带来了一些挑战,其中最为显著的问题之一就是死锁。本文将深入探讨死锁的概念、产生条件、预防策略和解决方法,帮助您更好地理解这一操作系统中的复杂问题。
一个风轻云淡
2024/06/19
3100
2-1.死锁-经典同步问题
Need[i, j]=Max[i, j]-Allocation[i, j] #尚需要的资源量=最大资源需求量-已分配资源量
见贤思齊
2020/08/05
5690
2-1.死锁-经典同步问题
操作系统核心原理-4.线程原理(下):死锁基础原理
我们都见过交通阻塞,一大堆汽车因为争夺行路权,互不相让而造成阻塞,又或者因为车辆发生故障抛锚或两辆车相撞而造成道路阻塞。在这种情况下,所有的车都停下来,谁也无法前行,这就是死锁。本篇就来了解一下什么是死锁,如何应对死锁。
Edison Zhou
2018/08/20
7300
操作系统核心原理-4.线程原理(下):死锁基础原理
操作系统:死锁的产生和处理
预防死锁:通过设置一些限制条件,破坏产生死锁的四个必要条件的一个或多个,来预防发生死锁。预防死锁实现简单,但是往往因为限制条件太过严格,导致系统资源利用率和吞吐量减少。
渔父歌
2020/09/07
1.8K0
操作系统入门(三)进程间通信
只要求读的进程称为“reader进程”,其他进程称为“writer进程”。 允许多个reader进程同时读一个共享对象,但决不允许一个writer进程和其他reader进程或writer进程同时访问共享对象 所谓读者-写者问题(The Reader-Writer Problem)是只保证一个writer进程必须与其他进程互斥地访问共享对象的同步问题
看、未来
2020/08/25
6110
重学操作系统原理系列 - 进程管理
**说明:**这是一个多线程的web服务器的工作方式,首先读取客户端的请求,之后由分派线程将各个任务分派给工作线程,这里还是采用了网页缓存
JavaEdge
2022/11/30
4380
重学操作系统原理系列 - 进程管理
【愚公系列】软考中级-软件设计师 027-操作系统(进程管理-银行家算法和线程)
进程是计算机中正在运行的程序的实例。它拥有自己的地址空间、内存、文件描述符等资源,可以独立地执行和调度。每个进程都是独立运行的,拥有自己的程序计数器、寄存器和堆栈,可以进行上下文切换。
愚公搬代码
2024/02/08
2890
计算机操作系统进程管理总结报告_进程的管理和控制实验报告
进程控制块PCB(Process Control Block)描述的是进程的基本信息以及进程的运行状态,我们说的创建及撤销进程都是对进程控制块PCB的操作。
全栈程序员站长
2022/09/30
1.2K0
计算机操作系统进程管理总结报告_进程的管理和控制实验报告
死锁:它是什么,如何检测、处理和预防-架构快速进阶教程
在进程共享资源的几乎任何情况下都可能发生死锁。它可以发生在任何计算环境中,但它在分布式系统中很普遍,其中多个进程在不同的资源上运行。
jack.yang
2025/04/05
1820
死锁:它是什么,如何检测、处理和预防-架构快速进阶教程
操作系统(第四版)期末复习总结(中)
很多小伙伴私信要word下载,我就整理出来了一份pdf,是和线上的完全一样,建议大家看线上的,因为pdf下载需要收费,但是下载有好处就是可以打印出来复习,各位伙伴自行选择吧。现在这里给出pdf完整下载: 操作系统(第四版)期末复习总结.pdf_操作系统复习-OS文档类资源-CSDN下载
全栈程序员站长
2022/11/03
1.1K0
【计算机基础】操作系统常见问答
操作系统只是硬件和应用软件之间的一个平台。32位操作系统针对32位的CPU设计。64位操作系统针对的64位的CPU设计。
章鱼carl
2022/03/31
6680
【计算机基础】操作系统常见问答
推荐阅读
相关推荐
死锁与死锁避免算法
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档