当前位置: 首页>编程语言>正文

androidx使用kotlin kotlin android

 

kotlin-android-extensions 插件

官网介绍

一、简介

Kotlin Android扩展插件 可以节省 findviewbyid(),实现 和 Data-BindingDagger 框架的效果,不需要添加任何额外代码,也不影响任何运行时体验。

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,最基本的一些有 ActivityFragmentView,但是可以通过 实现 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),例如 freeDebugfreeRelease也起作用。(这个不是很懂

2.6、视图缓存 (View Caching)

使用 findViewById()可能会变得很慢,尤其是很复杂的视图层中。所以,Android 扩展试图通过 缓存试图来减少 findViewById()

默认情况下,Android 扩展 在每个 container written in KotlinActivity, 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-BindingDagger 框架的效果,不需要添加任何额外代码,也不影响任何运行时体验。

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,最基本的一些有 ActivityFragmentView,但是可以通过 实现 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),例如 freeDebugfreeRelease也起作用。(这个不是很懂

2.6、视图缓存 (View Caching)

使用 findViewById()可能会变得很慢,尤其是很复杂的视图层中。所以,Android 扩展试图通过 缓存试图来减少 findViewById()

默认情况下,Android 扩展 在每个 container written in KotlinActivity, 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-BindingDagger 框架的效果,不需要添加任何额外代码,也不影响任何运行时体验。

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,最基本的一些有 ActivityFragmentView,但是可以通过 实现 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),例如 freeDebugfreeRelease也起作用。(这个不是很懂

2.6、视图缓存 (View Caching)

使用 findViewById()可能会变得很慢,尤其是很复杂的视图层中。所以,Android 扩展试图通过 缓存试图来减少 findViewById()

默认情况下,Android 扩展 在每个 container written in KotlinActivity, 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)


 


https://www.xamrdz.com/lan/5kw1924464.html

相关文章: