kotlin-android-extensions 插件
官网介绍
一、简介
Kotlin Android扩展插件 可以节省 findviewbyid()
,实现 和 Data-Binding
,Dagger
框架的效果,不需要添加任何额外代码,也不影响任何运行时体验。
Kotlin Android扩展 是 Kotlin 插件的组成之一,不需要在单独安装插件。
如下实例:
// Using R.layout.activity_main from the 'main' source set
import kotlinx.android.synthetic.main.activity_main.*
class MyActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Instead of findViewById<TextView>(R.id.textView)
textView.setText("Hello, world!")
}
}
textView
是Activity的扩展属性,而且它和 在 activity_main
中声明的 有同样的类型(因此它是一个 TextView
)。
二、使用 Kotlin Android Extensions
2.1、Gradle 配置
添加
apply plugin: 'kotlin-android-extensions'
2.2、导入合成属性
**在 Activity中: ** 按照 import kotlinx.android.synthetic.main.<布局>.*
格式,可以导入布局文件中所有控件属性。
**在 View 中(Adapter , Fragment等) 中: ** 按照 import kotlinx.android.synthetic.main.<布局>.view.*
格式,可以导入布局文件中所有控件属性。
例子:
<TextView
android:id="@+id/hello"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
activity.hello.text = "Hello World!"
2.3、实验模式(Experimental Mode)
Android 扩展插件包含一些实验性功能。比如,LayoutContainer
支持 和 Parcelable
实现生成器。 这些功能目前还是实验性的,所以需要在 build.gradle
中打开它以能够去使用这些功能:
android {
...
androidExtensions {
experimental = true
}
...
}
2.4、LayoutContainer Support
安卓扩展插件支持多种s,最基本的一些有 Activity
,Fragment
和 View
,但是可以通过 实现 LayoutController
接口 将任何类 (实际上)转为Android 扩展容器:
import kotlinx.android.extensions.LayoutContainer
class ViewHolder(override valView: View) : ViewHolder(containerView), LayoutContainer {
fun setup(title: String) {
itemTitle.text = "Hello World!"
}
}
需要配置 experimental mode
2.4.1、Kotlin之Fragment中直接引用视图控件id,报空指针的问题
解决方案一 :
要想直接使用控件 id需要符合前置条件,就是对应的 layout 文件加载完毕后才可以直接使用控件id来操作,如果你在
onCreateView()
方法中去直接使用控件id去操作,肯定是空指针异常,因为return view
还没有执行呢 。在确保
onCreateView()
方法执行完毕后,就可以直接使用控件id来操作。
解决方案二 :
在fragment使用这个extensions的时候,会找不到那个控件,解决办法
onCreateView()
tv_show = view?.findViewById(R.id.tv_show) as TextView
importkotlinx.android.synthetic.main.gv_home_fragment_item.*
onViewCreated()
tv_show.text="123456789"
2.5、Flavor Support
安卓扩展插件支持 Android flavors
。
2.5.1、什么是 flavor
Flavaor 的作用: 将 debug 和 release 的 维度 扩大。
flavorDimensions: 是基于 flavor 的一个扩展,作用是再次扩大维度,它是一个属性,学会用它必须要先会用 flavor。
2.5.2、维度
在理解了flavor的前提下(Flavor基本使用),我们需要明白一个概念—————维度!
维度在 gradle 中的体现是 : flavorDimensions
。
源码:
public void flavorDimensions(String... dimensions) {
this.checkWritability();
this.flavorDimensionList = Arrays.asList(dimensions);
}
譬如:
flavorDimensions(“money”, “channel”)
这是一个正确的维度扩展思路:
- money 场景,一个APP,我们推出收费和免费的版本。
- channel 场景,不同的市场渠道。
根据这个思路来改代码:
原有:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
productFlavors {
baidu {
manifestPlaceholders = [CHANNEL: "baidu"]
}
xiaomi {
manifestPlaceholders = [CHANNEL: "xiaomi"]
}
}
目前有四个渠道:
- baiduDebug
- baiduRelease
- xiaomiDebug
- xiaomiRelease
添加flavorDimensions后:
android{
...
flavorDimensions("money", "channel")
productFlavors {
vip {
dimension "money"
}
free{
dimension "money"
}
baidu {
manifestPlaceholders = [CHANNEL: "baidu"]
dimension "channel"
}
xiaomi {
manifestPlaceholders = [CHANNEL: "xiaomi"]
dimension "channel"
}
}
...
}
现在就会有8个渠道了:
- vipBaiduDebug
- vipBaiduRelease
- vipXiaomiDebug
- vipXiaomiRelease
- freeBaiduDebug
- freeBaiduRelease
- freeXiaomiDebug
- freeXiaomiRelease
逻辑上就是:
由:
debug/releas(2) * 渠道(2) = 4个
变成了:
debug/releas(2) * 渠道(2) * 新增维度vip/free(2) = 4 * 2 = 8个
然后我们再从代码理解上来:
- 只要你定义了flavorDimensions,那么你后面的每个flavor就必须要写”dimension”这个属性。
- baidu和xiaomi是属于渠道这个维度,所以它的dimension是channel。
- vip和free这俩个flavor是属于money这个维度(收费与免费),所以它的dimension是money。
2.5.3、添加 flavor
android {
productFlavors {
free {
versionName "1.0-free"
}
}
}
所以可以将free/res/layout/activity_free.xml
的布局通过导入添加:
import kotlinx.android.synthetic.free.activity_free.*
在 experimental mode中,您可以指定任何 variant 名称(不仅仅是 flavor),例如 freeDebug
或 freeRelease
也起作用。(这个不是很懂)
2.6、视图缓存 (View Caching)
使用 findViewById()
可能会变得很慢,尤其是很复杂的视图层中。所以,Android 扩展试图通过 缓存试图来减少 findViewById()
。
默认情况下,Android 扩展 在每个 container written in Kotlin
(Activity, Fragment, View or a LayoutContainer implementation
) 添加了一个隐藏的缓存函数 和一个存储 field。
这个方法非常小,所以不会怎么增加 APK 的大小。
下面的例子中,findViewById()
只被调用了一次:
class MyActivity : Activity()
fun MyActivity.a() {
textView.text = "Hidden view"
textView.visibility = View.INVISIBLE
}
但是,在下面的例子中:
fun Activity.b() {
textView.text = "Hidden view"
textView.visibility = View.INVISIBLE
}
我们不知道 这个函数是否仅仅在 我们源代码的 activities 或者 简单的Java activities 中调用。因此,即使 MyActivity
的实例作为接收者传递,我们不会在这里使用缓存。
2.7、更改视图缓存策略
您可以更改全局或每个容器的缓存策略。这也需要打开 experimental mode。
2.7.1 设置项目全局缓存策略
项目全局缓存策略 在 build.gradle
文件中配置:
android{
androidExtensions {
experimental = true
defaultCacheImplementation = "HASH_MAP" // also SPARSE_ARRAY, NONE
}
}
默认情况下,Android扩展插件 使用 HashMap
作为后备存储,但您可以切换到 SparseArray
实现或者关闭缓存。当您仅使用Android扩展的 Parcelable
部分时,后者特别有用。
2.7.2、设置 缓存策略
用一个容器注解@ContainerOptions
来改变它的缓存策略:
import kotlinx.android.extensions.ContainerOptions
@ContainerOptions(cache = CacheImplementation.NO_CACHE)
class MyActivity : Activity()
fun MyActivity.a() {
// findViewById() will be called twice
textView.text = "Hidden view"
textView.visibility = View.INVISIBLE
}
2.8、Parcelable (序列化)
从Kotlin 1.1.4
开始,Android扩展插件 提供了 Parcelable
实现生成器作为实验性功能。
Android中的序列化:
在开发中,如果有需要用到序列化和反序列化的操作,就会用到 Serializable
或者 Parcelable
,它们各有优缺点,会适用于不同的场景。
Serializable
Serializable 的优点是实现简单,你只需要实现一个 Serializable 接口,并不需要任何额外的代码,但是它的序列化和反序列化,实际上是使用反射做的,所以效率会略低,并且它会在序列化的过程中,会创建很多临时变量,所以更容易触发 GC。
Parcelable
Parcelable 需要开发者自己去实现序列化的规则,所以会增加代码量,正是因为规则确定,所以效率会提高很多,并且不容易触发 GC。
2.8.1 Kotlin中,启用 Parcelable
支持 :
打开 experimental mode
2.8.2、Kotlin中如何使用
使用 @Parcelize
注解类,然后实现Parcelable
接口(方法实现自动生成)。
import kotlinx.android.parcel.Parcelize
@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable
@Parcelize
要求在主构造函数中声明所有序列化的属性。Android
扩展将 对声明在类体中带有 幕后字段的属性发出警告:
@Parcelize
class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
// [PLUGIN WARNING]:Property would not be serialized into a `Parcel`.
// Add `@IgnoreOnParcer` annotation to remove warning
var sex: Int = 0
}
另外, 如果某些主构造函数参数不是属性,则@Parcelize
不能应用:
class User(val firstName: String, lastName: String, val age: Int) // 可以
// 报错。[PLUGIN ERROR]:`Parcelabel constructor parameter should be val or var
@Parcelize
class User(val firstName: String, lastName: String, val age: Int) : Parcelable
如果类需要更高级的序列化逻辑,你可以把它写在一个伴随类中:
@Parcelize
data class Value(val firstName: String, val lastName: String, val age: Int) : Parcelable {
private companion object : Parceler<User> {
override fun User.write(parcel: Parcel, flags: Int) {
// Custom write implementation
}
override fun create(parcel: Parcel): User {
// Custom read implementation
}
}
}
2.8.3、@Parcelize 支持的类型
原始类型(及其盒装版本); 对象和枚举;
- String,CharSequence;
- Exception;
- Size,SizeF,Bundle,IBinder,IInterface,FileDescriptor;
- SparseArray,SparseIntArray,SparseLongArray,SparseBooleanArray,
- 所有Serializable(是的,Date也支持)和Parcelable实现;
- 所有受支持类型的集合:List( 映射到ArrayList ),Set(映射到LinkedHashSet),Map(映射到LinkedHashMap);
- 也有一些具体的实现的:ArrayList,LinkedList,SortedSet,NavigableSet,HashSet,LinkedHashSet,TreeSet,SortedMap,NavigableMap,HashMap,LinkedHashMap,TreeMap,ConcurrentHashMap;
- 所有支持类型的数组;
- 所有受支持类型的可空版本。
2.8.4、自定义 Parcelers (不理解)
即使你的类型不直接支持,你也可以为它写一个 Parceler
映射对象。
class ExternalClass(val value: Int)
object ExternalClassParceler : Parceler<ExternalClass> {
override fun create(parcel: Parcel) = ExternalClass(parcel.readInt())
override fun ExternalClass.write(parcel: Parcel, flags: Int) {
parcel.writeInt(value)
}
}
使用@TypeParceler
或 @WriteWith
注解,外部 parcelers 可以被应用:
// Class-local parceler
@Parcelable
@TypeParceler<ExternalClass, ExternalClassParceler>()
class MyClass(val external: ExternalClass)
// Property-local parceler
@Parcelable
class MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass)
// Type-local parceler
@Parcelable
class MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass)
2.8.3、Activity 传递序列化对象
@Parcelize
data class Model(val title: String, val amount: Int) : Parcelable
// 传递
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra(DetailActivity.EXTRA, model)
startActivity(intent)
// 获取
val model: Model = intent.getParcelableExtra(EXTRA)
kotlin-android-extensions 插件
官网介绍
一、简介
Kotlin Android扩展插件 可以节省 findviewbyid()
,实现 和 Data-Binding
,Dagger
框架的效果,不需要添加任何额外代码,也不影响任何运行时体验。
Kotlin Android扩展 是 Kotlin 插件的组成之一,不需要在单独安装插件。
如下实例:
// Using R.layout.activity_main from the 'main' source set
import kotlinx.android.synthetic.main.activity_main.*
class MyActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Instead of findViewById<TextView>(R.id.textView)
textView.setText("Hello, world!")
}
}
textView
是Activity的扩展属性,而且它和 在 activity_main
中声明的 有同样的类型(因此它是一个 TextView
)。
二、使用 Kotlin Android Extensions
2.1、Gradle 配置
添加
apply plugin: 'kotlin-android-extensions'
2.2、导入合成属性
**在 Activity中: ** 按照 import kotlinx.android.synthetic.main.<布局>.*
格式,可以导入布局文件中所有控件属性。
**在 View 中(Adapter , Fragment等) 中: ** 按照 import kotlinx.android.synthetic.main.<布局>.view.*
格式,可以导入布局文件中所有控件属性。
例子:
<TextView
android:id="@+id/hello"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
activity.hello.text = "Hello World!"
2.3、实验模式(Experimental Mode)
Android 扩展插件包含一些实验性功能。比如,LayoutContainer
支持 和 Parcelable
实现生成器。 这些功能目前还是实验性的,所以需要在 build.gradle
中打开它以能够去使用这些功能:
android {
...
androidExtensions {
experimental = true
}
...
}
2.4、LayoutContainer Support
安卓扩展插件支持多种s,最基本的一些有 Activity
,Fragment
和 View
,但是可以通过 实现 LayoutController
接口 将任何类 (实际上)转为Android 扩展容器:
import kotlinx.android.extensions.LayoutContainer
class ViewHolder(override valView: View) : ViewHolder(containerView), LayoutContainer {
fun setup(title: String) {
itemTitle.text = "Hello World!"
}
}
需要配置 experimental mode
2.4.1、Kotlin之Fragment中直接引用视图控件id,报空指针的问题
解决方案一 :
要想直接使用控件 id需要符合前置条件,就是对应的 layout 文件加载完毕后才可以直接使用控件id来操作,如果你在
onCreateView()
方法中去直接使用控件id去操作,肯定是空指针异常,因为return view
还没有执行呢 。在确保
onCreateView()
方法执行完毕后,就可以直接使用控件id来操作。
解决方案二 :
在fragment使用这个extensions的时候,会找不到那个控件,解决办法
onCreateView()
tv_show = view?.findViewById(R.id.tv_show) as TextView
importkotlinx.android.synthetic.main.gv_home_fragment_item.*
onViewCreated()
tv_show.text="123456789"
2.5、Flavor Support
安卓扩展插件支持 Android flavors
。
2.5.1、什么是 flavor
Flavaor 的作用: 将 debug 和 release 的 维度 扩大。
flavorDimensions: 是基于 flavor 的一个扩展,作用是再次扩大维度,它是一个属性,学会用它必须要先会用 flavor。
2.5.2、维度
在理解了flavor的前提下(Flavor基本使用),我们需要明白一个概念—————维度!
维度在 gradle 中的体现是 : flavorDimensions
。
源码:
public void flavorDimensions(String... dimensions) {
this.checkWritability();
this.flavorDimensionList = Arrays.asList(dimensions);
}
譬如:
flavorDimensions(“money”, “channel”)
这是一个正确的维度扩展思路:
- money 场景,一个APP,我们推出收费和免费的版本。
- channel 场景,不同的市场渠道。
根据这个思路来改代码:
原有:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
productFlavors {
baidu {
manifestPlaceholders = [CHANNEL: "baidu"]
}
xiaomi {
manifestPlaceholders = [CHANNEL: "xiaomi"]
}
}
目前有四个渠道:
- baiduDebug
- baiduRelease
- xiaomiDebug
- xiaomiRelease
添加flavorDimensions后:
android{
...
flavorDimensions("money", "channel")
productFlavors {
vip {
dimension "money"
}
free{
dimension "money"
}
baidu {
manifestPlaceholders = [CHANNEL: "baidu"]
dimension "channel"
}
xiaomi {
manifestPlaceholders = [CHANNEL: "xiaomi"]
dimension "channel"
}
}
...
}
现在就会有8个渠道了:
- vipBaiduDebug
- vipBaiduRelease
- vipXiaomiDebug
- vipXiaomiRelease
- freeBaiduDebug
- freeBaiduRelease
- freeXiaomiDebug
- freeXiaomiRelease
逻辑上就是:
由:
debug/releas(2) * 渠道(2) = 4个
变成了:
debug/releas(2) * 渠道(2) * 新增维度vip/free(2) = 4 * 2 = 8个
然后我们再从代码理解上来:
- 只要你定义了flavorDimensions,那么你后面的每个flavor就必须要写”dimension”这个属性。
- baidu和xiaomi是属于渠道这个维度,所以它的dimension是channel。
- vip和free这俩个flavor是属于money这个维度(收费与免费),所以它的dimension是money。
2.5.3、添加 flavor
android {
productFlavors {
free {
versionName "1.0-free"
}
}
}
所以可以将free/res/layout/activity_free.xml
的布局通过导入添加:
import kotlinx.android.synthetic.free.activity_free.*
在 experimental mode中,您可以指定任何 variant 名称(不仅仅是 flavor),例如 freeDebug
或 freeRelease
也起作用。(这个不是很懂)
2.6、视图缓存 (View Caching)
使用 findViewById()
可能会变得很慢,尤其是很复杂的视图层中。所以,Android 扩展试图通过 缓存试图来减少 findViewById()
。
默认情况下,Android 扩展 在每个 container written in Kotlin
(Activity, Fragment, View or a LayoutContainer implementation
) 添加了一个隐藏的缓存函数 和一个存储 field。
这个方法非常小,所以不会怎么增加 APK 的大小。
下面的例子中,findViewById()
只被调用了一次:
class MyActivity : Activity()
fun MyActivity.a() {
textView.text = "Hidden view"
textView.visibility = View.INVISIBLE
}
但是,在下面的例子中:
fun Activity.b() {
textView.text = "Hidden view"
textView.visibility = View.INVISIBLE
}
我们不知道 这个函数是否仅仅在 我们源代码的 activities 或者 简单的Java activities 中调用。因此,即使 MyActivity
的实例作为接收者传递,我们不会在这里使用缓存。
2.7、更改视图缓存策略
您可以更改全局或每个容器的缓存策略。这也需要打开 experimental mode。
2.7.1 设置项目全局缓存策略
项目全局缓存策略 在 build.gradle
文件中配置:
android{
androidExtensions {
experimental = true
defaultCacheImplementation = "HASH_MAP" // also SPARSE_ARRAY, NONE
}
}
默认情况下,Android扩展插件 使用 HashMap
作为后备存储,但您可以切换到 SparseArray
实现或者关闭缓存。当您仅使用Android扩展的 Parcelable
部分时,后者特别有用。
2.7.2、设置 缓存策略
用一个容器注解@ContainerOptions
来改变它的缓存策略:
import kotlinx.android.extensions.ContainerOptions
@ContainerOptions(cache = CacheImplementation.NO_CACHE)
class MyActivity : Activity()
fun MyActivity.a() {
// findViewById() will be called twice
textView.text = "Hidden view"
textView.visibility = View.INVISIBLE
}
2.8、Parcelable (序列化)
从Kotlin 1.1.4
开始,Android扩展插件 提供了 Parcelable
实现生成器作为实验性功能。
Android中的序列化:
在开发中,如果有需要用到序列化和反序列化的操作,就会用到 Serializable
或者 Parcelable
,它们各有优缺点,会适用于不同的场景。
Serializable
Serializable 的优点是实现简单,你只需要实现一个 Serializable 接口,并不需要任何额外的代码,但是它的序列化和反序列化,实际上是使用反射做的,所以效率会略低,并且它会在序列化的过程中,会创建很多临时变量,所以更容易触发 GC。
Parcelable
Parcelable 需要开发者自己去实现序列化的规则,所以会增加代码量,正是因为规则确定,所以效率会提高很多,并且不容易触发 GC。
2.8.1 Kotlin中,启用 Parcelable
支持 :
打开 experimental mode
2.8.2、Kotlin中如何使用
使用 @Parcelize
注解类,然后实现Parcelable
接口(方法实现自动生成)。
import kotlinx.android.parcel.Parcelize
@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable
@Parcelize
要求在主构造函数中声明所有序列化的属性。Android
扩展将 对声明在类体中带有 幕后字段的属性发出警告:
@Parcelize
class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
// [PLUGIN WARNING]:Property would not be serialized into a `Parcel`.
// Add `@IgnoreOnParcer` annotation to remove warning
var sex: Int = 0
}
另外, 如果某些主构造函数参数不是属性,则@Parcelize
不能应用:
class User(val firstName: String, lastName: String, val age: Int) // 可以
// 报错。[PLUGIN ERROR]:`Parcelabel constructor parameter should be val or var
@Parcelize
class User(val firstName: String, lastName: String, val age: Int) : Parcelable
如果类需要更高级的序列化逻辑,你可以把它写在一个伴随类中:
@Parcelize
data class Value(val firstName: String, val lastName: String, val age: Int) : Parcelable {
private companion object : Parceler<User> {
override fun User.write(parcel: Parcel, flags: Int) {
// Custom write implementation
}
override fun create(parcel: Parcel): User {
// Custom read implementation
}
}
}
2.8.3、@Parcelize 支持的类型
原始类型(及其盒装版本); 对象和枚举;
- String,CharSequence;
- Exception;
- Size,SizeF,Bundle,IBinder,IInterface,FileDescriptor;
- SparseArray,SparseIntArray,SparseLongArray,SparseBooleanArray,
- 所有Serializable(是的,Date也支持)和Parcelable实现;
- 所有受支持类型的集合:List( 映射到ArrayList ),Set(映射到LinkedHashSet),Map(映射到LinkedHashMap);
- 也有一些具体的实现的:ArrayList,LinkedList,SortedSet,NavigableSet,HashSet,LinkedHashSet,TreeSet,SortedMap,NavigableMap,HashMap,LinkedHashMap,TreeMap,ConcurrentHashMap;
- 所有支持类型的数组;
- 所有受支持类型的可空版本。
2.8.4、自定义 Parcelers (不理解)
即使你的类型不直接支持,你也可以为它写一个 Parceler
映射对象。
class ExternalClass(val value: Int)
object ExternalClassParceler : Parceler<ExternalClass> {
override fun create(parcel: Parcel) = ExternalClass(parcel.readInt())
override fun ExternalClass.write(parcel: Parcel, flags: Int) {
parcel.writeInt(value)
}
}
使用@TypeParceler
或 @WriteWith
注解,外部 parcelers 可以被应用:
// Class-local parceler
@Parcelable
@TypeParceler<ExternalClass, ExternalClassParceler>()
class MyClass(val external: ExternalClass)
// Property-local parceler
@Parcelable
class MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass)
// Type-local parceler
@Parcelable
class MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass)
2.8.3、Activity 传递序列化对象
@Parcelize
data class Model(val title: String, val amount: Int) : Parcelable
// 传递
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra(DetailActivity.EXTRA, model)
startActivity(intent)
// 获取
val model: Model = intent.getParcelableExtra(EXTRA)
kotlin-android-extensions 插件
官网介绍
一、简介
Kotlin Android扩展插件 可以节省 findviewbyid()
,实现 和 Data-Binding
,Dagger
框架的效果,不需要添加任何额外代码,也不影响任何运行时体验。
Kotlin Android扩展 是 Kotlin 插件的组成之一,不需要在单独安装插件。
如下实例:
// Using R.layout.activity_main from the 'main' source set
import kotlinx.android.synthetic.main.activity_main.*
class MyActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Instead of findViewById<TextView>(R.id.textView)
textView.setText("Hello, world!")
}
}
textView
是Activity的扩展属性,而且它和 在 activity_main
中声明的 有同样的类型(因此它是一个 TextView
)。
二、使用 Kotlin Android Extensions
2.1、Gradle 配置
添加
apply plugin: 'kotlin-android-extensions'
2.2、导入合成属性
**在 Activity中: ** 按照 import kotlinx.android.synthetic.main.<布局>.*
格式,可以导入布局文件中所有控件属性。
**在 View 中(Adapter , Fragment等) 中: ** 按照 import kotlinx.android.synthetic.main.<布局>.view.*
格式,可以导入布局文件中所有控件属性。
例子:
<TextView
android:id="@+id/hello"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
activity.hello.text = "Hello World!"
2.3、实验模式(Experimental Mode)
Android 扩展插件包含一些实验性功能。比如,LayoutContainer
支持 和 Parcelable
实现生成器。 这些功能目前还是实验性的,所以需要在 build.gradle
中打开它以能够去使用这些功能:
android {
...
androidExtensions {
experimental = true
}
...
}
2.4、LayoutContainer Support
安卓扩展插件支持多种s,最基本的一些有 Activity
,Fragment
和 View
,但是可以通过 实现 LayoutController
接口 将任何类 (实际上)转为Android 扩展容器:
import kotlinx.android.extensions.LayoutContainer
class ViewHolder(override valView: View) : ViewHolder(containerView), LayoutContainer {
fun setup(title: String) {
itemTitle.text = "Hello World!"
}
}
需要配置 experimental mode
2.4.1、Kotlin之Fragment中直接引用视图控件id,报空指针的问题
解决方案一 :
要想直接使用控件 id需要符合前置条件,就是对应的 layout 文件加载完毕后才可以直接使用控件id来操作,如果你在
onCreateView()
方法中去直接使用控件id去操作,肯定是空指针异常,因为return view
还没有执行呢 。在确保
onCreateView()
方法执行完毕后,就可以直接使用控件id来操作。
解决方案二 :
在fragment使用这个extensions的时候,会找不到那个控件,解决办法
onCreateView()
tv_show = view?.findViewById(R.id.tv_show) as TextView
importkotlinx.android.synthetic.main.gv_home_fragment_item.*
onViewCreated()
tv_show.text="123456789"
2.5、Flavor Support
安卓扩展插件支持 Android flavors
。
2.5.1、什么是 flavor
Flavaor 的作用: 将 debug 和 release 的 维度 扩大。
flavorDimensions: 是基于 flavor 的一个扩展,作用是再次扩大维度,它是一个属性,学会用它必须要先会用 flavor。
2.5.2、维度
在理解了flavor的前提下(Flavor基本使用),我们需要明白一个概念—————维度!
维度在 gradle 中的体现是 : flavorDimensions
。
源码:
public void flavorDimensions(String... dimensions) {
this.checkWritability();
this.flavorDimensionList = Arrays.asList(dimensions);
}
譬如:
flavorDimensions(“money”, “channel”)
这是一个正确的维度扩展思路:
- money 场景,一个APP,我们推出收费和免费的版本。
- channel 场景,不同的市场渠道。
根据这个思路来改代码:
原有:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
productFlavors {
baidu {
manifestPlaceholders = [CHANNEL: "baidu"]
}
xiaomi {
manifestPlaceholders = [CHANNEL: "xiaomi"]
}
}
目前有四个渠道:
- baiduDebug
- baiduRelease
- xiaomiDebug
- xiaomiRelease
添加flavorDimensions后:
android{
...
flavorDimensions("money", "channel")
productFlavors {
vip {
dimension "money"
}
free{
dimension "money"
}
baidu {
manifestPlaceholders = [CHANNEL: "baidu"]
dimension "channel"
}
xiaomi {
manifestPlaceholders = [CHANNEL: "xiaomi"]
dimension "channel"
}
}
...
}
现在就会有8个渠道了:
- vipBaiduDebug
- vipBaiduRelease
- vipXiaomiDebug
- vipXiaomiRelease
- freeBaiduDebug
- freeBaiduRelease
- freeXiaomiDebug
- freeXiaomiRelease
逻辑上就是:
由:
debug/releas(2) * 渠道(2) = 4个
变成了:
debug/releas(2) * 渠道(2) * 新增维度vip/free(2) = 4 * 2 = 8个
然后我们再从代码理解上来:
- 只要你定义了flavorDimensions,那么你后面的每个flavor就必须要写”dimension”这个属性。
- baidu和xiaomi是属于渠道这个维度,所以它的dimension是channel。
- vip和free这俩个flavor是属于money这个维度(收费与免费),所以它的dimension是money。
2.5.3、添加 flavor
android {
productFlavors {
free {
versionName "1.0-free"
}
}
}
所以可以将free/res/layout/activity_free.xml
的布局通过导入添加:
import kotlinx.android.synthetic.free.activity_free.*
在 experimental mode中,您可以指定任何 variant 名称(不仅仅是 flavor),例如 freeDebug
或 freeRelease
也起作用。(这个不是很懂)
2.6、视图缓存 (View Caching)
使用 findViewById()
可能会变得很慢,尤其是很复杂的视图层中。所以,Android 扩展试图通过 缓存试图来减少 findViewById()
。
默认情况下,Android 扩展 在每个 container written in Kotlin
(Activity, Fragment, View or a LayoutContainer implementation
) 添加了一个隐藏的缓存函数 和一个存储 field。
这个方法非常小,所以不会怎么增加 APK 的大小。
下面的例子中,findViewById()
只被调用了一次:
class MyActivity : Activity()
fun MyActivity.a() {
textView.text = "Hidden view"
textView.visibility = View.INVISIBLE
}
但是,在下面的例子中:
fun Activity.b() {
textView.text = "Hidden view"
textView.visibility = View.INVISIBLE
}
我们不知道 这个函数是否仅仅在 我们源代码的 activities 或者 简单的Java activities 中调用。因此,即使 MyActivity
的实例作为接收者传递,我们不会在这里使用缓存。
2.7、更改视图缓存策略
您可以更改全局或每个容器的缓存策略。这也需要打开 experimental mode。
2.7.1 设置项目全局缓存策略
项目全局缓存策略 在 build.gradle
文件中配置:
android{
androidExtensions {
experimental = true
defaultCacheImplementation = "HASH_MAP" // also SPARSE_ARRAY, NONE
}
}
默认情况下,Android扩展插件 使用 HashMap
作为后备存储,但您可以切换到 SparseArray
实现或者关闭缓存。当您仅使用Android扩展的 Parcelable
部分时,后者特别有用。
2.7.2、设置 缓存策略
用一个容器注解@ContainerOptions
来改变它的缓存策略:
import kotlinx.android.extensions.ContainerOptions
@ContainerOptions(cache = CacheImplementation.NO_CACHE)
class MyActivity : Activity()
fun MyActivity.a() {
// findViewById() will be called twice
textView.text = "Hidden view"
textView.visibility = View.INVISIBLE
}
2.8、Parcelable (序列化)
从Kotlin 1.1.4
开始,Android扩展插件 提供了 Parcelable
实现生成器作为实验性功能。
Android中的序列化:
在开发中,如果有需要用到序列化和反序列化的操作,就会用到 Serializable
或者 Parcelable
,它们各有优缺点,会适用于不同的场景。
Serializable
Serializable 的优点是实现简单,你只需要实现一个 Serializable 接口,并不需要任何额外的代码,但是它的序列化和反序列化,实际上是使用反射做的,所以效率会略低,并且它会在序列化的过程中,会创建很多临时变量,所以更容易触发 GC。
Parcelable
Parcelable 需要开发者自己去实现序列化的规则,所以会增加代码量,正是因为规则确定,所以效率会提高很多,并且不容易触发 GC。
2.8.1 Kotlin中,启用 Parcelable
支持 :
打开 experimental mode
2.8.2、Kotlin中如何使用
使用 @Parcelize
注解类,然后实现Parcelable
接口(方法实现自动生成)。
import kotlinx.android.parcel.Parcelize
@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable
@Parcelize
要求在主构造函数中声明所有序列化的属性。Android
扩展将 对声明在类体中带有 幕后字段的属性发出警告:
@Parcelize
class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
// [PLUGIN WARNING]:Property would not be serialized into a `Parcel`.
// Add `@IgnoreOnParcer` annotation to remove warning
var sex: Int = 0
}
另外, 如果某些主构造函数参数不是属性,则@Parcelize
不能应用:
class User(val firstName: String, lastName: String, val age: Int) // 可以
// 报错。[PLUGIN ERROR]:`Parcelabel constructor parameter should be val or var
@Parcelize
class User(val firstName: String, lastName: String, val age: Int) : Parcelable
如果类需要更高级的序列化逻辑,你可以把它写在一个伴随类中:
@Parcelize
data class Value(val firstName: String, val lastName: String, val age: Int) : Parcelable {
private companion object : Parceler<User> {
override fun User.write(parcel: Parcel, flags: Int) {
// Custom write implementation
}
override fun create(parcel: Parcel): User {
// Custom read implementation
}
}
}
2.8.3、@Parcelize 支持的类型
原始类型(及其盒装版本); 对象和枚举;
- String,CharSequence;
- Exception;
- Size,SizeF,Bundle,IBinder,IInterface,FileDescriptor;
- SparseArray,SparseIntArray,SparseLongArray,SparseBooleanArray,
- 所有Serializable(是的,Date也支持)和Parcelable实现;
- 所有受支持类型的集合:List( 映射到ArrayList ),Set(映射到LinkedHashSet),Map(映射到LinkedHashMap);
- 也有一些具体的实现的:ArrayList,LinkedList,SortedSet,NavigableSet,HashSet,LinkedHashSet,TreeSet,SortedMap,NavigableMap,HashMap,LinkedHashMap,TreeMap,ConcurrentHashMap;
- 所有支持类型的数组;
- 所有受支持类型的可空版本。
2.8.4、自定义 Parcelers (不理解)
即使你的类型不直接支持,你也可以为它写一个 Parceler
映射对象。
class ExternalClass(val value: Int)
object ExternalClassParceler : Parceler<ExternalClass> {
override fun create(parcel: Parcel) = ExternalClass(parcel.readInt())
override fun ExternalClass.write(parcel: Parcel, flags: Int) {
parcel.writeInt(value)
}
}
使用@TypeParceler
或 @WriteWith
注解,外部 parcelers 可以被应用:
// Class-local parceler
@Parcelable
@TypeParceler<ExternalClass, ExternalClassParceler>()
class MyClass(val external: ExternalClass)
// Property-local parceler
@Parcelable
class MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass)
// Type-local parceler
@Parcelable
class MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass)
2.8.3、Activity 传递序列化对象
@Parcelize
data class Model(val title: String, val amount: Int) : Parcelable
// 传递
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra(DetailActivity.EXTRA, model)
startActivity(intent)
// 获取
val model: Model = intent.getParcelableExtra(EXTRA)