gradle学习笔记

新建一个安卓项目,Android Studio已经为我们生成的模板中,build.gradle总是很显眼。我也终于下定决心来了解下这个构建整合工具。

gradle脚本使用groovy编写,最近也开始支持用Kotlin编写。Kotlin大法好,但我暂时只找到了groovy的教程1,暂时不想去啃英文,先将就学着。

gradle安装

命令

对于OSX用户来说,brew install gradle就可以搞定这个步骤了。但是这里有个问题,就是这样会把openjdk当做依赖装上。也不算很大的问题,也能将就用。

gradle wrapper

gradle有一个很方便的命令,gradle wrapper,使用后会创建如下的目录结构

.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat

此时即使你没有安装gradle,仍然可以使用./gradlew代替gradle执行命令。如./gradlew clean install。在实际的项目中,这种方式使用比较普遍,因为gradle脚本很多特性受版本影响比较大,一般gradle的版本也会在项目中指定。

阅读全文
android项目开发:Kotlin

Kotlin

变量和函数

变量

varval定义变量,并有类型自动推导的支持。

val用来声明一个不可变的变量,var用来声明一个可变的变量。

val a = 10
var b: Int = 12

Kotlin完全抛弃了java中的基本数据类型,完全使用对象数据类型。

java基本数据类型Kotlin对象数据类型数据类型说明
intInt整型
longLong长整型
shortShort短整型
floatFloat单精度浮点型
doubleDouble双精度浮点型
booleanBoolean布尔型
charChar字符型
byteByte字节型

函数

语法:

fun main(args: Array<String>) {
    println("Hello World!")
}

Kotlin中的函数在无必要时可以省略很多东西:

import kotlin.math.max

fun largerNumber(a: Int, b: Int) = max(a, b)

fun main(args: Array<String>) {
    val a = 3
    val b = 5
    println("The larger number of a and b is ${largerNumber(a, b)}")
}

程序的逻辑控制

if

与java中的if语句相比,Kotlin中的if是可以有返回值的。

fun judge(score: Int) = if (score >= 60) "你及格了" else "你还需要多努力"

fun main() {
    println(judge(55))
    println(judge(66))
}

与此同时,Kotlin不再有java中的三元运算符1,语义上清晰了很多。

when条件语句

类似于java中的switch语句,根据变量的值执行不同的逻辑。

fun judge(score: Int) = if (score >= 60) "你及格了" else "你还需要多努力"

fun getScore(name: String) = when (name) {
    "Tom" -> 78
    "Jack" -> 35
    "Jerry" -> 84
    "Lee" -> 57
    else -> 0
}

fun main() {
    println(judge(getScore("Tom")))
    println(judge(getScore("Jack")))
    println(judge(getScore("Bill")))
}

对我来说,最令人振奋的是再也不需要在每个分支里面都写个break了。其次when也是有返回值的,这和其他特性组合写出来的代码非常简洁优雅。

写个小demo吧。

interface Speakable {
    fun speak()
}

class Dog : Speakable {
    override fun speak() {
        println("汪汪汪")
    }
}

class Cat : Speakable {
    override fun speak() {
        println("喵喵喵")
    }

    fun climb() {
        println("小猫会爬树")
    }
}

fun generateAnimal(): Speakable? = when ((1..3).random()) {
    1 -> Dog()
    2 -> Cat()
    else -> null
}

fun main() {
    when (val animal = generateAnimal()) {
        is Dog -> {
            println("生成的动物是小狗")
            animal.speak()
        }
        is Cat -> {
            println("生成的动物是小猫")
            animal.speak()
            animal.climb()
        }
        else -> println("生成动物时出现问题")
    }
}

循环语句

Kotlin中有两类循环,其中while循环与java学过的while循环非常相似,只说下有差异的for循环吧。

Kotlin的for循环只有for..in式的了,如for(i in list)

但有时对数组下标进行遍历还是有必要的。于是我们要先了解下Kotlin的区间的概念。

val range = 1..10

这表示[1,10]。但很多时候,我们需要左开右闭区间,比如数组有三个元素,我们需要[0,3)表示数组的下标。此时可以使用util关键字。

val indexRange = 0 util 3

有了range再和前面的for配合就完全可以替代之前java里面的for的作用了:

val array = arrayOf("Bob", "John", "Jackson")
for (i in 0 util array.size) {
    println("$i:${array[i]}")
}

此外,还可以用step指定步长值,实现”隔几个输出一次“的效果:

for (i in 0 util 10 step 2) {
    println(i)
}

如果需要10循环到1,则需要downTo关键字,

for (i in 10 downTo 1) {
    println(i)
}

阅读全文
我的黑历史被github埋到北极了

昨天整理自己的github仓库的时候发现自己的个人主页多了个徽章:Arctic Code Vault Contributor

QQ20210126-143812@2x.png

So what happened?

阅读全文
用Compose重构我的连连看游戏

此前的文章中提到,我闲暇时写了个连连看游戏。因为比较闲,正好又对Jetpack Compose比较感兴趣,于是我想用Compose重构界面,学习下Compose的使用。 然后没有花费太大力气就完成了。

GameViewModelCompose.kt

于是我想,既然用上Compose了,是不是可以直接迁移到桌面平台?

这篇文章实际上没什么内容,就是对于折腾过程的一个简单记录,最后的成品在github,直接去看代码就可以了。

阅读全文
数据结构 栈应用2 表达式求值

表达式求值是程序设计语言编译中的一个最基本问题,它的实现是栈应用的一个典型例子。

Question

表达式求值是进行数据处理的最基本操作。请编写程序完成一个简单算术表达式的求值。要求如下:

  1. 运算符包括:+、-、*、-、^(乘方)、括号
  2. 运算量为数值常量,根据自己的能力可以对运算量做不同的约束,例如1位整数、多位整数、实数等(会有不同的测试用例);

阅读全文
装饰模式实现分享功能

title: 装饰模式实现分享功能 author: 归零幻想 publishDate: 2021-07-19 editDate: 2021-07-19 tags: [设计模式, Kotlin, 分享]

在看业务代码时发现了一段代码,应用了装饰模式处理了分享功能的实现,非常巧妙,共赏。

装饰模式.svg.png.lin.png

阅读全文
ClassLoader双亲委托机制探究

最近在研究抖音进入热点内流的耗时问题,种种线索指向了类加载耗时上。为此,我研究了Java类加载的双亲委托机制,并尝试给出了优化建议。

双亲委托机制

双亲委托机制中最重要的是loadClass方法,让我们看看它是怎么实现的。

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name); // 已加载过直接返回
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false); //尝试让parent加载
                    } else {
                        c = findBootstrapClassOrNull(name); // bootstrap class loader是否加载过
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name); // 自己加载(找不到会抛出异常)
                }
            }
            return c;
    }

findClass就是根据类名去加载具体类的方法,则整个加载机制就很清楚了。

  1. 首先部分类会被BootstrapClassLoader加载,这部分是native实现。
  2. 会先尝试让parent加载
  3. parent找不到时才会自己加载

如果我们能创建一个ClassLoader,将它插入到PathClassLoader和BootstrapClassLoader之间,那么所有被PathClassLoader加载的类就都可以记录下来了。

阅读全文
DebugEntrance和DebugConfig

工欲善其事,必先利其器。毕设是一个相对复杂的项目了,我觉得要想顺利完成肯定是需要一些手段帮助我调试的。于是这里我准备了debug页面,主要功能就两个:提供某个功能的入口以及存储配置(最好能直接在手机上修改)

DebugEntrance

就是一个各种测试功能的入口。

1.jpg

这个一看实现就很简单,不细说了。

DebugConfig

因为字节自己的ABManager用着挺顺手,感觉自己项目调试时有类似这么个东西会比较舒服,于是搞了这么个东西。

2.jpg 3.jpg

使用

先看使用:

@ZeroConfig(key = "retrofit_config", title = "Retrofit配置", owner = "liuhaixin.zero")
data class RetrofitConfig(val baseUrl: String = RetrofitUtil.BASE_URL)

private val retrofitConfig by zeroConfig<RetrofitConfig>()

private val retrofit by lazy {
    Retrofit.Builder()
        .baseUrl(retrofitConfig?.baseUrl ?: BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .client(okHttpClient)
        .build()
}

看上去还是有点让人心动的吧。

阅读全文
ViewBinding、ViewModel和LiveData

毕设项目没有历史包袱,我可以尽量向best practice努力。

ViewBinding

无数人痛恨findViewById,并且为了干掉它做了许多尝试,比如ButterKnife、kotlin-android-extensions。

现在,有了ViewBinding,项目中真的可以不写findViewById了。至少目前为止我的毕设项目还没有一个findViewById。

其实与ViewBinding相似的,还有一个DataBinding,但我不太喜欢,感觉在xml里面写代码不是一个好主意。

使用

首先在build.gradle(或build.gradle.kts)中的android块添加

buildFeatures {
    viewBinding = true
}

在xml中正常定义你的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

    <Button
        android:id="@+id/test_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="toast" />

    <Button
        android:id="@+id/change_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="change" />
</LinearLayout>

接下来就可以愉快使用了

package top.ntutn.viewmodeldemo

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import top.ntutn.viewmodeldemo.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        initView()
    }

    private fun initView() {
        binding.apply {
            changeButton.setOnClickListener { textView.text = "Changed!" }
            testButton.setOnClickListener {
                Toast.makeText(
                    [email protected],
                    "Test",
                    Toast.LENGTH_LONG
                ).show()
            }
        }
    }
}

我们在xml里面用下划线分隔的id在这里就直接变成了binding里面的字段,相当舒服。

在RecyclerView中的使用

在RecyclerView中代码要稍微发生一点变化,因为我们是在onCreateViewHolder时反射创建布局的。

package top.ntutn.viewmodeldemo

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import top.ntutn.viewmodeldemo.databinding.ItemTestBinding

class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
    class ViewHolder(val binding: ItemTestBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            ItemTestBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        )
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.textView.text = TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }
}

仍然不需要findViewById!

阅读全文
avatar

归零幻想

邮件:[email protected]
Telegram: @zerofancy

正在追番《艾克斯奥特曼》。
——站长
RSS

友情链接