创建一个基于 Kotlin 的 Android 项目(下集)

简介: 本文讲的是创建一个基于 Kotlin 的 Android 项目(下集),在先前的文章中,我们从零开始新建了一个项目,并且为小猫咪应用调整了 build.gradle。
本文讲的是创建一个基于 Kotlin 的 Android 项目(下集),

在先前的文章中,我们从零开始新建了一个项目,并且为小猫咪应用调整了 build.gradle

接下来就是针对应用的基础部分编写代码了。

数据模型

此应用的一个主要特征是通过网络从 http://thecatapi.com/ 中解析数据。

完整的 API 如此调用:http://thecatapi.com/api/images/get?format=xml&results_per_page=10

API 返回一个 XML 文件,如下:

它需要反序列化数据来获取包含小猫咪图片位置的 url 属性。

Kotlin 有一个非常有用的数据类(data class)可以完美实现此目的。

右击 model.cats 包 (package) 开始新建一个类文件并且选择 New -> Kotlin File/Class 然后将其命名为 Cats 并选择Class 作为文件类型。

为像接收到的 XML 文件那样构造类,Cats.kt 文件将如下所示:

data class Cats(var data: Data? = null)

data class Data(var images: ArrayList<Image>? = null)

data class Image(var url: String? = "", var id: String? = "", var source_url: String? = "")

目前还非常简单……

但同样的类在 Java 中长多了!

Kotlin 中的数据类有几个好处,例如由编译器生成 getter()setter() 以及 toString() 方法,还有更多的像equals()hashCode() 以及 copy() 这些。所以使用它反序列化数据甚是完美。

API 调用

通过网络解析数据有很多种方法,也有各种第三方库可以应付。其中就有 Square 的 Retrofit2

这是一个非常强大的 HTTPClient 并且安装简单。

我们从 interface 开始,先在 network 包下创建之。

称其为 CatAPI,如下所示:

interface CatAPI {
    @GET("/api/images/get?format=xml&amp;results_per_page=" + BuildConfig.MAX_IMAGES_PER_REQUEST)
    fun getCatImageURLs(): Observable<Cats>
}

interface 会完成对 API 端 /api/images/get?format=xml&amp;results_per_page= 的 Get 请求。

本例中 results_per_page 参数从 build.gradle 中定义的 MAX_IMAGES_PER_REQUEST 常量获取数值,该常量的不同取值取决于使用的 buildTypes

buildTypes {
    debug {
        buildConfigField("int", "MAX_IMAGES_PER_REQUEST", "10")
        ...

此方式对常量在像 debug 或 release 情景下的不同取值极其有用, 尤其是在需要从线上 API 切换到测试 API 的时候

关于 interface CatAPI 有一个关键点,那就是用来实现从 API 回调的函数 fun getCatImageURLs(): Observable<Cats>

所以下一步便是其实现。

同在 network 包下,新建一个类并将其命名为 CatAPINetwork,如下:

class CatAPINetwork {
    fun getExec(): Observable<Cats> {
        val retrofit = Retrofit.Builder()
            .baseUrl("http://thecatapi.com")
            .addConverterFactory(SimpleXmlConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build()

        val catAPI: CatAPI = retrofit.create(CatAPI::class.java)

        return catAPI.getCatImageURLs().
            subscribeOn(Schedulers.io()).
            observeOn(AndroidSchedulers.mainThread())
    }
}

fun getExec(): Observable<Cats> 为隐式 public,这意味着它可以在此类以外被调用。

.addConverterFactory(SimpleXmlConverterFactory.create()) 这一行表明使用 XML 转换器来反序列化调用 API 的结果。

接着 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 是用于 API 回调的调用适配器。

return 行返回 RxJava 的 Observable 对象:

return catAPI.getCatImageURLs().
            subscribeOn(Schedulers.io()).
            observeOn(AndroidSchedulers.mainThread())

Presenter

Presenter 模块负责完成应用的逻辑部分并在 View 和 Model 之间实现数据绑定。

本例会实现 View 调用以解析 API 数据的方法并将其送至负责展示的 Adapter

为与 View 通信,我们先在 presenter 包中创建其 interface 然后将其命名为 MasterPresenter,如下所示:

interface MasterPresenter {
    fun connect(imagesAdapter: ImagesAdapter)
    fun getMasterRequest()
}

第一个函数 fun connect(imagesAdapter: ImagesAdapter) 用来连接 Adapter interface 以显示数据,并且由 fun getMasterRequest() 启动 API 请求。

我们将这些实现置于 presenter 包的一个新类中并将其命名为 MasterPresenterImpl

class MasterPresenterImpl : MasterPresenter {
    lateinit private var imagesAdapter: ImagesAdapter

    override fun connect(imagesAdapter: ImagesAdapter) {
        this.imagesAdapter = imagesAdapter
    }

    override fun getMasterRequest() {
        imagesAdapter.setObservable(getObservableMasterRequest(CatAPINetwork()))
    }

    private fun getObservableMasterRequest(catAPINetwork: CatAPINetwork): Observable<Cats> {
        return catAPINetwork.getExec()
    }
}

值得注意的是,在 lateinit private var imagesAdapter: ImagesAdapter 一行中,Kotlin 允许我们使用 lateinit 关键字在未初始化的情况下声明一个非空可变的对象。它将会在运行时第一次使用它的时候被初始化,比如在本例中会调用 fun connect(imagesAdapter: ImagesAdapter)

fun getMasterRequest() 函数负责启用 API 调用,只设置 Observable 以便 Adapter (例如 imagesAdapter)在启用执行 API 调用的 catAPINetwork.getExec() 函数后“订阅”之。

View 部分

实现 UI 的类均集中于 view 包中。

基本上都是 View 和 Adapter 这些;本例中是 MainActivity 和 ImagesAdapter

Layouts

开始实现之前,我们先来研究一下布局 ( Layout ) 设计。

Kitten App

为实现此设计我们大体上需要主容器和 item 容器这两个基本组件。

主容器包含 item 列表,且我们会将其置于项目 res -> layout 文件夹的 activity_main.xml 中;此文件已在创建项目的初始价段自动生成。

我们需要将应用装进一个RecyclerView 组件中(一个非常强大并且改良过的列表视图组件)。

activity_main.xml 如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    tools:context=".view.MainActivity"
    android:gravity="center">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/containerRecyclerView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scrollbars="vertical"
        android:layout_centerInParent="true" />
</RelativeLayout>

containerRecyclerView 组件代表 item 列表主容器

row_card_view.xml 是列表的 item 容器,大体上像这样:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/card_view"
    android:layout_gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    card_view:cardCornerRadius="4dp"
    android:layout_margin="16dp"
    android:background="@android:color/transparent"
    android:layout_centerInParent="true"
    android:elevation="4dp">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:foregroundGravity="center">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/imgVw_cat"
            android:padding="4dp"
            android:layout_centerInParent="true"
            android:scaleType="fitCenter"
            android:contentDescription="@string/cat_image" />
    </RelativeLayout>
</android.support.v7.widget.CardView>

如你所见,item 容器正是主要由一个包含 ImageView (imgVw_cat) 的 RelativeLayout 组成的 card_view

Adapter

现在已经有了 Layout 的基本部分,那么接下来我们继续实现 MainActivity 和 Adapter

从 Adapter 开始首先要创建其 interface 以被前面的 MasterPresenterImpl 调用,所以我们在 view 包中新建一个文件并将其命名为 ImagesAdapter,然后内容如下:

interface ImagesAdapter {
    fun setObservable(observableCats: Observable<Cats>)
    fun unsubscribe()
}

setObservable(observableCats: Observable<Cats>) 函数被 MasterPresenterImpl 调用来设置 Observable 以及让Adapter “订阅”。

unsubscribe() 函数会被 MainActivity 调用以在 activity 被销毁的时候“退订” Adapter

现在我们在同一个包下一个新建的类中实现它们,称其为 ImagesAdapterImpl,如下:

class ImagesAdapterImpl : RecyclerView.Adapter<ImagesAdapterImpl.ImagesURLsDataHolder>(), ImagesAdapter {
    private val TAG = ImagesAdapterImpl::class.java.simpleName

    private var cats: Cats? = null
    private val subscriber: Subscriber<Cats> by lazy { getSubscribe() }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImagesURLsDataHolder {
        return ImagesURLsDataHolder(
                LayoutInflater.from(parent.context).inflate(R.layout.row_card_view, parent, false))
    }

    override fun getItemCount(): Int {
        return cats?.data?.images?.size ?: 0
    }

    override fun onBindViewHolder(holder: ImagesURLsDataHolder, position: Int) {
        holder.bindImages(cats?.data?.images?.get(position)?.url ?: "")
    }

    private fun setData(cats: Cats?) {
        this.cats = cats
    }

    override fun setObservable(observableCats: Observable<Cats>) {
        observableCats.subscribe(subscriber)
    }

    override fun unsubscribe() {
        if (!subscriber.isUnsubscribed) {
            subscriber.unsubscribe()
        }
    }

    private fun getSubscribe(): Subscriber<Cats> {
        return object : Subscriber<Cats>() {
            override fun onCompleted() {
                Log.d(TAG, "onCompleted")
                notifyDataSetChanged()
            }

            override fun onNext(cats: Cats) {
                Log.d(TAG, "onNextNew")
                setData(cats)
            }

            override fun onError(e: Throwable) {
                //TODO : Handle error here
                Log.d(TAG, "" + e.message)
            }
        }
    }

    class ImagesURLsDataHolder(view: View) : RecyclerView.ViewHolder(view) {

        fun bindImages(imgURL: String) {
            Glide.with(itemView.context).
                    load(imgURL).
                    placeholder(R.mipmap.document_image_cancel).
                    diskCacheStrategy(DiskCacheStrategy.ALL).
                    centerCrop().
                    into(itemView.imgVw_cat)
        }
    }
}

这是填充 row_card_view.xml 的类,基本上就是 onCreateViewHolder 函数的 item 容器。

在 private val subscriber: Subscriber<Cats> by lazy { getSubscribe() } 一行中,getSubscribe() 函数为 Adapter“订阅”用到的 Observable,这里你会看到 lazy 初始化,这是一种声明一个不可变对象的方法(比如 subscriber)并且会在运行时首次调用时创建于函数体内(例如 getSubscribe())。

Subscriber 和 Observable 概念来源于 RxJava;我们今后会深入讨论。

最后值得注意的还有使用 Glide 库来填充 imgVw_cat 的名为 ImagesURLsDataHolder 的内部类 (inner class) ,这有助于从调用 API 取得的传递 URL 获取图片。这部分包含在 bindImages(imgURL: String) 函数中并且由统一文件中的onBindViewHolder 方法调用。

Activity

最后同样重要的便是 Activity(例如 MainActivity):

class MainActivity : AppCompatActivity() {
    private val imagesAdapterImpl: ImagesAdapterImpl by lazy { ImagesAdapterImpl() }

    private val masterPresenterImpl: MasterPresenterImpl
            by lazy {
                MasterPresenterImpl()
            }

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

        initRecyclerView()
        connectingToMasterPresenter()
        getURLs()
    }

    override fun onDestroy() {
        imagesAdapterImpl.unsubscribe()
        super.onDestroy()
    }

    private fun initRecyclerView() {
        containerRecyclerView.layoutManager = GridLayoutManager(this, 1)
        containerRecyclerView.adapter = imagesAdapterImpl
    }

    private fun connectingToMasterPresenter() {
        masterPresenterImpl.connect(imagesAdapterImpl)
    }

    private fun getURLs() {
        masterPresenterImpl.getMasterRequest()
    }
}

注意到以下函数:

  • initRecyclerView()
  • connectingToMasterPresenter()
  • getURLs()

分别用于:

  • 初始化主容器(例如 RecyclerView
  • 将 MasterPresenterImpl 连接至 MainActivity 并传至 ImagesAdapterImpl(又称 Adapter) 的 interface
  • getURLs() 启动 API 请求以获取 XML 数据,然后执行任务(反序列化数据,通过 Adapter 获取图片)。

至此小猫咪应用已经准备就绪。

你可以在我 Github 仓库中找到 KShow 完整的项目。

该项目也有 Java 的实现:JShows,以便对比。





原文发布时间为:2016年04月20日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
1天前
|
XML Java Android开发
Android 分享机顶盒项目的封装类《GridView》(三)(转)
Android 分享机顶盒项目的封装类《GridView》(三)(转)
11 2
|
2天前
|
Android开发
Android 分享机顶盒项目的封装类《GridView》(二)(转)
Android 分享机顶盒项目的封装类《GridView》(二)(转)
11 2
|
3天前
|
安全 Java Android开发
构建高效Android应用:采用Kotlin进行内存优化的策略
【5月更文挑战第8天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,合理管理内存资源是确保应用流畅运行的关键因素之一。近年来,Kotlin作为官方推荐的开发语言,以其简洁、安全和互操作性的特点受到开发者青睐。本文将深入探讨利用Kotlin语言特性,通过具体策略对Android应用的内存使用进行优化,旨在帮助开发者提高应用性能,减少内存消耗,避免常见的内存泄漏问题。
7 0
|
4天前
|
Android开发 Kotlin
Kotlin开发Android之基础问题记录
Kotlin开发Android之基础问题记录
15 1
|
4天前
|
移动开发 数据库 Android开发
构建高效Android应用:Kotlin协程的全面应用
【5月更文挑战第7天】 在移动开发领域,性能优化与流畅的用户体验是至关重要的。随着Kotlin语言的流行,其并发神器——协程,已成为提升Android应用性能的重要工具。本文将深入探讨如何在Android项目中利用Kotlin协程进行异步编程、网络请求和数据库操作,以及如何通过协程简化代码结构,增强应用的响应性和稳定性。我们的目标是为开发者提供一套实用的协程使用模式和最佳实践,以便构建更加高效的Android应用。
20 3
|
4天前
|
移动开发 Java Android开发
Android应用开发:Kotlin语言的优势与实践
【5月更文挑战第7天】 在移动开发的世界中,Android平台的Kotlin语言以其高效、简洁的语法和强大的功能吸引了众多开发者。本文将深入探讨Kotlin语言的核心优势,并通过实际案例展示如何在Android应用开发中有效地运用这一现代编程语言。我们将从语言特性出发,逐步分析其在提升开发效率、改善代码质量以及增强应用性能方面的具体表现,为读者提供一个全面而细致的Kotlin应用开发指南。
|
4天前
|
移动开发 数据库 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【5月更文挑战第7天】 在移动开发领域,性能优化和资源管理始终是核心议题。随着Kotlin语言的普及,其提供的协程特性为Android开发者带来了异步编程的新范式。本文将深入探讨如何通过Kotlin协程来优化Android应用的性能,实现流畅的用户体验,并减少资源消耗。我们将分析协程的核心概念,并通过实际案例演示其在Android开发中的应用场景和优势。
|
7天前
|
移动开发 前端开发 Android开发
构建高效Android应用:探究Kotlin协程的优势
【5月更文挑战第4天】 在移动开发领域,尤其是对于Android开发者而言,编写响应迅速且高效的应用程序至关重要。Kotlin作为一种现代的编程语言,其提供的协程特性为异步编程带来了革命性的改变。本文将深入探讨Kotlin协程在Android开发中的应用优势,并通过实例代码展示如何利用协程简化异步任务处理,提高应用性能和用户体验。
|
7天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能对比
【5月更文挑战第4天】在移动开发的世界中,性能一直是衡量应用质量的重要指标。随着Kotlin的兴起,许多Android开发者开始考虑是否应该从传统的Java迁移到Kotlin。本文通过深入分析两者在Android平台上的性能差异,帮助开发者理解Kotlin在实际项目中的表现,并提供选择编程语言时的参考依据。
20 5
|
10天前
|
Java 编译器 Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【5月更文挑战第1天】 在移动开发的世界中,性能优化始终是开发者关注的焦点。随着Kotlin的兴起,许多团队和开发者面临着一个选择:是坚持传统的Java语言,还是转向现代化、更加简洁的Kotlin?本文通过深入分析和对比Kotlin与Java在Android应用开发中的性能表现,揭示两者在编译效率、运行速度和内存消耗等方面的差异。我们将探讨如何根据项目需求和团队熟悉度,选择最适合的语言,以确保应用的高性能和流畅体验。