溺死在零梦里吧。

Someone famous in 归零幻想

我的黑历史被github埋到北极了

发布时间:2021-01-26  修改时间:2021-01-26  作者:归零幻想

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

QQ20210126-143812@2x.png

So what happened?

阅读全文 ▼

android项目开发:多线程编程

发布时间:2021-01-06  修改时间:2021-01-06  作者:归零幻想

仍然是《第一行代码》的笔记,不过略过了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异步消息处理机制

阅读全文 ▼

截图不可信,我们能信什么

发布时间:2021-01-04  修改时间:2021-01-04  作者:归零幻想

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

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

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

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

v2-965e16d02fbc62ea9ad792318c88d372_r.jpg

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

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

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

阅读全文 ▼

android项目开发:通知

发布时间:2021-01-03  修改时间:2021-01-03  作者:归零幻想

仍然是第一行代码的笔记,这篇是有关通知的,最基本的用法。

通知的相关知识

通知是什么不再赘述,这里只记录些重要但没接触的概念。

通知渠道在8.0(O)引入。要求APP将通知分类,通过不同渠道进行分发,用户可以选择性禁用某个渠道的通知,或者调整优先等级。

通知可以有不同的重要等级,有四种:IMPORTANCE_HIGHIMPORTANCE_DEFAULTIMPORTANCE_LOWIMPORTANCE_MIN。根据重要等级不同,通知可能有不同的展现策略,比如在前台提示甚至播放声音。

在通知渠道创建时通知的重要等级也就确定了,之后不能再被APP修改。

阅读全文 ▼

android项目开发:持久化

发布时间:2020-12-24  修改时间:2020-12-28  作者:归零幻想

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

持久化

文件存储

先暂时只是写了个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()
        }
    }
}
阅读全文 ▼

禁用macOS的窗口标签

发布时间:2020-12-22  修改时间:2020-12-22  作者:归零幻想

作为一个对更新相对激进的用户,我当然是第一时间升级了最新的macOS Big Sur。说实在的,这名字给我的第一印象并不好,因为被我看成了『Big Bug』。

圆角变大了,还有我一开始比较喜欢的功能,姑且称之为窗口标签。在BigSur中,当你打开两个全屏的Android Studio,它们将出现在同一个窗口,窗口上方出现不同的标签页,和浏览器一样。

好景不长,这个功能表现很不稳定,我不得不考虑干掉这个功能。如果只是没有成功触发也就算了,大不了当没升级用,但它常常会把一些弹出窗口也搞成和原窗口并列的标签。比如当你rename一个文件时,弹出的窗口有时就会并列到标签上,然后Android Studio就卡死了。

好吧,既然它开始影响我的工作效率了,我就找了找禁用的方法1


defaults write com.google.android.studio AppleWindowTabbingMode manual
阅读全文 ▼

android项目开发:Broadcast

发布时间:2020-12-22  修改时间:2020-12-22  作者:归零幻想

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

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

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

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

android项目开发:UI设计

发布时间:2020-12-21  修改时间:2020-12-21  作者:归零幻想

仍然是《第一行代码》的学习笔记,这里记录的东西相对少一点,UI上的东西还是更多在实际项目中感受到。比如我想没有必要写TextView的介绍吧。

控件的使用方法

dp是一种屏幕密度无关的尺寸单位,可以保证在不同分辨率的手机上显示效果尽可能一致。

match_parent表示让当前控件大小和父布局的大小一致。

wrap_content表示让当前控件的大小能正好包裹里面的内容。

android:gravity指定控件内的内容对齐方式,有topbottomstartendcenter等可选,可以用|指定多个值。比如center等价于center_vertical|center_horizonal

基本布局

LinearLayout

线性布局,通过android:orientation指定方向。

有一个重要属性:android:layout_weight,它将控件已经占用的空间减掉后按照比重分给各个控件。一般我们直接指定android:layout_width为0dp,而给它指定一个比重,这样控件的尺寸将占满剩余空间。

阅读全文 ▼

android项目开发:Fragment

发布时间:2020-12-21  修改时间:2020-12-21  作者:归零幻想

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项目开发:Activity

发布时间:2020-12-15  修改时间:2020-12-15  作者:归零幻想

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

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。

阅读全文 ▼