Android开发中Kotlin的扩展库及实现Parcelable

参考链接:

在使用Kotlin开发Android中我们可以使用安卓扩展库来方便Android开发。

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'   //扩展库

不一样的findViewById()

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".main.MainActivity">

    <TextView
        android:id="@+id/hellotext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

不使用findViewById()而是直接获取对象(对象名就是定义的id)

class MainActivity: BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main);

        hellotext.setText(R.string.string_use_kotlin_ext)
    }

    companion object {
        fun newIntent(context: Context): Intent{
            return Intent(context, MainActivity::class.java)
        }
    }
}

是不是很优雅,使用起来很方便呢?

在MainActivity.kt中我们发现自动导入了一个包

import kotlinx.android.synthetic.main.activity_main.*

我上面提到代码,其实生成的代码包含页面元素缓存,因此你再次获取这个页面元素的时候,就不需要再使用findViewById方法了。

Android Studio的反编译kotlin

我们知道Java代码可以很方便的转换成Kotlin代码(Code–>Convert Java File To Kotlin File),同样在Android Studio中有一个很方便很好用的功能来将Kotlin转换成Java代码。

第一步:Tools–>Kotlin–>Show Kotlin Bytecode(这一步可以将kotlin转换成该文件生成的字节码)

kotlin转换成该文件生成的字节码

第二步:点击Kotlin Bytecode左上角的Decompile来反编译成Java代码

protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(2131296284);
    ((TextView)this._$_findCachedViewById(id.hellotext)).setText(-2000042);
}

public View _$_findCachedViewById(int var1) {
    if (this._$_findViewCache == null) {
        this._$_findViewCache = new HashMap();
    }

    View var2 = (View)this._$_findViewCache.get(var1);
    if (var2 == null) {
        var2 = this.findViewById(var1);
        this._$_findViewCache.put(var1, var2);
    }

    return var2;
}

可以看到事实上扩展帮我们做了对象缓存。

Kotlin 1.1.4 Experimental Mode(实验模式)

Kotlin 1.1.4中添加了一些扩展,我们可以在build.gradle中增加如下配置:

androidExtensions {
    experimental = true
}

这样我们就可以在ViewHolder或者其他自定义类中使用扩展库了。

import kotlinx.android.extensions.LayoutContainer

class ViewHolder(override val containerView: View) : ViewHolder(containerView), LayoutContainer {
    fun setup(title: String) {
        itemTitle.text = "Hello World!"
    }
}

多渠道支持

安卓扩展插件支持安卓多渠道。假设当前在 build.gradle 文件中指定一个名为 baidu 的渠道:

android {
    productFlavors {
        baidu {
            versionName "1.0-0"
        }
    }
}

改变View缓存策略

我们上面已经知道,kotlin的findViewById实现了对View的缓存,我们可以通过配置build.gradle来更改缓存策略。

androidExtensions {
    defaultCacheImplementation = "HASH_MAP" // also SPARSE_ARRAY, NONE
}

默认缓存使用的是HashMap结构(HASH_MAP),我们可以设置为SparseArray实现(SPARSE_ARRAY)或者直接关闭掉(NONE)缓存。

Parcelable接口

在Activity之间传递数据我们通过Intent,如果我们要传递的是一个对象可以实现Serializable接口,还可以实现Parcelable接口。

实现Parcelable需要实现接口中的两个方法:

public int describeContents();
public void writeToParcel(Parcel dest, int flags);

第一个方法是内容接口描述,默认返回0就可以了 第二个方法是将我们的对象序列化一个Parcel对象,也就是将我们的对象存入Parcel中

@Override
public int describeContents() {
    return 0;  //一般返回零就可以了
}

@Override
public void writeToParcel(Parcel dest, int flags) {      //在这个方法中写入这个类的变量
    dest.writeString();   //对应着 String name;
    dest.writeInt();      //对应着 Int age;
}

在实现上面的接口方法后,接下来还需要执行反序列化,定义一个变量,并重新定义其中的部分方法。

public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>(){

    //在这个方法中反序列化上面的序列化内容,最后根据反序列化得到的各个属性,得到之前试图传递的对象
    @Override
    public Person createFromParcel(Parcel source) { 
        //反序列化的属性的顺序必须和之前写入的顺序一致
        Person person = new Person();
        person.name = source.readString();
        person.age = source.readAge();
        return person;
    }

    @Override
    public Person[] newArray(int size) {
        return new Person[size]; //一般返回一个数量为size的传递的类的数组就可以了
    }
};

最后使用Intent传递:

//传递
Person person = new Person("张三",18);
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);

//接受
Person person = (Person) getIntent.getParcelableExtra("person_data);

很方便的实现Parcelable接口

在kotlin中使用 @Parcelize注解,你能用一种简单的方式让任何类都实现Parcelable接口。 你只需要加注解,插件会做所有的脏活累活:

import kotlinx.android.parcel.Parcelize

@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable

被@Parcelize注解标记的类的属性必须都是可序列化的而且必须在主构造函数中声明,否则会有警告。如果部分属性没有在主构造函数中声明则不能够使用该标记。

如果需要特殊的序列化逻辑,可以在companion class中实现。

@Parcelize
data class User(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
        }
    }
}

@Parcelize标记支持的数据类型:

  • 基础类型(包括包装类型)
  • 对象和枚举
  • String、CharSequence
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • 所有实现了Serializable的类(包括Date)和所有实现了Parcelable的类。
  • 支持所有集合类型:List、Set、Map和具体实现类型ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • 所有类型的数组

自定义Parcelers

如果你要使用的类型不直接支持,还可以写一个Paracler映射对象。

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)
    }
}

外部parcelers可以使用@TypeParceler或者@WriteWith注解。

// Class-local parceler
@Parcelize
@TypeParceler<ExternalClass, ExternalClassParceler>()
class MyClass(val external: ExternalClass)

// Property-local parceler
@Parcelize
class MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass)

// Type-local parceler
@Parcelize
class MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass)