AndroidPi

Kotlin类属性与常量

参考: 对象表达式与声明 类属性 类属性是定义在类中的变量或不变量,分别使用var和val定义。这里把Kotlin中的不变量和常量作下区分,不变量只是在第一次初始化后便不可改变,其余特性和变量完全一样,但它不是常量,常量的说明在后面的小节给出。 Kotlin对类属性的完整声明是: (var|val) <propertyName>[: <PropertyType>] [= <property_initializer>] [<getter>] [<setter>] 方括号[]表示该部分是可选的,其中属性初始化器、getter、setter在任何情况下都是可选的,如果属性类型可以从属性初始化器或getter中推断出,那么它也是可选的。使用val所声明的不变量是没有setter的。 类属性可以不显式地初始化,但未显式初始化的属性必须在构造器中初始化: var allByDefault: Int? // error: explicit initializer required, default getter and setter implied var initialized = 1 // has type Int, default getter and setter val simple: Int? // has type Int, default getter, must be initialized in constructor val inferredType = 1 // has type Int and a default getter 属性访问器 所谓访问,就是对属性的读与写,类会为每个属性提供默认的访问器。称读访问器为getter,写访问器为setter。对于属性变量两者都会提供,不变量只有getter,显然是由于它不可改变。访问器可以在声明属性时进行重写以替换默认访问器: val isEmpty: Boolean private get() = this....

October 30, 2017 · 2 min · 355 words · LEOY

Kotlin类与继承

类定义 类声明由类名、类首部(指定类型参数,主构造器等)、类正文(用花括号包围)。类首部和正文都是可选的。因此最简单的类定义为: class Empty // Implicitly inherits from Any 类似所有Java类都继承类Object,所有Kotlin类都继承类Any,但Any不是与特定平台(例如JVM)相关的,它不是一个java.lang.Object,这表明当Kotlin与Java代码进行互操作时,为了使用Object的成员方法,Kotlin提供了扩展函数。 例如为了调用Object的wait()/notify()方法,可以将一个kotlin对象foo转型为Object: (foo as java.lang.Object).wait() 为了获取一个kotlin对象的Java类,Kotlin1.1之后可以使用class引用的java扩展属性,也可以直接使用扩展属性javaClass: val fooClass = foo::class.java // class reference val fooClass = foo.javaClass // extension property: javaClass 与Java代码进行互操作,Kotlin在语言层面上是这样设计的,但在编译为Java字节码以及JVM运行时中,每个类总是默认继承了Object。 构造器 Kotlin的构造器分为主构造器(primary constructor)和次要构造器(secondary constructors)。两者都是可选的,即可以不用明确定义任何构造器,这时如果该类不是抽象类,就采用默认生成的无参主构造器。主构造器只能有一个,次要构造器可以有多个,次要构造器必须代理到主构造器,可以是直接的代理或使用其它次要构造器间接代理。造器的可见性默认是public的。 这里讨论下主构造器,先了解下Java对象的初始化,大致说了Java对象初始化机制有三种:实例初始化器(初始化块)、实例变量初始化器、构造器。初始化块和实例变量初始化器可以认为是实例初始化器的两种不同形式,实例变量初始化器只不过定义了实例变量并使用表达式而非块进行了初始化。在对象初始化时,由于存在继承层次结构,每一层级类都需要进行初始化,一个对象从自身开始向上到父类,到父类的父类等等,直到Object类,完成整个类层次的初始化。所以该对象的构造器必须直接或间接地调用父类构造器,这也说明了为什么构造器不属于Java类成员,它不会被子类继承,不然会造成初始化的死循环。构造器是初始化的入口,但不是初始化的全部,并且为了保证初始化过程的顺序,编译器会为每个构造器生成一个<init>方法,包含以下代码: 对另一个构造函数的调用(为了直接或间接调用父类构造器) 初始化器(包括变量初始器器和块初始化器,为了保证初始化器仅运行一次,如果第1步调用是本地另一个构造函数,那么便不需要 初始化器的代码) 构造函数正文 在Kotlin中,初始化块使用init关键字声明: init { print("This is a intializer block.") } 如果一个Kotlin类未继承其它另外定义的类,那么它默认继承类Any, 如下所示: 未定义主构造器 未定义次要构造器:编译器会为其生成一个无参主构造器 定义了次要构造器:那么它将仅有次要构造器,不会自动生成无参构造器 class Car { // ... constructor(type: String, name: String) { // ... } } 定义了主构造器,并且定义了次要构造器 class Car(type: String, name: String) { // ....

October 29, 2017 · 3 min · 434 words · LEOY

Kotlin函数

参考: 函数 函数(Function) Kotlin使用名称函数(function)而非方法(method),我想是为了与Java方法作出区分,和C语言类似函数可以单独定 义和使用,不依赖于类与对象,而Java中的方法只存在于某个类中。由于Kotlin与Java是兼容的,因此在类中定义的函数仍然 适用于方法这个语义。 Kotlin使用fun来声明函数: fun double(x: Int): Int { return 2*x } 参数 函数参数使用Pascal的表示来定义,即name: type。 默认参数值 函数参数可以有默认值,这样在调用函数时如果没有传入某个参数,那么便使用改参数对应的默认值,这样可以从某种程度上减少 方法重载(overload)。在参数类型后,使用=后跟上一个值来定义默认值。 fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { ... } 对于类中方法的重写(override),子类方法总是使用与父类方法相同的默认值,并且子类方法签名中不能有默认值。 open class A { open fun foo(i: Int = 10) { ... } } class B : A() { override fun foo(i: Int) { ... } // no default value allowed } 命名参数值 看到这里,发现这和Python语法太TM像了。默认参数传递是根据位置来判断的,称为位置参数,由于有了默认参数,可能调用时 传入的参数比签名中的少,例如调用时,一个参数值前面有一个忽略了的默认参数,该参数值的位置被提前了,这时根据位置就无 法判断参数值与参数的对应关系,此时需要将该非默认参数值使用参数名明确标出。...

October 29, 2017 · 2 min · 231 words · LEOY

从MVC到MVVM

MVP与MVVM,该选哪一个? 目前为止,我在工作中用过的应用架构有基于Otto的总线模式和MVP模式,业余时间使用Clean架构和MVVM做过一些开发。对于这些模式有一些很好的开源项目可以参考: Google应用架构蓝图 Clean结构示例 对于架构的搭建,除了采用第三方库以外,还可以使用Google官方的应用架构组件: Android应用架构组件指南 架构组件示例 架构这么多,应该怎么选择?建议根据项目实际情况作出选择,如果只是一个小的玩具项目,可以不用任何架构,大的项目也不应该陷入了过度设计的误区。 概念 为什么需要这些架构以及怎么选择?首先要明确这些架构中的几个概念。从MVC被提出以来,发展到如今的MVP,MVVM等等,所有这些架构中有两个层都没有变化,那就是模型(Model)和视图(View)。 业务 业务是应用所提供的服务。可以是后端远程提供的服务以本地化的方式展现,也可以是单独的本地服务。 视图 视图是业务的用户界面(user interface)。它为用户提供操作和展示业务的接口,用户不一定是人,也可以是消息,例如一个外部的推送服务,它通过消息接口与业务交互,所以界面也不一定是肉眼可见的界面。 模型 模型是对业务在软件工程中的抽象与建模。它提供了开展所有业务需要数据实体和状态,以及管理这些数据和状态的接口。具体实现中一般是简单的数据访问与状态改变。 领域 一个应用领域指一个独立完整的应用。不同的应用属于不同的领域。它包含了对模型和以及与模型交互的抽象(这里与模型交互的抽象不是指对视图的抽象),一般不涉及具体的实现和具体的用户界面。 领域架构 这一架构主要是根据Clean架构的思想得来的,如果将Interface替换为UserCase那基本上就是Clean架构了。那么有何改进和区别,在上面链接的示例以及实际使用Clean架构中发现,如果使Domain完全成为一个Java库,然后在各个层之间定义对应的实体进行转换会增加大量的工作和复杂度。虽然说这样是为了达到完全的解耦,但很明显,这个Java库一般并不会用到非Android平台中,这样做的意义不大。还有一点是UserCase的使用过于冗余了,并且现在有了RxJava和LiveData这样的基于观察者模式的响应式编程库,在模型和视图的交互中,不用再编写大量的回调接口。 其中Domain层中的实体和Data层中的实体是一致的。并且不使用UserCase来进行交互,而是直接调用接口,然后从返回的被观察者更新视图就可以了。 这种基于Domain的模式是十分灵活和易于扩展的,即可以直接从View调用Domain中的接口,也可以在这两者间添加Presenter/Controller/ViewModel层。它仍具有Clean架构中分层的和解耦方面的优点,易于编写测试。可以说这种方式即适用于小型项目,也适用于中大型项目。 实现 Domain层是无关具体实现的,可以专门定义面向用户的接口,然后在Data层中实现。Data层中有实体对象访问的Dao和访问服务器的API等接口,并且可以采用仓储模式来隐藏访问的细节,这样Data中会多一层Repository接口。如果这样实现,视图和模型间会有用户接口和仓储接口这两层接口,显得有点冗余了。 这里有一个接口粒度的问题,可以认为是一个接口方法调用所需要完成工作的量,如果Domain中的接口粒度和Data仓储接口粒度一致,那么可以将两者合并。实际中发现,除非能够在应用开发前就确定两种接口的粒度大小,不然将它们两者分别实现没有太大的意义,但这样做不太现实,因为如果业务频繁地变更,Domain的接口也随之改变,无法在开发之初就完成这项工作。因此一开始将两种接口合并就好了,并且我们不希望业务变化时Data的接口也频繁地变化,不然两层之间的解耦就没有意义了,那么可以从Data层中去掉Repository接口,或者将其提升为Domain层的用户接口。

July 8, 2017 · 1 min · 26 words · LEOY

Dagger2 Api

Component注解 Component注解用于注解一个接口或抽象类,以便从其Module集合中生成一个完全成型的依赖注入实现。 每个使用@Component注解的类型必须包含至少一个抽象组件方法(Component methods),组件方法可以拥有任何名称,但其签名必须遵循供给和成员注入协约。 供给方法(Provision methods) 供给方法没有参数,并返回一个被注入或者被供给的类型,每个方法也可以有一个限定符注解,以下都是有效的供给方法: SomeType getSomeType(); Set<SomeType> getSomeTypes(); @PortNumber int getPortNumber(); 供给方法和注入点一样可以使用Provider或Lazy更加显示地控制供给请求,Provider允许组件用户通过调用Provider.get()来请求任意次的供给,Lazy自始至终只会请求单个供给,但会将其推迟到对Lazy.get()的首次调用。以下供给方法都请求对同一类型的供给,但分别应用了不同的机制: SomeType getSomeType(); Provider<SomeType> getSomeTypeProvider(); Lazy<SomeType> getLazySomeType(); 成员注入方法(Members-injection methods) 成员注入方法具有单个参数,并将依赖注入到传入实例的每个使用Inject注解的域和方法,一个成员注入方法可以是void的,也可以返回其单个参数以便链接。以下都是合法的成员注入方法声明: void injectSomeType(SomeType someType); SomeType injectAndReturnSomeType(SomeType someType) 一个没有参数但返回MembersInjector的方法和成员注入方法是等效的,在返回对象上调用MembersInjector.injectMembers(T)方法和成员注入方法的作用一样: MembersInjector<SomeType> getSomeTypeMembersInjector(); 关于协方差的注意事项 尽管一个类型的成员注入方法会可以接收其子类的实例(参数是返回类型的子类),只有参数类型和其超类的Inject注解成员会被注入,而参数类型的子类则不会,例如,如下类型中,当将Child实例作为参数传入成员注入方法injectSelf(Self instance)时只有a和b会被注入, class Parent { @Inject A a; } class Self extends Parent { @Inject B b; } class Child extends Self { @Inject C c; } 实例化 组件(Component)的实例化主要通过生成的builder。一个builder实例通过组件上的builder()方法获取,返回的builder有一个方法用于设置每个modules() 和组件dependencies(),它们都以每个module或dependency类型的小写驼峰形式命名。每个缺少默认构造函数的组件dependency和module都必须显式地设置,但任何具有默认或无参构造函数(可以被组件实现访问)的module则可以忽略。如下所示: public static void main(String[] args) { OtherComponent otherComponent = ....

July 12, 2016 · 1 min · 158 words · LEOY