欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Kotlin For Android】(二): Kotlin Android Extensions

程序员文章站 2024-03-14 23:05:19
...

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

安卓扩展插件支持多种 containers,最基本的一些有 ActivityFragmentView,但是可以通过 实现 LayoutController 接口 将任何类 (实际上)转为Android 扩展容器:

import kotlinx.android.extensions.LayoutContainer

class ViewHolder(override val containerView: 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、设置 container 缓存策略

用一个容器注解@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://my.oschina.net/Agnes2017/blog/1802129