透明度 十六进制 % Hex % Hex % Hex % Hex
100% FF 99% FC 98% FA 97% F7 96% F5
95% F2 94% F0 93% ED 92% EB 91% E8
90% E6 89% E3 88% E0 87% DE 86% DB
85% D9 84% D6 83% D4 82% D1 81% CF
80% CC 79% C9 78% C7 77% C4 76% C2
75% BF 74% BD 73% BA 72% B8 71% B5
70% B3 69% B0 68% AD 67% AB 66% A8
65% A6 64% A3 63% A1 62% 9E 61% 9C
60% 99 59% 96 57% 94 56% 91 56% 8F
55% 8C 54% 8A 53% 87 52% 85 51% 82
50% 80 49% 7D 48% 7A 47% 78 46% 75
45% 73 44% 70 43% 6E 42% 6B 41% 69
40% 66 39% 63 38% 61 37% 5E 36% 5C
35% 59 34% 57 33% 54 32% 52 31% 4F
30% 4D 29% 4A 28% 47 27% 45 26% 42
25% 40 24% 3D 23% 3B 22% 38 21% 36
20% 33 19% 30 18% 2E 17% 2B 16% 29
15% 26 14% 24 13% 21 12% 1F 11% 1C
10% 1A 9% 17 8% 14 7% 12 6% 0F
5% 0D 4% 0A 3% 08 2% 05 1% 03
0% 00

1、删除 .gitignore 排除的文件,例如 build/ .idea/ 等文件

当备份项目源码时,并不需要例如 build 等占用很大磁盘空间的无效文件。当是 git 工程时,可以使用如下命令删除无用文件:

git clean -d -fx [-n(显示删除内容)]

由于 git 工程非常多,写了批量删除的脚本,如下:

#!/bin/bash

CODE_DIR_PATH=''
SHOW_REMOVE_FILE=false
ITERATION_DEGREE=2


while getopts d:nh ops
do
    case ${ops} in
        d)
            CODE_DIR_PATH=${OPTARG}
            ;;
        n)
            SHOW_REMOVE_FILE=true
            ;;
        h)
            echo "help : "
            echo "-d code dir path"
            echo "-n show would remove file"
            echo "-h help"
            exit
            ;;
        *)
            echo "unknow params"
            ;;
    esac

done


if [[ ! -e $CODE_DIR_PATH ]]; then
    echo "error ! resource dir not found"
    exit
fi

array_git_dir=()
index_git_dir=0

function func_search_file()
{
    # echo "func_search_file $1   $2"
    for fileName in ` ls -A $1 `
    do
        if [ -d $1"/"$fileName ] 
        then
            # check git project
            if [[ '.git' = $fileName ]]; then
                # echo $1"/"$fileName
                array_git_dir[index_git_dir++]=$1
                break
            else
                # -ge >=;  -le <=;   -gt >;  -lt <;  -eq ==; -ne !=;
                if [[ $2 -lt $ITERATION_DEGREE && '.' != ${fileName:0:1} ]]; then
                    func_search_file $1"/"$fileName $(($2 + 1))
                fi
            fi
        fi
    done
}

func_search_file $CODE_DIR_PATH 0

# echo ${array_git_dir[@]}

for dir in ${array_git_dir[@]}; do
    echo $dir
    cd $dir
    if [[ 'true' = $SHOW_REMOVE_FILE ]]; then
        git clean -d -fx -n
    else
        git clean -d -fx
    fi
    echo '---done'
done

echo 'end running'  

使用方法如下,其中 「deleteBuild.sh」 为该脚本文件名:

sh deleteBuild.sh -d /Users/ionesmile/Documents/iOnesmileDocs/WorkSpace/

# 使用 -n 查看将要删除的文件,例如
sh deleteBuild.sh -d /Users/ionesmile/Documents/iOnesmileDocs/WorkSpace/ -n

# 使用 -h 帮助
sh deleteBuild.sh -h

编写以上脚本,查询的资料和总结点:

2、一键打包 AAR,并打包运行 Demo

#!/bin/bash

basedir=`cd $(dirname $0); pwd -P`
# echo "START: basedir = $basedir"

GeneProject=/Users/ionesmile/Desktop/Package/FmxosGene
DemoProject=/Users/ionesmile/Desktop/Package/FmxosAAR

versionCode='1'
mVersionName='“1.0.0”'

# 打开 gradle 文件,修改版本配置等
# vim $GeneProject/FmxosPlatform/build.gradle

function logi(){
    echo "#########################################################
    echo "##"
    echo "##   "$*
    echo "##"
    echo "#########################################################
}

function die(){
    echo
    echo "$*"
    echo
    exit 1
}

# 更新版本名称,两个参数。要求辅助输入版本,回车表示不变
# $1:build.gradle 文件路径
# $2:版本号,可选,如果为空时,弹出输入提示
function update_version_name(){
    current_version_name=`grep -o 'versionName *["_a-zA-Z0-9.]*' $1 |sed -n '1p'`
    if [[ -z "$2" ]]; then
        read -p "current $current_version_name, please input: " answer
    else
        answer="$2"
    fi

    if [[ -z "$answer" ]]; then
        echo "answer is empty! continue..."
        # 贪婪匹配最后一个空格并截取
        mVersionName=${current_version_name##* }
    else
        # 版本名,添加双引号
        if [[ ${answer:0:1} != '"' ]]; then
            answer="\"$answer\""
        fi
        mVersionName=$answer
        sed -i '' "s/$current_version_name/versionName $answer/g" $1
        echo "changed: $current_version_name ---> versionName $answer"
    fi
}

function update_version_code(){
    current_version_code=`grep -o 'versionCode *[0-9]*' $1 |sed -n '1p'`
    if [[ -z "$2" ]]; then
        read -p "current $current_version_code, please input: " answer
    else
        answer="$2"
    fi

    if [[ -z "$answer" ]]; then
        echo "answer is empty! continue..."
        # 贪婪匹配最后一个空格并截取
        versionCode=${current_version_code##* }
    else
        versionCode=$answer
        sed -i '' "s/$current_version_code/versionCode $answer/g" $1
        echo "changed: $current_version_code ---> versionCode $answer"
    fi
}

# 更新版本名和版本号,第一个为主版本号,之后为辅助版本号同第一个更新
function update_version(){
    if [[ ! -f $1 ]]; then
        die "GeneProject build.gradle file not exist. -> $1"
    fi
    if [[ ! -f $2 ]]; then
        die "DemoProject build.gradle file not exist. -> $2"
    fi
    update_version_name $1
    update_version_name $2 $mVersionName
    update_version_code $1
    update_version_code $2 $versionCode
}

update_version $GeneProject/FmxosPlatform/build.gradle $DemoProject/app/build.gradle


##########################################################
## 合成代码到单个工程,并生成 AAR,最后打包测试Demo
##########################################################


start_time=$(date +%s)

function gene_aar(){
    # 调用 java,执行合成代码功能
    java -jar $basedir/MergeModule_jar/MergeModule.jar

    # 调用生成 AAR 文件
    cd $GeneProject
    ./gradlew clean
    logi "GeneProject clean done.   "$(($(date +%s)-start_time))"s"
    ./gradlew :FmxosPlatform:assembleRelease
    logi "GeneProject assemble done.   "$(($(date +%s)-start_time))"s"

    # 将 AAR 文件复制到 Demo 中
    cp FmxosPlatform/build/outputs/aar/FmxosPlatform-release.aar $DemoProject/FmxosPlatform/FmxosPlatform-release.aar
    # open FmxosPlatform/build/outputs/aar/
}

function package_demo(){
    # 调用打包测试APK
    cd $DemoProject
    ./gradlew clean
    logi "AARProject clean done.   "$(($(date +%s)-start_time))"s"
    ./gradlew assembleRelease
    logi "AARProject assemble done.   "$(($(date +%s)-start_time))"s"

    # 调用打开 APK 目录
    open app/build/outputs/apk/release/

    # 调用自动安装 apk 到手机
    apkName=$(ls app/build/outputs/apk/release/ | grep '.apk' | sed -n '1p')
    adb install -r app/build/outputs/apk/release/$apkName
    adb shell am start -n com.fmxos.fmxosaar/.MainActivity
}

gene_aar

package_demo

logi 'END... time:'$(($(date +%s)-start_time))"s"

知识点总结:

  1. 获取当前 shell 脚本文件的目录,执行相对路径下的 jar 文件
  2. 使用 grep 命令,查找 versionName,并使用字符串截取值
  3. 使用 sed 命令,修改 versionName 的值
  4. 调用 java -jar 执行 jar 文件,使用 IDEA 导出 jar
  5. 调用 gradlew 打包指定模块的 Release 版本
  6. cp 命令复制文件,open 打卡文件夹
  7. 使用 $(date +%s) 计算命令执行时间差

Google 推出 Kotlin 作为 Android 的官方语言已经有一段时间,最近用工作上一些闲暇时间做了个项目,切身体验下。

一、需求描述

一直以来对各个网站的密码管理都比较头疼,因为担心“撞库”,所有网站密码都不相同。注册网站时都会随便写一个密码,却没有一个好的密码管理工具,下次登录时基本都需要找回密码,结果又忘记如此反复。对于普通的网站重新找回一次并不算复杂,但是对于像 QQ、微信、支付宝 这样有比较高安全验证的网站找回起来并不容易,处理起来很繁琐特别头疼(承认我记忆力不好,突然想到自己好几张银行卡密码也忘记了 ( ̄▽ ̄)~~~ ,不过也没存款。。。。。。)

之前找过管理密码的软件,但不是太放心。软件又没开源,也不确定有没有后门或漏洞,自己动手要踏实得多。

我的《密码本》正是基于这一需求产生的,不但让自己的密码相对有一个保障,同时练练手学习新的技术。最后该项目作为开源项目,希望也能帮助你解决同样的烦恼。

二、项目截图

GitHub: https://github.com/iOnesmile/PasswordNotebook

安装包: 百度云下载

三、待完善

  • 提升加密文件安全度,研究其它算法并检验安全性
  • 应用内安全验证,如数据存储、锁屏、页面超时、导出权限等
  • 优化交互体验,简化操作流程,和指纹解锁等验证机制
  • 其它平台开发(iOS、Windows、MacOS),信息同步
  • 语言国际化
  • 其它……

如果有什么好的想法和建议,或在使用中遇到什么问题,欢迎反馈,我们一起完善吧!!!

四、使用 Kotlin 的坑或技术总结

  1. 在设置监听时,提示错误 Expected a value of type Boolean
    原因:该监听有一个返回值,类型是 Boolean
    例如:

    textView.onLongClick {
        // TODO
        return@onLongClick true
    }
    
  2. EditText 设置值时提示 Type mismatch. Required: Editable! Found: String
    原因:要给 EditText 设置 String 类型的值时,需要使用 setText() 方法
    例如:

    editText.setText("XXX")
    
  3. 函数式编程
    • map
      映射函数也是一个高阶函数,将一个集合经过一个传入的变换函数映射成另外一种集合

    • filter
      筛选函数将用户给定的布尔逻辑作用于集合,返回由原集合中符合条件的元素组合的一个子集

    • reduce
      归纳函数将一个数据集合的所有元素通过传入的操作函数实现数据集合的积累叠加效果

五、使用技术/库

一、打包 aar

1、单个模块打包

  1. 打开 Gradle 工具窗口,找到 Android Library 模块. 在 build 任务中双击 assemble.

  2. 执行成功后,在 mylibrary/build/outputs/aar 目录下找到 aar 包.

默认 DebugReleaseAAR 包都会打出来,当然你也可以选择只打 Debug 的包,双击 assembleDebug 任务就可以了. 只打 Release 的包同理.

2、多个模块打包

当要打包的模块又依赖了其它几个模块时,常常需要把它们打包成一个 aar。多模块打包使用 fat-aar,打包关键步骤如下:

  1. 将下载好的 fat-aar.gradle 文件添加到对应的模块目录中,并在 build.gradle 中引入 apply from: 'fat-aar.gradle'。或直接引用 apply from: 'https://raw.githubusercontent.com/adwiv/android-fat-aar/master/fat-aar.gradle'

  2. 添加要打包的工程,使用 embedded 关键字。示例代码如下:

    apply from: 'fat-aar.gradle'
    dependencies {
        ...
       embedded project(':DynamicPageLibrary')
       embedded project(':VideoPlayerLib')
       embedded project(':AudioPlayLibrary')
       embedded project(':BaseCloudMusicResource')
    }
    
  3. 步骤同上《单个模块打包》一致。

二、引入 aar

方法一、通过 libs 引入到 app 中

  1. aar 文件放在 libs 目录下

  2. appbuild.gradle 中添加如下内容

    repositories {
        flatDir {
            dirs 'libs' 
        }
    }
    
  3. 之后通过如下方式引入
    dependencies {
        compile(name:'test', ext:'aar')
    }
    
  4. Rebuild project

  5. 如果发现引入后无法使用,重启 Android studio

方法二、把 aar 作为一个库工程的方式引入

当项目中库工程较多且依赖关系比较复杂时,最好采用这一种方式。如:某一个库工程也要引入这个 aar 时。

菜单栏 -> File -> New -> New Module

-> Import .Jar/.AAR Package

-> Next

-> 选择 File name 的文件 -> Subproject name 命名工程

-> Finish

创建完成后是一个工程,工程中包括 aar 文件和 build.gradle 文件。build.gradle 文件内容如下:

configurations.create("default")
artifacts.add("default", file('musiclibrary_20170622.aar'))

三,遇到的问题

  1. Non-constant Fields in Case Labels

    原因:在 Android Library 中不能使用 switch case

    解决:改成用 else if,如下图:

  2. java.lang.IllegalArgumentException: No view found for id 0x7f0d013d () for fragment TestFragment

    描述:在项目中引用了库里的 fragment,在运行后抛出了找不到 view 的异常。但是在 Demo 项目中运行是没有问题的。

    原因:库里 fragmentlayoutID 与项目中另外一个 FragmentlayoutID 名字相同,导致项目中的布局会覆盖库中的布局。

    解决:修改为不同的名称。在库中要注意资源名称可能与项目同名的问题,比如在库中的资源文件都添加前缀或后缀,或较长不容易重复的名字。同名的资源文件只会存在一个,根据库的嵌套关系,外层会覆盖内层的资源文件。

一、类与方法

1,类

  • 类的声明
    class Bar(var b: Int): Foo() {
        var c = 1
        init {
            println("class initializer")
        }
    
        constructor(): this(1) {
            println("secondary constructor")
        }
    }
    

    Bar类在这里继承了Foo类,Bar类有两个构造函数,直接在Bar类头的是primary constructor,另外一个构造函数使用constructor关键字定义,注意必须要先调用primary constructor,另外,init标明的是class initializer,每个构造函数都会首先调用class initializer里面的代码,再调用构造函数

  • 创建类的实例,不需要 new

    var bar = Bar()
    
  • 继承

    内定义默认是 final 的,要想能被继承,基类头必须有 open 注解

  • Inner class

    class Outer {
        class Inner {      
        }
    }
    

    与 Java 不同,Kotlin 中所有的内部类默认就是静态的,这样可以减少很多内存泄露的问题。如果需要在内部类中引用外部类对象,可以在Inner类的声明前加上inner关键字,然后在Inner类中使用标记的this:this@Outer来指向外部类对象

  • Singleton

    object Single {
        var c = 1
    
        fun foo() = println("foo")
    }
    

    单利对象用 object 关键字表示,可以直接使用 Single.foo() 来调用了

2,接口

interface Interface {
    fun foo() {
        println(1)
    }
    fun bar()
}

可以带有默认的实现方法,并且不允许通过属性来维护状态。

3,函数

  • 函数声明
    fun foo(va: Int): Int {
        return 1
    }
    
  • 也可以使用单行声明
    fun foo(va: Int): Int = 1
    
  • 重载

    与类的派生一样,允许重载的方法要有open注解,而在派生类中重载时要使用override注解

    override fun foo(va: Int): Int {
        return 2
    }
    

4,修饰符

5,成员变量的 Get 与 Set

注:
1,类和方法默认定义都是 final,以此来提高效率。类想要被继承用 open 关键字
2,类 和 成员变量 默认是 public 修饰

二、语法

1,语法糖,对类的扩充

在不修改类的原始定义的情况下实现对类的扩展,如下面的代码为Person类增加了一个名为isTeenager的扩展:

fun Person.isTeenager(): Boolean {
    return age in 13..19
}

2,排除空指针

  • 定义一个为空的变量是需要加上 ? 符号
    var text: String? = null
    
  • 操作一个可能为空的对象时,同样要加上 ? 符号
    var length = text?.length
    
  • 如果将该变量传递给函数,在参数后面需要加 !! 符号
    if (text != null) {
        customPrint(text!!)
    }
    
  • 如何去掉 !! 符号呢,当代码充满该符号时显然很不优雅,这时可以使用 let 函数
    text?.let { customPrint(it) }
    
  • 如果遇到多个参数的情况,你可以选择嵌套多个 let,但这样可读性并不好。比如:
    if (mUserName != null && mPhotoUrl != null) {
       uploadPhoto(mUserName!!, mPhotoUrl!!)
    }
    

    这时你可以构建一个全局函数:

    fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
       if (value1 != null && value2 != null) {
           bothNotNull(value1, value2)
       }
    }
    

    调用方式

    ifNotNull(mUserName, mPhotoUrl, {name, url ->
            uploadPhoto(name, url)
    })
    

3,高阶函数和Lambda表达式

  • 例如给一个变量赋 lambda 表达式 {x,y->x+y}
    val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
    
  • 定义一个可以传表达式的高阶函数

    kotlin
    fun doubleTheResult(x:Int, y:Int, f:(Int, Int)->Int): Int {
    return f(x,y) * 2
    }
    kotlin

  • 调用方法如下

    val result1 = doubleTheResult(3, 4, sumLambda)
    或
    val result2 = doubleTheResult(3, 4, {x,y -> x+y})
    

4,范围表达式

  • 范围创建只需要 .. 操作符,为升序,例如:
    // 该范围包含数值1,2,3,4,5
    val r1 = 1..5
    
  • 如果要表示降序,用 downTo 函数
    // 该范围包含数值5,4,3,2,1
    val r2 = 5 downTo 1
    
  • 如果步长不是1,则需要使用step函数
    // 该范围包含数值5,3,1
    val r3 = 5 downTo 1 step 2
    // 同理,升序的序列
    val r4 = 1..10 step 2
    

5,条件结构

  • if 表达式(类似于 Java 的 ?: 运算符)
    var age = 20
    val isEligibleToVote = if(age > 18) "Yes" else "No"
    
  • when表达式(类似于 Java 的 switch,但功能更强大)
    val age = 17
    
    val typeOfPerson = when(age){
        0 -> "New born"
        in 1..12 -> "Child"
        in 13..19 -> "Teenager"
        else -> "Adult"
    }
    

6,循环结构

使用 for..in 遍历数组、集合及其它提供了迭代器的数据结构,语法同Java几乎完全相同,只是用 in 操作符取代了 : 操作符

val names = arrayOf("Jake", "Jill", "Ashley", "Bill")

for (name in names) {
    println(name)
}

while 和 do..while 循环的语法与Java完全相同。

7,字符串模板

可以在字符串中嵌入变量表达式,例如:

val name = "Bob"
println("My name is ${name}") //打印"My name is Bob"

val a = 10
val b = 20
println("The sum is ${a+b}") //打印"The sum is 30"

三、XML 布局 + kotlin-android-extensions

1, 通常在 xml 中查找控件的写法

val name = find<TextView>(R.id.tv_name)
// 等同于 findViewById()
val name = findViewById(R.id.tv_name) as TextView
name.text="张三"

2,如果使用扩展后,可以直接调用并赋值

tv_name.text="张三"

环境配置

1,项目下面的 build.gradle 加入如下代码:

buildscript {

    ext.kotlin_version ="1.0.4"

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

2,app 下面的 build.gradle 加入如下代码:

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    sourceSets{
        main.java.srcDirs+='src/main/kotlin'
    }
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile 'org.jetbrains.anko:anko-sdk25:0.10.0-beta-1'// sdk15, sdk19, sdk21, sdk23 are also available
    compile 'org.jetbrains.anko:anko-appcompat-v7:0.10.0-beta-1'
}

3,Gradle Sync

4,菜单栏 —> Code —> Convert Java File to Kotlin File

Anko Layout

一、优点

1,运行速度快。XML布局是在运行时解析的,也就是说XML需要从资源文件中获取,然后 XmlPullParser 需要解析所有的元素并一个一个的创建它们。还要解析元素的属性,然后设置,这个过程非常繁重。

2,类型安全,不再需要那么多的 findById() 之后的类型转换。

3,null 安全,Kotlin 里,如果一个变量用?表示为可空,并且使用?之后再调用的时候,即使变量为空也不会引发异常。

4,代码复用,可以通过继承AnkoComponent的方式实现代码复用。XML布局是每一个Activity,每一个View各自专属一个,代码复用比较少。

二、缺点

1,Anko DSL 布局不能预览。虽然有一个叫 Anko Preview Plugin 的预览插件,但是每次修改后都需要 make 下才能预览,关键是在新版本 Android Studio2.2 以上都不支持。

  • 笔者在 Android studio2.3 上安装该插件,导致重启后无法进入项目界面。
  • 幸好在启动页面的左下角有一个 Config 选项,点击其中的 Plugin,卸载 Anko Preview 插件才可以正常启动。

2,定义 id 比较繁琐,需要定义一个变量,或者在 values 资源文件下定义 ids。不用 id 行不行呢?你去问问 RelativeLayout 答应不答应吧。

3,如果定义在 xml 的话,可以直接通过 id 使用对应的 View(XML 布局 + kotlin-android-extensions 的方式),但是在 Anko DSL 布局的话,只能通过定义变量的方式来实现。

4,动态替换外部资源以达到换肤的效果,那么 XML 显然比 Kotlin 代码要来得容易:前者可以编译成一个只有资源的 apk 供应用加载,后者的话就得搞一下动态类加载了。

三、引用方式

// 继承 AnkoComponent 创建布局
class LoginLayout<T> : AnkoComponent<T> {

    override fun createView(ui: AnkoContext<T>): View {
        return with(ui){
            ...
        }
    }
}

// Activity 中引用
override fun onCreate(savedInstanceState: Bundle?) {
    LoginLayout<MainActivity>().setContentView(this)
}

// Fragment 中引用
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    var view = LoginLayout<LoginFragment>().createView(AnkoContext.create(context, LoginFragment()))
    return view
}

四、常用语法

1,定义 TextView

textView("Hello") {
    textSize = 16f
    textColor = Color.RED
    backgroundResource = R.drawable.shape_et_bg
    gravity = Gravity.CENTER
}.lparams(matchParent, wrapContent){
    margin = dip(12)
    padding = dip(2)
}

2,提取样式

// 给 EditText 扩展样式方法
fun EditText.commonStyle(){
    textSize = 16f
    backgroundResource = R.drawable.shape_et_bg
}

// 直接在布局的后面添加上
.style {
    view ->
    when (view) {
        is Button -> {
            view.gravity = Gravity.CENTER
        }
        is TextView -> {
            view.gravity = Gravity.LEFT
            view.textSize = 20f
            view.textColor = Color.DKGRAY
        }
    }
}

3,设置点击事件

var etInput = editText {
    hint = "请输入文字"
    commonStyle()
}

button("点我"){
    // 在按钮属性内部设置点击事件
    onClick {
        toast("输入的内容:${etInput.text}")
    }
}

// 通过变量 + . 的方式设置
etInput.onClick { 

}

4,布局方式

val ID_USERNAME = 1

// 垂直布局,== LinearLayout + orientation="vertical"
verticalLayout {  }
// 相对布局,需要使用到 ID
relativeLayout {
    textView("姓名") {
        id = ID_USERNAME
    }
    textView("描述") {

    }.lparams {
        below(ID_USERNAME)
        alignParentLeft()
    }
}
// 线性布局
linearLayout {
    orientation = LinearLayout.HORIZONTAL
}
frameLayout { }
tableLayout { }

5,ui: AnkoContext

// 包含的变量
val ctx: Context
val owner: T
val view: View

// 例如,可以通过 owner 直接调用外部 Activity 的方法
override fun createView(ui: AnkoContext<T>): View {
    if (ui.owner is Activity) {
        (owner as Activity).onBackPressed()
    }
}

参考链接

Kotlin Primer·第二章·基本语法 https://kymjs.com/code/2017/02/04/01/

http://blog.csdn.net/io_field/article/details/53365834

只需五分钟,开始使用Kotlin开发Android
https://barryhappy.github.io/2016/10/20/start-kotlin-in-5-mins/

登陆注册 Demo
http://blog.csdn.net/xiehuimx/article/details/72354371

Kotlin 系统入门到进阶 视频教程 https://github.com/enbandari/Kotlin-Tutorials

官方文档:https://www.kancloud.cn/pholance/kotlin/125094