【译】用BuildSrc和Kotlin_DSL管理Gradle依赖

多模块工程中一个更好的引入依赖的方法

翻译自Gradle Dependency Management With BuildSrc and Kotlin DSL

第一次尝试翻译英文文章……

要点

主要集中在如何用buildSrc目录和Kotlin DSL脚本构建一个Gradle依赖管理系统,你也会学到这样做相对使用传统Groovy代码的好处。

如果你倾向于通过视频来看这篇博客,文末附有一个Youtube视频。

问题

众所周知在一个快速发展的项目中维护依赖是一个乏味的工作,而传统的Groovy脚本没有code navigation、自动补全,再加上性能问题和运行时错误让这一切变得更糟糕。

更重要的是,多数安卓开发者不懂Groovy,甚至我也不知道我之前在用Groovy做啥。

感谢Gradle团队和社区的工作提供了一个顺畅安全的构建流程,他们提出的最棒的主意之一就是用Kotlin DSL脚本写buildSrc目录。

解决

依赖库引入和自定义task不应该放到构建脚本中,它们应该被声明到一个独立文件中再被构建脚本使用。在这个实现的早期,开发者习惯于创建一个Gradle文件来声明所有库并在构建脚本中使用。

这确实在一定程度上解决了问题,你可以在这篇文章读到这种方法。但这个简单方案不能解决类似自动补全和code navigation的问题,这使得在长远上看这个方案不够可靠。在这之外,buildSrc似乎有希望解决这个问题。

这个目录被当作一个included build看待。在发现这个目录之后,Gradle自动编译和测试它的代码,并将编译结果放到你的构建脚本的class path中。在一个多模块的工程中只能有一个这样的目录,并且要放到工程的顶级目录中。应该优先通过script plugins因为这样更便于管理、重构和测试代码。 ——Gradle团队

创建buildSrc目录

使用Kotlin DSL脚本不但能解决构建脚本中的这些问题,还能得到先进的IDE支持,包括code navigation、编译时错误提示等。最重要的,我们再也不用使用Groovy了。

阅读全文
android项目开发:多线程编程

仍然是《第一行代码》的笔记,不过略过了deprated的内容,并探究了下Handler的工作机制。

上班了,果然没有那么多大块时间写博客了。

Handler

主线程不能进行耗时处理,子线程不能访问UI,所以我们需要异步消息处理机制。

使用


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

    private val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            when(msg.what){
                MSG_UPDATE_TEXT -> binding.textView.text = "Nice to meet you. "
            }
        }
    }

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

    private fun initView() {
        binding.apply {
            changeTextButton.setOnClickListener {
                thread {
                    val msg = Message()
                    msg.what = MSG_UPDATE_TEXT
                    handler.sendMessage(msg)
                }
            }
        }
    }

    companion object {
        private const val MSG_UPDATE_TEXT = 1
    }
}

原理

图片有点多,懒得一张张转移了,去我整理的文档看吧:

安卓Handler异步消息处理机制

阅读全文
截图不可信,我们能信什么

这两天网上流传一件事,又有一个拼多多员工猝死了,而这名员工是98年出生的,年仅23岁

如何看待网传拼多多员工加班后猝死一事?拼多多需要承担哪些责任? - 知乎

之前虽然一直有程序员猝死的案例,但总感觉离自己很远,甚至拿程序员钱多话少死的快这种梗自嘲,但这次我慌了,因为我也是98年的。

之后也在对这个事情保持关注,然后突然在知乎流传一张截图,截图为拼多多自己在上述问题下的回答,并称该回答已经被删除。

v2-965e16d02fbc62ea9ad792318c88d372_r.jpg

这是要搞事情的节奏啊。当然接下来拼多多自然说自己没说过,于是有了第二个问题:拼多多疑似回应「员工猝死」后秒删,是真的吗?拼多多又称截图系谣言,你相信吗? - 知乎

然后知乎小管家发布内容证明拼多多说过:拼多多疑似回应「员工猝死」后秒删,是真的吗?拼多多又称截图系谣言,你相信吗? - 知乎小管家的回答 - 知乎

好吧,拼多多栽了,那么我们有没有别的方法证明这件事情发生过?万一下次是腾讯出了什么问题,小管家扛得住压力吗?

阅读全文
android项目开发:Broadcast

仍然是《第一行代码》的学习笔记,安卓内置广播机制。

Android中每个应用程序都可以对自己感兴趣的广播进行注册,包括来自系统的,和其他应用程序的。

广播分为标准广播和有序广播。

  • 标准广播异步执行,所有BroadcastReceiver几乎同时收到广播的消息。
  • 有序广播 同步执行,只有前一个Receiver逻辑执行完后才会传递给下一个,且可以将广播截断。

阅读全文
android项目开发:Fragment

Fragment

Fragment的使用方式

静态添加Fragment

Fragment的写法

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

    <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button"
            android:layout_gravity="center_horizontal"/>

</LinearLayout>
package top.ntutn.fragmenttest

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

class LeftFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.left_fragment, container, false)
    }
}

静态添加Fragment


<?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="horizontal"
              tools:context=".MainActivity">

    <fragment
            android:id="@+id/leftFragment"
            android:name="top.ntutn.fragmenttest.LeftFragment"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"/>

    <fragment
            android:id="@+id/rightFragment"
            android:name="top.ntutn.fragmenttest.RightFragment"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"/>

</LinearLayout>

阅读全文
android项目开发:持久化

《第一行代码》阅读记录,有关数据持久化存储,略过了数据库的方式。

持久化

文件存储

先暂时只是写了个demo,有需要再深入看。


    private fun saveText(inputText: String) {
        try {
            val output = openFileOutput("data", Context.MODE_PRIVATE)
            val writer = BufferedWriter(OutputStreamWriter(output))
            writer.use {
                it.write(inputText)
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    private fun loadText(): String {
        val content = StringBuilder()
        try {
            val input = openFileInput("data")
            val reader = BufferedReader(InputStreamReader(input))
            reader.use { r ->
                r.forEachLine {
                    content.appendLine(it)
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return content.toString()
    }

SharedPreference


            saveButton.setOnClickListener {
                getSharedPreferences("data", Context.MODE_PRIVATE).edit {
                    putString("name", "Tom")
                    putInt("age", 28)
                    putBoolean("married", false)
                }
            }

            restoreButton.setOnClickListener {
                getSharedPreferences("data", Context.MODE_PRIVATE).apply {
                    val name = getString("name", "苏珊")
                    val age = getInt("age", 15)
                    val isMarried = getBoolean("married", false)
                    Toast.makeText(
                        [email protected],
                        "${name}年龄${age}岁${if (isMarried) "已婚" else "未婚"}",
                        Toast.LENGTH_LONG
                    ).show()
                }
            }

数据库

基本用法demo


package top.ntutn.databasetest

import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.util.Log
import android.widget.Toast

class MyDatabaseHelper(val context: Context, name: String, version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    private val createBook = """
        create table Book (
            id integer primary key autoincrement,
            author text,
            price real,
            pages integer,
            name text
        )
    """.trimIndent()
    private val createCategory = """
        create table Category (
            id integer primary key autoincrement,
            category_name text,
            category_code integer
        )
    """.trimIndent()

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)
        db.execSQL(createCategory)
        Toast.makeText(context, "建立数据库完成", Toast.LENGTH_LONG).show()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        if (oldVersion == newVersion) return
        Log.d(javaClass.simpleName, "旧版是${oldVersion},新版是${newVersion},数据库升级开始")
        db.execSQL("drop table if exists Book")
        db.execSQL("drop table if exists Category")
        onCreate(db)
    }
}

fun cvOf(vararg pairs: Pair<String, Any?>): ContentValues {
    val cv = ContentValues()
    for (pair in pairs) {
        val key = pair.first
        when (val value = pair.second) {
            is Int -> cv.put(key, value)
            is Long -> cv.put(key, value)
            is Short -> cv.put(key, value)
            is Float -> cv.put(key, value)
            is Double -> cv.put(key, value)
            is Boolean -> cv.put(key, value)
            is String -> cv.put(key, value)
            is Byte -> cv.put(key, value)
            is ByteArray -> cv.put(key, value)
            null -> cv.putNull(key)
            else -> throw IllegalArgumentException("不支持的cv类型")
        }
    }
    return cv
}

fun Cursor.getString(name: String): String = getString(getColumnIndex(name))

fun Cursor.getInt(name: String): Int = getInt(getColumnIndex(name))

fun Cursor.getDouble(name: String): Double = getDouble(getColumnIndex(name))

package top.ntutn.databasetest

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import top.ntutn.databasetest.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)

        val dbHelper = MyDatabaseHelper(this, "BookStore.db", 6)

        binding.createDatabaseButton.setOnClickListener {
            dbHelper.writableDatabase
        }
        binding.addDataButton.setOnClickListener {
            val db = dbHelper.writableDatabase
            val values1 = cvOf(
                "name" to "The Da Vinci Code",
                "author" to "Dan Brown",
                "pages" to 454,
                "price" to 16.96
            )
            db.insert("Book", null, values1)
            val values2 = cvOf(
                "name" to "The Lost Symbol",
                "author" to "Dan Brown",
                "pages" to 510,
                "price" to 19.95
            )
            db.insert("Book", null, values2)
        }
        binding.updateDataButton.setOnClickListener {
            val db = dbHelper.writableDatabase
            val values = cvOf("price" to 10.99)
            db.update("Book", values, "name=?", arrayOf("The Da Vinci Code"))
        }
        binding.deleteDataButton.setOnClickListener {
            val db = dbHelper.writableDatabase
            db.delete("Book", "pages > ?", arrayOf("500"))
        }
        binding.queryDataButton.setOnClickListener {
            val db = dbHelper.writableDatabase
            val cursor = db.query("Book", null, null, null, null, null, null)
            if (cursor.moveToFirst()) {
                do {
                    val name = cursor.getString("name")
                    val author = cursor.getString("author")
                    val pages = cursor.getInt("pages")
                    val price = cursor.getDouble("price")
                    Log.d(
                        javaClass.simpleName,
                        "name: $name, author: $author, pages: $pages, price: $price"
                    )
                } while (cursor.moveToNext())
            }
            cursor.close()
        }
    }
}

阅读全文
android项目开发:Activity

title: android项目开发:Activity author: 归零幻想 publishDate: 2020-12-15 editDate: 2020-12-15 tags: [android, 第一行代码, Kotlin, Activity]

仍然是《第一行代码》的读书笔记,可能引用原书的定义和描述,或代码案例。

Activity

Activity基本用法

Android讲究设计逻辑与视图分离,一般Activity都会对应一个布局文件(XML文件)。

所有的Activity都要在AndroidManifest中注册才生效。

Activity可以创建菜单。首先在res/menu下创建一个xml文件(Android Studio中也提供了可视化编辑的方法):

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:android="http://schemas.android.com/apk/res/android">

    <item
            android:id="@+id/add_item"
            android:title="Add"/>
    <item
            android:id="@+id/remove_item"
            android:title="Remove"/>
</menu>

重写两个方法

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    menuInflater.inflate(R.menu.main, menu)
    return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    when (item.itemId) {
        R.id.add_item -> Toast.makeText(this, "Add a book.", Toast.LENGTH_LONG).show()
        R.id.remove_item -> Toast.makeText(this, "Remove a book.", Toast.LENGTH_LONG).show()
    }
    return true
}

photo_2020-12-10_21-00-32.jpg

finish()方法可以关闭一个Activity。

阅读全文
作为一名男性,你愿意和丁真互换人生吗

忘记是哪一天了,一个叫『丁真』的人突然刷屏,热搜十条有6条都是他。但我见怪不怪了,微博热搜本来就是个笑话。

然后看看贴吧,知乎……全是他。

那就看看,这回是谁,为啥火了。一个放牛小伙,正巧被摄影师拍了,眼睛澄澈,笑容阳光,剩下的全是营销。

『为何贴吧男性普遍仇视辱骂丁真?』我玩贴吧,但对丁真没啥恶感,只是对这一堆热搜有恶感:**明明这两天有更值得关注的事情的。**好吧,咱也不知道,咱也不敢问,你们知道有几条热搜被盖过去了就行了。

『作为一名男性,你愿意和丁真互换人生吗?』好么,没完没了了,还不惜故意挑起性别对立。我就引用某个网友1的回答


  1. @在下头不是很铁了,原回答在某回答的回复里,但无法直接通过链接打开,大概是被限流了。转载已获授权。

阅读全文
android项目开发:项目结构

虽然已经在字节实习并拿到转正offer,但实际我自己感受我现在对安卓基础知识掌握的程度还差很多,感觉写业务代码本身并不能带来多少提升。

恰逢前两天看到黄正楠那里有一本看上去不错的书1,而在淘宝也在打折,就买了一本。

那么从Hello Wrold开始,先看看安卓项目的项目结构。


  1. 《第一行代码》

阅读全文
从QBASIC开始

前两天看到一个知乎问题,拍了一张数学课本的截图,里面是一段程序,问如何才能运行这种程序。1

看到这熟悉的语法有些感慨,毕竟这门语言就是我编程入门学习的第一门语言了。

2020-12-02_08-46.png

记得那时没有什么娱乐手段,只能翻翻我姐的旧书——当然多数看不懂。少数能看懂的,就包括一本QBASIC的编程入门教程。

单词不认识,字母总还是认识的。于是我学的Hello World其实是P-R-I-N-T-"H-E-L-L-O-W-O-R-L-D"。看着都觉得累…… 没有电脑,就直接在脑袋里推演,在纸上执行程序。家里人还以为我在学英语~

阅读全文
avatar

归零幻想

邮件:[email protected]
Telegram: @zerofancy

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

友情链接