willkernel

Lifelong learning


  • 首页

  • 归档

  • 分类

  • 关于

  • 搜索

C++(一)

发表于 2018-04-08 | 分类于 C++ | 阅读次数:

简介

C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程
C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点
C++ 是由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计开发的。C++ 进一步扩充和完善C,是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序
注意:使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查
2014 ISO/IEC 14882:2014 C++14 第四个C++标准

C++ 完全支持面向对象的程序设计,包括面向对象开发的四大特性:
封装
抽象
继承
多态

标准库
标准的 C++ 由三个重要部分组成:
核心语言,提供了所有构件块,包括变量、数据类型和常量,等等。
C++ 标准库,提供了大量的函数,用于操作文件、字符串等。
标准模板库(STL),提供了大量的方法,用于操作数据结构等

  • MinGW

语法

  • 程序结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <iostream>
    using namespace std;

    // main() 是程序开始执行的地方 ,单行注释

    int main()
    {
    cout << "Hello World"; // 输出 Hello World
    return 0;
    }
    • 包含头文件<iostream>
    • using namespace std;使用std命名空间
    • int main()主函数,程序从这里开始执行
    • return 0 终止main()函数,返回0
  • 编译执行
    1
    2
    3
    4
    $ touch hello.cpp
    $ g++ hello.cpp
    $ ./a.exe
    Hello World
阅读全文 »

SQLite(三)

发表于 2018-04-08 | 分类于 Android | 阅读次数:

SQLite Java

Source Insight 使用

发表于 2018-04-07 | 分类于 Tools | 阅读次数:
  • New project
  • Add all
  • close
    阅读全文 »

Java 小知识

发表于 2018-04-02 | 分类于 Java | 阅读次数:
  • transient vs volatile不同之处

    • volatile和序列化无关,写数据到改修饰的变量,会同步刷新到内存中,访问变量时从内存中读取,与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分,不具备原子性。只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
      1.对变量的写操作不依赖于当前值
      2.该变量没有包含在具有其他变量的不变式中

    • 第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性

  • 如果凑巧两个线程在同一时间使用不一致的值执行 setLower 和 setUpper 的话,则会使范围处于不一致的状态。例如,如果初始状态是 (0, 5),同一时间内,线程 A 调用 setLower(4) 并且线程 B 调用 setUpper(3),显然这两个操作交叉存入的值是不符合条件的,那么两个线程都会通过用于保护不变式的检查,使得最后的范围值是 (4, 3) —— 一个无效值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @NotThreadSafe 
    public class NumberRange {
    private int lower, upper;

    public int getLower() { return lower; }
    public int getUpper() { return upper; }

    public void setLower(int value) {
    if (value > upper)
    throw new IllegalArgumentException(...);
    lower = value;
    }

    public void setUpper(int value) {
    if (value < lower)
    throw new IllegalArgumentException(...);
    upper = value;
    }
    }
  • 正确使用volatile模式

    • 状态标志,公共特性是:通常只有一种状态转换;shutdownRequested 标志从 false 转换为 true,然后程序停止

      1
      2
      3
      4
      5
      6
      7
      8
      9
       volatile boolean shutdownRequested;
      ...
      public void shutdown() { shutdownRequested = true; }

      public void doWork() {
      while (!shutdownRequested) {
      // do stuff
      }
      }
    • 其他待续

  • 序列化会排除被transient修饰的变量,反序列化会返回相应的默认值

Window和WindowManager

发表于 2018-04-02 | 分类于 Android | 阅读次数:
  • Window和WindowManager
    Window表示一个窗口,可以实现在桌面上显示悬浮窗,Window是一个抽象类,具体实现是PhoneWindow。通过WindowManager创建Window,WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService,WindowManager和WindowManagerService交互是一个IPC过程,Window实际是View的直接管理者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

    val btn = Button(baseContext)
    btn.text = "button"
    btn.setOnClickListener { toast("Window Demo") }
    val layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT,
    LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSLUCENT)
    layoutParams.type = LayoutParams.TYPE_SYSTEM_OVERLAY/TYPE_SYSTEM_ERROR/TYPE_SYSTEM_OVERLAY/TYPE_APPLICATION_PANEL
    //不需要获取焦点,不需要接收各种输入事件
    layoutParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE
    //window 区域以为的单击事件传递给底层window,当前window区域以内的自己处理,否则其他window无法收到单击事件
    .or(LayoutParams.FLAG_NOT_TOUCH_MODAL)
    //window 显示在锁屏上
    .or(LayoutParams.FLAG_SHOW_WHEN_LOCKED)
    layoutParams.gravity = Gravity.LEFT.or(Gravity.TOP)
    layoutParams.x = 100
    layoutParams.y = 300
    windowManager.addView(btn, layoutParams)
  • public interface WindowManager extends ViewManager

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public interface ViewManager
    {
    /**
    * Assign the passed LayoutParams to the passed View and add the view to the window.
    * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
    * errors, such as adding a second view to a window without removing the first view.
    * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
    * secondary {@link Display} and the specified display can't be found
    * (see {@link android.app.Presentation}).
    * @param view The view to be added to this window.
    * @param params The LayoutParams to assign to view.
    */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
    }
  • 添加触摸事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    val btn = Button(baseContext)
    btn.isClickable = true
    btn.text = "button"

    // btn.setOnClickListener { toast("Window Demo") }
    val layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT,
    LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSLUCENT)
    // layoutParams.type = LayoutParams.TYPE_SYSTEM_OVERLAY
    layoutParams.type =LayoutParams.TYPE_APPLICATION_PANEL
    layoutParams.flags =
    //不需要获取焦点,不需要接收各种输入事件
    LayoutParams.FLAG_NOT_FOCUSABLE
    //window 区域以为的单击事件传递给底层window,当前window区域以内的自己处理,否则其他window无法收到单击事件
    .or(LayoutParams.FLAG_NOT_TOUCH_MODAL)
    //window 显示在锁屏上
    .or(LayoutParams.FLAG_SHOW_WHEN_LOCKED)
    layoutParams.gravity = Gravity.LEFT.or(Gravity.TOP)
    layoutParams.x = 100
    layoutParams.y = 300
    btn.setOnTouchListener { v, event ->
    when (event.action) {
    MotionEvent.ACTION_MOVE -> {
    layoutParams.x = event.rawX.toInt()
    layoutParams.y = event.rawY.toInt()
    windowManager.updateViewLayout(btn, layoutParams)
    }
    else -> {
    Log.i("touch", "else")
    }
    }
    return@setOnTouchListener false
    }
    windowManager.addView(btn, layoutParams)

Window内部机制

Window是抽象概念,每一个Window对应一个View和ViewRootImpl,以View的形式存在

  • 接口WindowManager实现类WindowManagerImpl
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.updateViewLayout(view, params);
    }

    @Override
    public void removeView(View view) {
    mGlobal.removeView(view, false);
    }

SQLite(二)

发表于 2018-04-02 | 分类于 Android | 阅读次数:

数据库操作

  • 创建

    1
    sqlite3 DatabaseName.db
  • 查看数据库列表

    1
    .databases
  • 导出完整的数据库到一个文本文件

    1
    2
    3
    4
    sqlite3 DatabaseName.db .dump > DatabaseName.sql

    恢复数据库
    sqlite3 DatabaseName.db < DatabaseName.sql
  • SQLite 的 ATTACH DATABASE 语句是用来选择一个特定的数据库,使用该命令后,所有的 SQLite 语句将在附加的数据库下执行

    如果数据库尚未被创建,将创建一个数据库,如果数据库已存在,则把数据库文件名称与逻辑数据库 ‘Alias-Name’ 绑定在一起

    1
    ATTACH DATABASE 'DatabaseName' As 'Alias-Name';
  • DETACH DTABASE 分离数据库,上述附件数据库的反操作,DETACH 命令将只断开给定名称的连接,而其余的仍然有效,无法分离 main 或 temp 数据库

    1
    DETACH DATABASE 'Alias-Name';
  • 创建表 CREATE TABLE创建表 .tables 命令来验证表是否已成功创建 .schema命令得到表的完整信息

  • 删除表 DROP TABLE database_name.table_name;
  • INSERT 添加新的记录,新的数据

    1
    2
    3
    4
    5
    6
    7
    8
    INSERT INTO TABLE_NAME [(column1, column2, column3,...columnN)]  
    VALUES (value1, value2, value3,...valueN);

    省略列名,保证顺序一致
    INSERT INTO TABLE_NAME VALUES (value1,value2,value3,...valueN);

    INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)
    VALUES (6, 'Kim', 22, 'South-Hall', 45000.00 );
  • 将第二个表数据添加到第一个表

    1
    2
    3
    4
    INSERT INTO first_table_name [(column1, column2, ... columnN)] 
    SELECT column1, column2, ...columnN
    FROM second_table_name
    [WHERE condition];
  • SELECT 获取表中数据,返回结果集

    1
    2
    3
    4
    5
    6
    7
    8
    SELECT column1, column2, columnN FROM table_name;

    SELECT * FROM table_name;


    .header on
    .mode column
    .width 10, 20, 10
  • Schema 信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    列出所有在数据库中创建的表
    SELECT tbl_name FROM sqlite_master WHERE type = 'table';
    产生以下结果:

    tbl_name
    ----------
    COMPANY

    列出关于 COMPANY 表的完整信息
    SELECT sql FROM sqlite_master WHERE type = 'table' AND tbl_name = 'COMPANY';
    假设在 testDB.db 中已经存在唯一的 COMPANY 表,产生以下结果:

    CREATE TABLE COMPANY(
    ID INT PRIMARY KEY NOT NULL,
    NAME TEXT NOT NULL,
    AGE INT NOT NULL,
    ADDRESS CHAR(50),
    SALARY REAL
    )

运算符

  • 算术运算符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    sqlite> select 10+20;
    30
    sqlite> .mode line
    sqlite> select 10-20;
    10-20 = -10
    sqlite> select 1/3;
    1/3 = 0
    sqlite> select 12%5;
    12%5 = 2
  • 比较运算符

  • 逻辑运算符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    WHERE AGE >= 25 AND/OR SALARY >= 65000;
    WHERE AGE IS NOT NULL
    WHERE NAME LIKE 'Ki%';
    WHERE NAME GLOB 'Ki*';
    WHERE AGE NOT IN ( 25, 27 );
    WHERE AGE BETWEEN 25 AND 27;

    SQL 子查询,子查询查找 SALARY > 65000 的带有 AGE 字段的所有记录,后边的 WHERE 子句与 EXISTS 运算符一起使用
    SELECT AGE FROM COMPANY WHERE EXISTS (SELECT AGE FROM COMPANY WHERE SALARY > 65000);

    AGE
    ----------
    25
    23
    25

    SELECT * FROM COMPANY WHERE AGE > (SELECT AGE FROM COMPANY WHERE SALARY > 65000);
  • 位运算符 A=60, B=13

表达式

  • 布尔表达式

    1
    SELECT * FROM COMPANY WHERE SALARY = 10000;
  • 数值表达式

    1
    2
    SELECT COUNT(*) AS "RECORDS" FROM COMPANY; 
    RECORDS = 7
  • 日期表达式

    1
    2
    3
    4
    select CURRENT_TIMESTAMP;
    CURRENT_TIMESTAMP
    -------------------
    2018-04-07 15:36:11

Where子句

1
2
3
4
5
6
7
SELECT * FROM COMPANY WHERE AGE BETWEEN 25 AND 27;

SELECT AGE FROM COMPANY
WHERE EXISTS (SELECT AGE FROM COMPANY WHERE SALARY > 65000);

SELECT * FROM COMPANY
WHERE AGE > (SELECT AGE FROM COMPANY WHERE SALARY > 65000);

AND/OR

1
2
3
SELECT * FROM COMPANY WHERE AGE >= 25 AND SALARY >= 65000;

SELECT * FROM COMPANY WHERE AGE >= 25 OR SALARY >= 65000;

Update

1
2
3
UPDATE COMPANY SET ADDRESS = 'Texas' WHERE ID = 6;

UPDATE COMPANY SET ADDRESS = 'Texas', SALARY = 20000.00;

Delete

1
2
DELETE FROM COMPANY WHERE ID = 7;
DELETE FROM COMPANY;

Like

通配符 百分号(%)代表零个、一个或多个数字或字符。下划线(_)代表一个单一的数字或字符

1
2
3
4
5
AGE 以 2 开头的所有记录
SELECT * FROM COMPANY WHERE AGE LIKE '2%';

ADDRESS 文本里包含一个连字符(-)的所有记录
SELECT * FROM COMPANY WHERE ADDRESS LIKE '%-%';

Glob

星号(*)代表零个、一个或多个数字或字符。问号(?)代表一个单一的数字或字符,大小写敏感

1
SELECT * FROM COMPANY WHERE AGE  GLOB '2*';

Limit

  • 限制返回数据数量

    1
    SELECT * FROM COMPANY LIMIT 6;
  • 特定的偏移开始提取记录

    1
    2
    3
    4
    5
    6
    7
    SELECT * FROM COMPANY LIMIT 3 OFFSET 2;

    ID NAME AGE ADDRESS SALARY
    ---------- ---------- ---------- ---------- ----------
    3 Teddy 23 Norway 20000.0
    4 Mark 25 Rich-Mond 65000.0
    5 David 27 Texas 85000.0

Order By

  • 按 NAME 和 SALARY 升序降序排序
    1
    SELECT * FROM COMPANY ORDER BY NAME, SALARY [ASC|DESC];

Group By

  • 对相同的数据进行分组,在 SELECT 语句中,GROUP BY 子句放在 WHERE 子句之后,放在 ORDER BY 子句之前
    1
    2
    3
    4
    5
    6
    7
    8
    9
    查找名称,薪资总和数据,以名称分组
    SELECT NAME, SUM(SALARY) FROM COMPANY GROUP BY NAME;
    SELECT NAME, SUM(SALARY) FROM COMPANY GROUP BY NAME ORDER BY NAME;

    NAME SUM(SALARY)
    ---------- -----------
    Allen 15000.0
    David 85000.0
    James 10000.0

Having

  • 过滤分组数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    SELECT column1, column2
    FROM table1, table2
    WHERE [ conditions ]
    GROUP BY column1, column2
    HAVING [ conditions ]
    ORDER BY column1, column2

    名称出现2次的数据
    SELECT * FROM COMPANY GROUP BY name HAVING count(name) > 2;

Distinct

  • 获取重复记录中的唯一一次记录
    1
    2
    3
    4
    5
    6
    SELECT DISTINCT column1, column2,.....columnN 
    FROM table_name
    WHERE [condition]

    去掉重复名称
    SELECT DISTINCT name FROM COMPANY;

SQLite(一)

发表于 2018-03-30 | 分类于 Android | 阅读次数:

简介

SQLite特点:无服务器、零配置、事务性的 SQL 数据库引擎,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接;非常小轻量级,兼容ACID原则,是进程或线程安全的

ACID是一组强调高可靠性的数据库系统设计原则,在软件崩溃甚至是硬件故障的情况下,数据也不会损坏。当你需要依赖兼容ACID原则的业务时,你不必重复造轮子去实现一致性检查和崩溃恢复机制。如果你有额外的安全保证机制,可以调整牺牲掉ACID的一些可靠性换取更高的性能和数据吞吐量
A: atomicity (原子性)
C: consistency (一致性)
I: isolation (隔离性)
D: durability (持久性)

SQL92 不支持的特性如下
RIGHT OUTER JOIN 只实现了 LEFT OUTER JOIN
FULL OUTER JOIN 只实现了 LEFT OUTER JOIN
ALTER TABLE 支持 RENAME TABLE 和 ALTER TABLE 的 ADD COLUMN variants 命令,不支持 DROP COLUMN、ALTER COLUMN、ADD CONSTRAINT
Trigger 支持 FOR EACH ROW 触发器,但不支持 FOR EACH STATEMENT 触发器
VIEWs 在 SQLite 中,视图是只读的,不可以在视图上执行 DELETE、INSERT 或 UPDATE 语句
GRANT 和 REVOKE 可以应用的唯一的访问权限是底层操作系统的正常文件访问权限

阅读全文 »

Kotlin for Android(七)

发表于 2018-03-25 | 分类于 Kotlin | 阅读次数:

创建业务逻辑访问数据

从数据库获取数据
检查是否存在对应星期的数据
如果有,返回UI并且渲染
如果没有,请求服务器获取数据
结果被保存在数据库中并且返回UI渲染

  • 数据源应该是一个具体的实现,这样就可以被容易地修改,所以增加一些额外的代码,然后把 command 从数据访问中抽象出来听

    1
    2
    3
    4
    interface ForecastDataSource {
    fun requestForecastByZipCode(zipCode: Long, date: Long): For
    ecastList?
    }
  • 使用数据库的数据源和服务端数据源。顺序是很重要的,因为它会根据顺序去遍历这个sources,然后一旦获取到有效的返回值就会停止查询。逻辑顺序是先在本地查询(本地数据库中),然后再通过API查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class ForecastProvider(private val source: List<ForecastDataSource> = ForecastProvider.SOURCES) {
    companion object {
    val DAY_IN_MILLIS = 1000 * 60 * 60 * 24
    val SOURCES = listOf(ForecastDb(), ForecastServer())
    }

    fun requestByZipCode(zipCode: Long, days: Int): ForecastList = source.firstResult {
    requestSource(it, days, zipCode)
    }

    private fun requestSource(source: ForecastDataSource, days: Int, zipCode: Long): ForecastList? {
    val res = source.requestForecastByZipCode(zipCode, todayTimeSpan())
    return if (res != null && res.size >= days) res else null
    }

    private fun todayTimeSpan() = System.currentTimeMillis() / DAY_IN_MILLIS * DAY_IN_MILLIS
    }
阅读全文 »

Kotlin for Android(六)

发表于 2018-03-24 | 分类于 Kotlin | 阅读次数:

Kotlin中的null安全

  • 在Kotlin中一切都是对象(甚至是Java中原始数据类型),一切都是可null的。所以,当然我们可以有一个可null的integer,检查了一个对象的可null性,之后这个对象就会自动转型成不可null类型,这就是Kotlin编译器的智能转换,在 if 中, a 从 Int? 变成了 Int ,所以我们可以不需要再检查可null性而直接使用它,if 代码之外,又得检查处理,仅仅在变量当前不能被改变的时候才有效val 属性或者本地( val or var )变量,因为这个value可能被另外的线程修改,这时前面的检查会返回false

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    val a:Int?=null
    ...
    if(a!=null){
    a.toString()
    }

    简化代码
    val a: Int? = null
    ...
    a?.toString()

    val a:Int? = null
    val myString = a?.toString() ?: ""
  • throw 和 return 都是表达式

    1
    2
    val myString = a?.toString() ?: return false
    val myString = a?.toString() ?: throw IllegalStateException()
  • 我们确定我们是在用一个非null变量,但是他的类型却是可null的。我们可以使用 !!操作符来强制编译器执行可null类型时跳过限制检查,此时确定了不为null,直接定义为非null对象

    1
    2
    val a: Int? = null
    a!!.toString() throw KotlinNullPointerException

可null性和Java库

  • Java中在一些获取对象的方法在Kotlin中显示返回 Any!这表示让开发者自己决定是否这个变量是否可null

    1
    2
    3
    4
    5
    6
    public class NullTest {
    @Nullable
    public Object getObject(){
    return "";
    }
    }
  • 编译不通过

  • 比如重写 Activity 的 onCraete 函数,我们可以决定是否让 savedInstanceState 可null,都会被编译,但是第二种是错误的,因为一个Activity很可能接收到一个null的bundle
    1
    2
    3
    4
    override fun onCreate(savedInstanceState: Bundle?) {
    }
    override fun onCreate(savedInstanceState: Bundle) {
    }

Kotlin for Android (五)

发表于 2018-03-24 | 分类于 Kotlin | 阅读次数:

创建SQLiteHelper

一般使用SqliteOpenHelper 去调用 getReadableDatabase() 或者 getWritableDatabase() ,然后我们可以执行我们的搜索并拿到结果。在这之后,我们不能忘记调用 close()

ManagedSqliteOpenHelper
  • 函数中,最后一行表示返回值。因为T没有任何的限制,所以我们可以返回任何对象。甚至如果我们不想返回任何值就使用 Unit,try-finally保证关闭数据库
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    val result = forecastDbHelper.use {
    val queriedObject = ...
    queriedObject
    }

    fun <T> use(f: SQLiteDatabase.() -> T): T {
    try {
    return openDatabase().f()
    } finally {
    closeDatabase()
    }
    }
定义表
  • CityForecastTable 提供了表的名字还有需要列:一个id(这个城市的zipCode),城市的名称和所在国家,DayForecast天气信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    object CityForecastTable {
    val NAME = "CityForecast"
    val ID = "_id"
    val CITY = "city"
    val COUNTRY = "country"
    }

    object DayForecastTable {
    val NAME = "DayForecast"
    val ID = "_id"
    val DATE = "date"
    val DESCRIPTION = "description"
    val HIGH = "high"
    val LOW = "low"
    val ICON_URL = "iconUrl"
    val CITY_ID = "cityId"
    }
实现SqliteOpenHelper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun SQLiteDatabase.createTable(tableName: String, ifNotExists: Boolean = false, vararg columns: Pair<String, SqlType>) {
val escapedTableName = tableName.replace("`", "``")
val ifNotExistsText = if (ifNotExists) "IF NOT EXISTS" else ""
execSQL(
columns.map { col ->
"${col.first} ${col.second.render()}"
}.joinToString(", ", prefix = "CREATE TABLE $ifNotExistsText `$escapedTableName`(", postfix = ");")
)
}

db.createTable(CityForecastTable.NAME, true,
Pair(CityForecastTable.ID, INTEGER + PRIMARY_KEY),
Pair(CityForecastTable.CITY, TEXT),
Pair(CityForecastTable.COUNTRY, TEXT))

第一个参数是表的名称
第二个参数,当是true的时候,创建之前会检查这个表是否存在。
第三个参数是一个 Pair 类型的 vararg 参数。 vararg 也存在在Java中,
这是一种在一个函数中传入联系很多相同类型的参数。这个函数也接收一个对
象数组。
Anko中有一种叫做 SqlType 的特殊类型,它可以与 SqlTypeModifiers 混合,
比如 PRIMARY_KEY 。 + 操作符像之前那样被重写了。这个 plus 函数会把两者
通过合适的方式结合起来,然后返回一个新的 SqlType

1
2
3
4
fun SqlType.plus(m: SqlTypeModifier) : SqlType {
return SqlTypeImpl(name, if (modifier == null) m.toString()
else "$modifier $m")
}

  • public fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)因为带有一个函数参数的函数可以被用于inline,所以结果非常清晰:val pair = object1 to object2
  • 简化后的代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    class ForecastDbHelper(ctx: Context = App.instance) : ManagedSQLiteOpenHelper(App.instance,
    ForecastDbHelper.DB_NAME, null, ForecastDbHelper.DB_VERSION) {
    override fun onCreate(db: SQLiteDatabase) {
    db.createTable(CityForecastTable.NAME, true,
    CityForecastTable.ID to INTEGER + PRIMARY_KEY,
    CityForecastTable.CITY to TEXT,
    CityForecastTable.COUNTRY to TEXT)

    db.createTable(DayForecastTable.NAME, true,
    DayForecastTable.ID to INTEGER + PRIMARY_KEY,
    DayForecastTable.DATE to INTEGER,
    DayForecastTable.DESCRIPTION to TEXT,
    DayForecastTable.HIGH to INTEGER,
    DayForecastTable.LOW to INTEGER,
    DayForecastTable.ICON_URL to TEXT,
    DayForecastTable.CITY_ID to INTEGER)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    db.dropTable(CityForecastTable.NAME, true)
    db.dropTable(DayForecastTable.NAME, true)
    onCreate(db)
    }

    companion object {
    val DB_NAME = "forecast.db"
    val DB_VERSION = 1
    val instance: ForecastDbHelper by lazy {
    ForecastDbHelper()
    }
    }
    }

    instance 这个属性使用了 lazy 委托,它表示直到它真的被调用才会被创建。
    如果数据库没有被使用,就没有去创建这个对象。一般 lazy 委托的代码块可以阻止在多个不同的线程中创建多个对象。这个只会发生在两个线程在同时访问这个 instance 对象, lazy 委托是线程安全的

集合和函数操作符

  • 本地接口

    Iterable:父类。所有我们可以遍历一系列的都是实现这个接口
    MutableIterable:一个支持遍历的同时可以执行删除的Iterables
    Collection:这个类相是一个范性集合。我们通过函数访问可以返回集合的size、是否为空、是否包含一个或者一些item。这个集合的所有方法提供查询,因为collection是不可修改的
    MutableCollection:一个支持增加和删除item的Collection。它提供了额外的函数,比如 add 、 remove 、 clear 等等
    List:可能是最流行的集合类型。它是一个范性有序的集合。因为它的有序,我们可以使用 get 函数通过position来访问
    MutableList:一个支持增加和删除item的List
    Set:一个无序并不支持重复item的集合
    MutableSet:一个支持增加和删除item的Set
    Map:一个key-value对的collection。key在map中是唯一的,也就是说不能有两对key是一样的键值对存在于一个map中
    MutableMap:一个支持增加和删除item的map

    总数操作符
  • any 如果至少有一个元素符合给出的判断条件,则返回true

    1
    2
    3
    val list = listOf(1, 2, 3, 4, 5, 6)
    assertTrue(list.any { it % 2 == 0 }) true
    assertFalse(list.any { it > 10 }) false
  • all 如果全部的元素符合给出的判断条件,则返回true

    1
    2
    assertTrue(list.all { it < 10 })  true
    assertFalse(list.all { it % 2 == 0 }) false
  • count 返回符合给出判断条件的元素总数

    1
    assertEquals(3, list.count { it % 2 == 0 }) true
  • fold 在一个初始值的基础上从第一项到最后一项通过一个函数累计所有的元素,所有元素相加,并加上初始值

    1
    assertEquals(25, list.fold(4) { total, next -> total + next }) true
  • foldRight 与 fold 一样,但是顺序是从最后一项到第一项

  • foreEach 遍历所有元素,并执行给定的操作

    1
    list.forEach { println(it) }
  • forEachIndexed 与 forEach ,但是我们同时可以得到元素的index

    1
    list.forEachIndexed { index, i -> println("position $index value $i") }
  • max/min 返回最大值/最小值,没有返回null

  • maxBy/minBy 根据给定的函数返回最大/最小的一项,如果没有则返回null

    1
    2
    // The element whose negative is greater
    assertEquals(1, list.maxBy { -it })
  • none 如果没有任何元素与给定的函数匹配,则返回true

    1
    2
    // No elements are divisible by 7
    assertTrue(list.none { it % 7 == 0 }) true
  • reduce与 fold 一样,但是没有一个初始值。通过一个函数从第一项到最后一项进行累
    计

    1
    assertEquals(21, list.reduce { total, next -> total + next })
  • reduceRight与 reduce 一样,但是顺序是从最后一项到第一项

    1
    2
    assertEquals(21, list.reduceRight { total, next -> total + next
    })
  • sumBy返回所有每一项通过函数转换之后的数据的总和

    1
    assertEquals(3, list.sumBy { it % 2 })
过滤操作符
  • drop返回包含去掉前n个元素的所有元素的列表

    1
    assertEquals(listOf(5, 6), list.drop(4))
  • dropWhile返回根据给定函数从第一项开始去掉指定元素的列表

    1
    assertEquals(listOf(3, 4, 5, 6), list.dropWhile { it < 3 })
  • dropLastWhile返回根据给定函数从最后一项开始去掉指定元素的列表

    1
    assertEquals(listOf(1, 2, 3, 4), list.dropLastWhile { it > 4 })
  • filter过滤所有符合给定函数条件的元素

    1
    assertEquals(listOf(2, 4, 6), list.filter { it % 2 == 0 })
  • 1
    2
    3
    4
    val listWithNull = listOf(1, 2, null)
    Log.e(tag, "filterNotNull " + listWithNull.filterNotNull())

    filterNotNull [1, 2]
  • slice 过滤一个list中指定index的元素

    1
    2
    3
    Log.e(tag, "slice " + list.slice(listOf(1, 2, 4)))

    slice [2, 3, 5]
  • take返回从第一个开始的n个元素, takeLast返回从最后一个开始的n个元素,takeWhile返回从第一个开始符合给定函数条件的元素

    1
    2
    3
    assertEquals(listOf(1, 2), list.take(2))
    assertEquals(listOf(5, 6), list.takeLast(2))
    assertEquals(listOf(1, 2), list.takeWhile { it < 3 })
  • flatMap遍历所有的元素,为每一个创建一个集合,最后把所有的集合放在一个集合中

    1
    2
    assertEquals(listOf(1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7),
    list.flatMap { listOf(it, it + 1) })
  • groupBy 返回一个根据给定函数分组后的map

    1
    2
    assertEquals(mapOf("odd" to listOf(1, 3, 5), "even" to listOf(2,
    4, 6)), list.groupBy { if (it % 2 == 0) "even" else "odd" })
  • map返回一个每一个元素根据给定的函数转换所组成的List

    1
    assertEquals(listOf(2, 4, 6, 8, 10, 12), list.map { it * 2 })
  • mapIndexed返回一个每一个元素根据给定的包含元素index的函数转换所组成的List

    1
    2
    assertEquals(listOf (0, 2, 6, 12, 20, 30), list.mapIndexed { ind
    ex, it -> index * it })
  • mapNotNull返回一个每一个非null元素根据给定的函数转换所组成的List

    1
    2
    assertEquals(listOf(2, 4, 6, 8), listWithNull.mapNotNull { it * 2
    })
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
            val list = listOf(1, 1, 3, 4, 5, 6)
    Log.e(tag, "any1 " + list.any { it % 2 == 0 })
    Log.e(tag, "any2 " + list.any { it > 10 })
    Log.e(tag, "all1 " + list.all { it < 10 })
    Log.e(tag, "all2 " + list.all { it % 2 == 0 })
    Log.e(tag, "count " + list.count { it % 2 == 0 })
    Log.e(tag, "fold " + list.fold(4) { total, next -> total + next })
    Log.e(tag, "foldRight " + list.foldRight(4) { total, next -> total + next })
    list.forEach { println(it) }
    list.forEachIndexed { index, i -> println("position $index value $i") }
    Log.e(tag, "max " + list.max())
    Log.e(tag, "maxBy " + list.maxBy { -it })
    Log.e(tag, "min " + list.min())
    Log.e(tag, "minBy " + list.minBy { -it })
    Log.e(tag, "none " + list.none { it % 7 == 0 })
    Log.e(tag, "reduce " + list.reduce { total, next -> total + next })
    Log.e(tag, "reduce right " + list.reduceRight { total, next -> total + next })
    Log.e(tag, "sumBy " + list.sumBy { it % 2 })
    Log.e(tag, "drop " + list.drop(4))
    Log.e(tag, "dropWhile " + list.dropWhile { it > 2 })
    Log.e(tag, "dropLastWhile " + list.dropLastWhile { it > 2 })
    Log.e(tag, "filter " + list.filter { it > 2 })
    Log.e(tag, "filterNot " + list.filterNot { it > 2 })
    val listWithNull = listOf(1, 2, null)
    Log.e(tag, "filterNotNull " + listWithNull.filterNotNull())
    Log.e(tag, "slice " + list.slice(listOf(1, 2, 4)))
    Log.e(tag, "flatMap " + list.flatMap { listOf(it, it + 1) })
    Log.e(tag, "groupBy " + list.groupBy { if (it % 2 == 0) "even" else "odd" })
    Log.e(tag, "map " + list.map { it * 2 })
    Log.e(tag, "partition " + list.partition { it % 2 == 0 })
    Log.e(tag, "plus " + list + listOf(7, 8))
    Log.e(tag, "zip " + list.zip(listOf(7, 4, 9, 8)))
    Log.e(tag, "unzip " + listOf(Pair(5, 7), Pair(6, 8)).unzip())

    E/MainActivity: any1 true
    any2 false
    all1 true
    all2 false
    count 2
    fold 24
    foldRight 24
    I/System.out: 1
    1
    3
    4
    5
    6
    position 0 value 1
    position 1 value 1
    position 2 value 3
    position 3 value 4
    position 4 value 5
    position 5 value 6
    E/MainActivity: max 6
    maxBy 1
    min 1
    E/MainActivity: minBy 6
    none true
    reduce 20
    reduce right 20
    sumBy 4
    drop [5, 6]
    dropWhile [1, 1, 3, 4, 5, 6]
    dropLastWhile [1, 1]
    filter [3, 4, 5, 6]
    filterNot [1, 1]
    filterNotNull [1, 2]
    slice [1, 3, 5]
    flatMap [1, 2, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7]
    groupBy {odd=[1, 1, 3, 5], even=[4, 6]}
    map [2, 2, 6, 8, 10, 12]
    partition ([4, 6], [1, 1, 3, 5])
    plus [1, 1, 3, 4, 5, 6][7, 8]
    zip [(1, 7), (1, 4), (3, 9), (4, 8)]
    unzip ([5, 6], [7, 8])
元素操作符
生产操作符
顺序操作符

创建数据库model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* SQLite表与对象之间的互相映射
*/
class CityForecast(val map: MutableMap<String, Any?>,
val dailyForecast: List<DayForecast>) {
var _id: Long by map
var city: String by map
var country: String by map

constructor(id: Long, city: String, country: String,
dailyForecast: List<DayForecast>)
: this(HashMap(), dailyForecast) {
this._id = id
this.city = city
this.country = country
}
}

/**不同之处就是不需要设置id,因为它将
通过SQLite自增长*/
class DayForecast(val map: MutableMap<String, Any?>) {
var _id: Long by map
var date: Long by map
var description: String by map
var high: Long by map
var low: Long by map
var iconUrl: String by map
var cityId: Long by map

constructor(date: Long, description: String, high: Long,
low: Long, iconUrl: String, cityId: Long) : this(HashMap()) {
this.date = date
this.description = description
this.high = high
this.low = low
this.iconUrl = iconUrl
this.cityId = cityId
}
}

写入和查询数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ForecastDb(val forecastDbHelper: ForecastDbHelper = ForecastDbHelper.instance,
val dataMapper: DbDataMapper = DbDataMapper()) {

//dailyRequest 是查询语句中 where 的一部分。它是 whereSimple 函
//数需要的第一个参数,这与我们用一般的helper做的方式很相似
fun requestForecastByZipcode(zipCode: String, date: Date) = forecastDbHelper.use {
val dailyRequest = "${DayForecastTable.CITY_ID}=? " +
"AND ${DayForecastTable.DATE}>=?"
val dailyForecast = select(DayForecastTable.NAME)
.whereSimple(dailyRequest, zipCode, date.toString())
.parseList { DayForecast(HashMap(it)) }

// 简化写法
// val dailyRequest = "${DayForecastTable.CITY_ID} = {id}" + "AND $
// {DayForecastTable.DATE} >= {date}"
// val dailyForecast = select(DayForecastTable.NAME)
// .where(dailyRequest, "id" to zipCode, "date" to date)
// .parseList { DayForecast(HashMap(it)) }
val city = select(CityForecastTable.NAME)
.whereSimple("${CityForecastTable.ID} = ?", zipCode)
.parseOpt { CityForecast(HashMap(it), dailyForecast) }
city?.let { dataMapper.convertToDomain(it) }
}

fun saveForecast(forecastList: ForecastList) = forecastDbHelper.use {
clear(DayForecastTable.NAME)
clear(CityForecastTable.NAME)
with(dataMapper.convertFromDomain(forecastList)) {
insert(CityForecastTable.NAME, *map.toVarargArray())
dailyForecast.forEach { insert(DayForecastTable.NAME, *it.map.toVarargArray()) }
}
}
}
  • DbDataMapper使用with简化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class DbDataMapper {

    fun convertFromDomain(forecastList: ForecastList) = with(forecastList) {
    val daily = dailyForecast.map { convertDayFromDomain(id, it) }
    CityForecast(id, city, country, daily)
    }

    private fun convertDayFromDomain(cityId: Long, forecast: Forecast) = with(forecast) {
    DayForecast(date, description, high, low, iconUrl, cityId)
    }

    fun convertToDomain(forecast: CityForecast) = with(forecast) {
    val daily = dailyForecast.map { convertDayToDomain(it) }
    ForecastList(_id, city, country, daily)
    }

    fun convertDayToDomain(dayForecast: DayForecast) = with(dayForecast) {
    Forecast(_id, date, description, high, low, iconUrl)
    }
    }
  • Map,SelectQueryBuilder 扩展函数 它需要一个表名和一个 vararg 修饰的 Pair<String, Any> 作为参数。这个函数会把 vararg 转换成Android SDK需要的 ContentValues 对象。所以任务组成是把 map 转换成一个 vararg 数组,在toVarargArray 函数结果前面使用 * 表示这个array会被分解成为一个 vararg 参数,这个在Java中是自动处理的,但是我们需要在Kotlin中明确指明,通过 map 的使用,可以用很简单的方式把类转换为数据表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    fun <K, V : Any> Map<K, V?>.toVarargArray(): Array<out Pair<K, V>> =
    map({ Pair(it.key, it.value!!) }).toTypedArray()

    fun <T : Any> SelectQueryBuilder.parseList(parser: (Map<String, Any?>) -> T): List<T> =
    parseList(object : MapRowParser<T> {
    override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
    })

    fun <T : Any> SelectQueryBuilder.parseOpt(parser: (Map<String, Any?>) -> T): T? =
    parseOpt(object : MapRowParser<T> {
    override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
    })

    fun SQLiteDatabase.clear(tableName: String) {
    execSQL("delete from $tableName")
    }

Kotlin for Android (四)

发表于 2018-03-23 | 分类于 Kotlin | 阅读次数:

Application单例化和属性的Delegated

需要有一个更简单的访问application context的方式

Application单例化
1
2
3
4
5
6
7
8
9
10
class App : Application() {
companion object {
private var instance: Application? = null
fun instance() = instance!!
}
override fun onCreate() {
super.onCreate()
instance = this
}
}

Android有一个问题,就是我们不能去控制很多类的构造函数。比如,我们不能初
始化一个非null属性,因为它的值需要在构造函数中去定义。所以我们需要一个可
null的变量,和一个返回非null值的函数。我们知道我们一直都有一个 App 实例,
但是在它调用 onCreate 之前我们不能去操作任何事情,所以我们为了安全性,我
们假设 instance() 函数将会总是返回一个非null的 app 实例。但是这个方案看起来有点不自然。我们需要定义个一个属性(已经有了getter和setter),然后通过一个函数来返回那个属性。我们有其他方法去达到相似的效果么?是的,我们可以通过委托这个属性的值给另外一个类。这个就是我们知道的委托属性

委托属性
  • 属性委托的结构如下:这个T是委托属性的类型。 getValue 函数接收一个类的引用和一个属性的元数据。 setValue 函数又接收了一个被设置的值。如果这个属性是不可修改(val),就会只有一个 getValue 函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Delegate<T> : ReadWriteProperty<Any?, T> {
    fun getValue(thisRef: Any?, property: KProperty<*>): T {
    return ...
    }
    fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
    {
    ...
    }
    }
  • 示例属性委托怎么设置,by 这个关键字来指定一个委托对象

    1
    2
    3
    class Example {
    var p: String by Delegate()
    }
标准委托
  • Lazy
    它包含一个lambda,当第一次执行 getValue 的时候这个lambda会被调用,所以
    这个属性可以被延迟初始化。之后的调用都只会返回同一个值,当我们在它们第一次真正调用之前不是必须需要它们的时候。我们可以节省内存,在这些属性真正需要前不进行初始化,database并没有被真正初始化,直到第一次调用 onCreate 时。在那之后,我们才确保applicationContext存在,并且已经准备好可以被使用了。 lazy 操作符是线程安全的。如果你不担心多线程问题或者想提高更多的性能,你也可以使用lazy(LazyThreadSafeMode.NONE){ … }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class App : Application() {
    val database: SQLiteOpenHelper by lazy {
    MyDatabaseHelper(applicationContext)
    }
    override fun onCreate() {
    super.onCreate()
    val db = database.writableDatabase
    }
    }
  • Observable这个委托会帮我们监测我们希望观察的属性的变化。当被观察属性的 set 方法被调用的时候,它就会自动执行我们指定的lambda表达式。所以一旦该属性被赋了新的值,我们就会接收到被委托的属性、旧值和新值,下面的示例是一些我们需要关心的ViewMode,每次值被修改了,就会保存它们到数据库

    1
    2
    3
    4
    5
    6
    class ViewModel(val db: MyDatabase) {
    var myProperty by Delegates.observable("") {
    d, old, new ->
    db.saveChanges(this, new)
    }
    }
  • Vetoable 这是一个特殊的 observable ,它让你决定是否这个值需要被保存。它可以被用于在真正保存之前进行一些条件判断,下面的示例,允许在新的值是正数的时候执行保存。在lambda中,最后一行表示返回值。你不需要使用return关键字(实质上不能被编译)

    1
    2
    3
    4
    var positiveNumber = Delegates.vetoable(0) {
    d, old, new ->
    new >= 0
    }
  • NotNull 含有一个可null的变量并会在我们设置这个属性的时候分配一个真实的值。如果这个值在被获取之前没有被分配,它就会抛出一个异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class App : Application() {
    companion object {
    var instance: App by Delegates.notNull()
    }
    override fun onCreate() {
    super.onCreate()
    instance = this
    }
    }
  • 从Map中映射值 属性的值会从一个map中获取value,属性的名字对应这个map中的key,如果我们importkotlin.properties.getValue ,我们可以从构造函数映射到 val 属性来得到一个不可修改的map。如果我们想去修改map和属性,我们也可以import kotlin.properties.setValue 。类需要一个 MutableMap 作为构造函数的参数

  • 想象我们从一个Json中加载了一个配置类,然后分配它们的key和value到一个map中。我们可以仅仅通过传入一个map的构造函数来创建一个实例:

    1
    2
    3
    4
    5
    6
    7
    import kotlin.properties.getValue
    class Configuration(map: Map<String, Any?>) {
    val width: Int by map
    val height: Int by map
    val dp: Int by map
    val deviceName: String by map
    }
  • 创建一个map

    1
    2
    3
    4
    5
    6
    conf = Configuration(mapOf(
    "width" to 1080,
    "height" to 720,
    "dp" to 240,
    "deviceName" to "mydevice"
    ))
  • 自定义委托 创建一个类然后继承 ReadWriteProperty :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any
    ?, T> {
    override fun getValue(thisRef: Any?, property: KProperty
    <*>): T {
    throw UnsupportedOperationException()
    }
    override fun setValue(thisRef: Any?, property: KProperty
    <*>, value: T) {
    }
    }
  • 这个委托可以作用在任何非null的类型。它接收任何类型的引用,然后像getter和setter那样使用T。现在我们需要去实现这些函数

    Getter函数 如果已经被初始化,则会返回一个值,否则会抛异常
    Setter函数 如果仍然是null,则赋值,否则会抛异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any
    ?, T> {
    private var value: T? = null
    override fun getValue(thisRef: Any?, property: KProperty<*>)
    : T {
    return value ?: throw IllegalStateException("${desc.name
    } " +
    "not initialized")
    }
    override fun setValue(thisRef: Any?, property: KProperty<*>,
    value: T) {
    this.value = if (this.value == null) value
    else throw IllegalStateException("${desc.name} already i
    nitialized")
    }
    }

    使用上述委托示例

    1
    2
    3
    4
    object DelegatesExt {
    fun notNullSingleValue<T>():
    ReadWriteProperty<Any?, T> = NotNullSingleValueVar()
    }
  • 重新实现Application单例化,可以在app的任何地方去修改这个值,因为如果我们使用 Delegates.notNull() ,属性必须是var的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class App : Application() {
    companion object {
    var instance: App by Delegates.notNull()
    }
    override fun onCreate() {
    super.onCreate()
    instance = this
    }
    }

    上述可以任何地方修改问题,解决办法使用下面的委托,只能修改一次
    companion object {
    var instance: App by DelegatesExt.notNullSingleValue()
    }

Kotlin for Android (三)

发表于 2018-03-23 | 阅读次数:

操作符

  • 操作符在Adapter中的应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //    val size: Int get() = dailyForecast.size
    override fun getItemCount(): Int = items.size

    // operator fun get(position: Int) = dailyForecast[position]
    override fun onBindViewHolder(holder: VH, position: Int) {
    with(items[position]) {
    // with(items.dailyForecast[position]) {
    holder.textView.text = "$date - $description - $high - $low"
    }
    }
  • 扩展函数中的操作符,访问ViewGroup中的View

    1
    2
    3
    4
    5
    operator fun ViewGroup.get(position: Int): View = getChildAt(pos
    ition)

    val container: ViewGroup = find(R.id.container)
    val view = container[2]

使Forecast list可点击

  • item_forecast.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    <?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:padding="@dimen/spacing_xlarge"
    android:background="?attr/selectableItemBackground"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    <ImageView
    android:id="@+id/icon"
    android:layout_width="48dp"
    android:layout_height="48dp"
    tools:src="@mipmap/ic_launcher"/>
    <LinearLayout
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:layout_marginLeft="@dimen/spacing_xlarge"
    android:layout_marginRight="@dimen/spacing_xlarge"
    android:orientation="vertical">
    <TextView
    android:id="@+id/date"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAppearance="@style/TextAppearance.AppCompat.Medium"
    tools:text="May 14, 2015"/>
    <TextView
    android:id="@+id/description"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAppearance="@style/TextAppearance.AppCompat.Caption"
    tools:text="Light Rain"/>
    </LinearLayout>
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="vertical">
    <TextView
    android:id="@+id/maxTemperature"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="@style/TextAppearance.AppCompat.Medium"
    tools:text="30"/>
    <TextView
    android:id="@+id/minTemperature"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="@style/TextAppearance.AppCompat.Caption"
    tools:text="15"/>
    </LinearLayout>
    </LinearLayout>
  • 添加iconUrl参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    data class Forecast(val date: String, val description: String,
    val high: Int, val low: Int, val iconUrl: St
    ring)

    在 ForecastDataMapper 中:
    private fun convertForecastItemToDomain(forecast: Forecast): Mod
    elForecast {
    return ModelForecast(convertDate(forecast.dt),
    forecast.weather[0].description, forecast.temp.max.t
    oInt(),
    forecast.temp.min.toInt(), generateIconUrl(forecast.
    weather[0].icon))
    }
    private fun generateIconUrl(iconCode: String): String
    = "http://openweathermap.org/img/w/$iconCode.png"
  • Adapter添加点击事件,对应操作符invoke,可以有两种方式调用

    itemClick.invoke(forecast)
    itemClick(forecast)

    1
    2
    3
    4
    public interface OnItemClickListener {
    //对应的操作符为 itemClick(forecast)
    operator fun invoke(forecast: Forecast)
    }
  • 新的Adapter,重新设置布局,绑定数据,设置监听

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    class ForecastListAdapter(val items: ForecastList, val itemClick: OnItemClickListener) : RecyclerView.Adapter<ForecastListAdapter.VH>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.item_forecast, parent, false)
    return VH(view, itemClick)
    }

    // val size: Int get() = dailyForecast.size
    override fun getItemCount(): Int = items.size

    override fun onBindViewHolder(holder: VH, position: Int) {
    bindForecast(position, holder)
    }

    // operator fun get(position: Int) = dailyForecast[position]
    private fun bindForecast(position: Int, holder: VH) {
    with(items[position]) {
    // with(items.dailyForecast[position]) {
    // holder.itemView.date.text = "$date - $description - $high - $low"
    holder.bindForecast(this)
    }
    }

    class VH(itemView: View, val itemClick: OnItemClickListener) : RecyclerView.ViewHolder(itemView) {
    private val iconView: ImageView
    private val dateView: TextView
    private val descriptionView: TextView
    private val maxTemperatureView: TextView
    private val minTemperatureView: TextView

    init {
    iconView = itemView.findViewById(R.id.icon)
    dateView = itemView.findViewById(R.id.date)
    descriptionView = itemView.findViewById(R.id.description)
    maxTemperatureView = itemView.findViewById(R.id.maxTemperature)
    minTemperatureView = itemView.findViewById(R.id.minTemperature)
    }

    fun bindForecast(forecast: Forecast) {
    with(forecast) {
    Picasso.with(itemView.context).load(iconUrl).into(iconView)
    dateView.text = date
    descriptionView.text = description
    maxTemperatureView.text = high.toString()
    minTemperatureView.text = low.toString()
    itemView.setOnClickListener { itemClick(this) }
    }
    }
    }

    public interface OnItemClickListener {
    //对应的操作符为 itemClick(forecast)
    operator fun invoke(forecast: Forecast)
    }
    }
  • 设置Adapter,创建一个匿名内部类,我们去创建了一个实现了刚刚创建的接口的对象。需要使用另一个强大的函数式编程的特性,把这些代码转换得更简单

    1
    2
    3
    4
    5
    6
    7
    uiThread {
    recyclerView.adapter = ForecastListAdapter(result, object : ForecastListAdapter.OnItemClickListener {
    override fun invoke(forecast: Forecast) {
    toast(forecast.date)
    }
    })
    }
  • ViewExtensions.kt

    通过 ctx 这个属性来返回context,但是在View中缺少这个属
    性。所以我们要创建一个新的名叫 ViewExtensions.kt 文件来代
    替 ui.utils ,然后增加这个扩展属性
    val View.ctx: Context
    get() = context
    从现在开始,任何View都可以使用这个属性了。这个不是必须的,因为你可以使用
    扩展的context属性,但是我觉得如果我们使用 ctx 的话在其它类中也会更有连贯
    性。而且,这是一个很好的怎么去使用扩展属性的例子

    Lambdas

  • 一个lambda表达式通过参数的形式被定义在箭头的左边(被圆括号包围),然后在箭头的右边返回结果值,接收一个View,然后返回一个Unit

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    fun setOnClickListener(listener: (View) -> Unit)

    view.setOnClickListener({ view -> toast("Click")})

    如果参数没有使用,可以省略参数
    view.setOnClickListener({ toast("Click") })

    如果函数的最后一个参数是函数,可以把这个函数参数移到括号外
    view.setOnClickListener() { toast("Click") }

    这个函数只有一个参数,省略圆括号
    view.setOnClickListener { toast("Click") }

    所以MainActivity中设置ForecaAdapter

    1
    2
    3
    4
    recyclerView.adapter = ForecastListAdapter(result) { forecast -> toast(forecast.date) }

    如果这个函数只有一个参数,可以使用it引用,不用去指定左边的参数
    val adapter = ForecastListAdapter(result) { toast(it.date) }

扩展语言

  • with函数

    1
    2
    3
    4
    5
    6
    7
    @kotlin.internal.InlineOnly
    public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
    callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
    }

    第二个参数是一个函数,可以把他放在圆括号外面,所以可以创建一个代码块

    1
    2
    3
    4
    5
    6
    7
    8
     with(forecast) {
    Picasso.with(itemView.ctx).load(iconUrl).into(iconView)
    dateView.text = date
    descriptionView.text = description
    maxTemperatureView.text = "$high"
    minTemperatureView.text = "$low"
    itemView.setOnClickListener { itemClick(this) }
    }

    内联函数
    内联函数与普通的函数有点不同。一个内联函数会在编译的时候被替换
    掉,而不是真正的方法调用。这在一些情况下可以减少内存分配和运行时
    开销。举个例子,如果我们有一个函数,只接收一个函数作为它的参数。
    如果是一个普通的函数,内部会创建一个含有那个函数的对象。另一方
    面,内联函数会把我们调用这个函数的地方替换掉,所以它不需要为此生
    成一个内部的对象

  • 判断版本执行代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    inline fun supportsLollipop(code: () -> Unit) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    code()
    }
    }

    它只是检查版本,然后如果满足条件则去执行。现在我们可以这么做:
    supportsLollipop {
    window.setStatusBarColor(Color.BLACK)
    }
  • 可见性修饰符

    protected
    这个修饰符只能被用在类或者接口中的成员上。一个包成员不能被定义为 protected ,定义在一个成员中,就与Java中的方式一样了:它可以被成员自己和继承它的成员可见(比如,类和它的子类)

    internal
    如果是一个定义为 internal 的包成员的话,对所在的整个 module 可见。如果
    它是一个其它领域的成员,它就需要依赖那个领域的可见性了。比如,如果我们写
    了一个 private 类,那么它的 internal 修饰的函数的可见性就会限制与它所在
    的这个类的可见性。我们可以访问同一个 module 中的 internal 修饰的类,但是不能访问其它 module 的

  • 构造器

    所有构造函数默认都是 public 的,它们类是可见的,可以被其它地方使用。我们
    也可以使用这个语法来把构造函数修改为 private :
    class C private constructor(a: Int) { ... }

  • 布局中的\<include>增加内嵌布局,需要手动import

    1
    2
    import kotlinx.android.synthetic.activity_main.*
    import kotlinx.android.synthetic.content_main.*
  • 绑定一个xml中的view到另一个view。唯一不同的就是需要 import :
    import kotlinx.android.synthetic.view_item.view.*

  • 在Adapter中可以简化代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import kotlinx.android.synthetic.main.item_forecast.view.*

    class VH(itemView: View, val itemClick: (Forecast) -> Unit) : RecyclerView.ViewHolder(itemView) {
    fun bindForecast(forecast: Forecast) {
    with(forecast) {
    Picasso.with(itemView.context).load(iconUrl).into(itemView.icon)
    itemView.date.text = date
    itemView.description.text = description
    itemView.maxTemperature.text = high.toString()
    itemView.minTemperature.text = low.toString()
    itemView.setOnClickListener { itemClick(this) }
    }
    }
    }

Kotlin for Android (二)

发表于 2018-03-23 | 分类于 Kotlin | 阅读次数:

Anko

  • Anko Commons:一个轻量级的库,里面包含了intents,对话框,日志等帮助类
  • Anko Layouts:用于编写动态Android布局的快速且类型安全的方法
  • Anko SQLite:查询适用于Android SQLite的DSL和分析器集合
  • Anko Coroutines:基于kotlinx.coroutines库的实用程序
  • 简化获取RecyclerView
    1
    val forecastList: RecyclerView = find(R.id.recyclerView)

扩展函数

扩展函数数是指在一个类上增加一种新的行为,甚至我们没有这个类代码的访问权
限。这是一个在缺少有用函数的类上扩展的方法。在Java中,通常会实现很多带有
static方法的工具类。Kotlin中扩展函数的一个优势是我们不需要在调用方法的时候
把整个对象当作参数传入。扩展函数表现得就像是属于这个类的一样,而且我们可
以使用 this 关键字和调用所有public方法

1
2
3
fun Context.toastF(message: CharSequence,duration: Int=Toast.LENGTH_SHORT){
Toast.makeText(this, "$message", duration).show()
}

  • Anko已经扩展toast函数,提供了CharSequence,(resource id)Int的函数

    1
    2
    toast(R.string.app_name)
    longToast("longToast")
  • 扩展函数并不是真正地修改了原来的类,它是以静态导入的方式来实现的。扩展函数可以被声明在任何文件中,通用的实践是把一系列有关的函数放在一个新建的文件里

    1
    2
    3
    var TextView.text: CharSequence
    get() = getText()
    set(v) = setText(v)
阅读全文 »

Kotlin for Android (一)

发表于 2018-03-22 | 分类于 Kotlin | 阅读次数:

介绍Kotlin

  • 编写代码量少
  • 更加安全:Kotlin编译时期就处理了各种null的情况,避免了执行时异常。如果一个对象可以是null,则我们需要明确地指定它,然后在使用它之前检查它是否是null
  • 它是函数式的:Kotlin是基于面向对象的语言,它使用了很多函数式编程的概念,比如,使用lambda表达式来更方便地解决问题。其中一个很棒的特性就是Collections的处理方式
  • 它可以扩展函数:可以扩展类的更多的特性,甚至我们没有权限去访问这个类中的代码
  • 它是高度互操作性的:你可以继续使用所有的你用Java写的代码和库,因为两个语言之间的互操作性是完美的。可以在一个项目中使用Kotlin和Java两种语言混合编程

特性

Expresiveness 可读性
  • POJO

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Artist {
    private long id;
    private String name;
    private String url;
    private String mbid;
    public long getId() {
    return id;
    }
    public void setId(long id) {
    this.id = id;
    }
    ···
  • Kotlin中创建数据类Artist.kt,自动生成所有属性和访问器

    1
    data class Artist(var id: Long, var name: String, var url: String, var mbid: String)
空安全
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 //编译不通过,非空类不能为null
var artist:Artist=null

//安全调用操作符? 明确地指定一个对象是否能为空
var artist: Artist? = null

// 无法编译, artist可能是null,需要进行处理
// artist.hashCode()

//在artist!=null时调用
artist?.hashCode()

// 判空
if(artist!=null){
artist.hashCode()
}

//给定在null时的替代者
val name=artist?.name?:"empty"

//确保artist不是null的情况下调用,否在抛异常KotlinNullPointerException
artist!!.hashCode()
阅读全文 »

RxJava2

发表于 2018-03-20 | 分类于 Android | 阅读次数:
RxJava2

RxJava是Java VM响应式编程扩展的实现,扩展了观察者模式,通过操作符对数据事件流操作,来编写异步和基于事件的程序,从而不用关心同步,线程安全并发等问题

  • app/build.gradle
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'

    //retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    //Gson converter
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
    //RxJava2 Adapter
    implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    //okhttp
    implementation 'com.squareup.okhttp3:okhttp:3.8.1'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
阅读全文 »

Data Binding

发表于 2018-03-16 | 分类于 Android | 阅读次数:
Data Binding Library

数据绑定库编写声明式布局,尽量减少绑定应用程序逻辑和布局所需的代码,减少布局绑定相关代码

  • build environment

    1
    2
    3
    4
    5
    6
    android{
    ...
    dataBinding {
    enabled = true
    }
    }
  • Data Binding Compiler V2

    1
    2
    android.databinding.enableV2=true
    向后不兼容
阅读全文 »

Dagger

发表于 2018-03-13 | 分类于 Android | 阅读次数:
Dagger

依赖注入(Dependency Injection),简称DI,又叫控制反转(Inversion of Control),简称IOC
当一个类的实例需要另一个类的实例,在传统的设计中,通常由调用者来创建被调用者的实例,然而依赖注入的方式,创建被调用者不再由调用者创建实例,创建被调用者的实例的工作由IOC容器来完成,然后注入到调用者。因此也被称为依赖注入

  • API
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public @interface Component {
    Class<?>[] modules() default {};
    Class<?>[] dependencies() default {};
    @Target(TYPE)
    @Documented
    @interface Builder {}
    }

    public @interface Subcomponent {
    Class<?>[] modules() default {};
    @Target(TYPE)
    @Documented
    @interface Builder {}
    }

    public @interface Module {
    Class<?>[] includes() default {};
    @Beta
    Class<?>[] subcomponents() default {};
    }

    public @interface Provides {
    }

    public @interface MapKey {
    boolean unwrapValue() default true;
    }

    public interface Lazy<T> {
    T get();
    }
阅读全文 »

ConstraintLayout

发表于 2018-03-10 | 分类于 Android | 阅读次数:
  • 引入constraint-layout

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    allprojects {
    repositories {
    jcenter()
    maven {
    url 'https://maven.google.com'
    }
    }
    }

    compile 'com.android.support.constraint:constraint-layout:1.1.0-beta5'
  • 属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    layout_constraintLeft_toLeftOf
    左对齐
    layout_constraintLeft_toRightOf
    左边和约束控件的右边对齐
    layout_constraintRight_toLeftOf
    右边在某组件的左边
    layout_constraintRight_toRightOf
    右边在某组件的右边
    layout_constraintTop_toTopOf
    上边和某组件的上边对其
    layout_constraintTop_toBottomOf
    上边在某组件的下边
    layout_constraintBottom_toTopOf
    下边在某组件的上边
    layout_constraintBottom_toBottomOf
    下边在某组件的下边
    layout_constraintBaseline_toBaselineOf
    组件的基线位置和某组件的基线位置对其(很少用)
    layout_constraintStart_toEndOf
    layout_constraintStart_toStartOf
    layout_constraintEnd_toStartOf
    layout_constraintEnd_toEndOf
    属性的值有两种,一种是同层级组件ID,还有就是parent,当值为parent时即是相对于父布局进行定位
阅读全文 »

CoordinatorLayout

发表于 2018-03-09 | 分类于 Android | 阅读次数:

CoordinatorLayout

Material风格布局,包含在support Library中,结合AppbarLayout,CollapsingToolbarLayout等可达到MD设计风格布局

  • build.gradle
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def SUPPORT = "26.1.0"
    dependencies {
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation "com.android.support:design:$SUPPORT"
    implementation "com.android.support:cardview-v7:$SUPPORT"
    implementation "com.android.support:recyclerview-v7:$SUPPORT"
    implementation 'de.hdodenhof:circleimageview:1.3.0'
    }
阅读全文 »

Git

发表于 2018-03-09 | 分类于 Git | 阅读次数:

入门

  • .git 的隐藏目录是你的本地仓库(Local Repository)
  • git log 查看历史
  • git log -p查看详细历史
  • git log --stat 查看简要统计
  • git show e66666/branch查看指定commit,加文件名看指定文件
  • git diff --staged/--cached查看当前工作目录与暂存区的不同,可以看到即将添加到暂存区的改动内容,git diff HEAD看到当前工作目录与上一个commit的不同
    • commit 的 SHA-1 校验和
      commit 8cf88cf35ce40cb91488e7d9b12cf46463fedc2f
  • git status
    阅读全文 »
12
willkernel

willkernel

Lifelong learning

35 日志
7 分类
10 标签
RSS
GitHub
Creative Commons
Links
  • wanandroid
  • Drakeet
  • Weishu
  • Gityuan
  • Daimajia
  • Kymjs
  • droidyue
  • 尼古拉斯.赵四
  • 程序员非猿
  • Piasy
  • Jake Wharton
  • codeKK
© 2018 willkernel
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4