当前位置:首页 > 经验 >

android开发技术(android开发详细教程)

来源:原点资讯(www.yd166.com)时间:2022-11-01 21:14:34作者:YD166手机阅读>>

下面我们选取 Kotlin 的几个典型特性,结合代码简单介绍下其优势。

4.2 简化函数声明

Kotlin 语法的简洁体现在很多地方,就比如函数声明的简化。

如下是一个包含条件语句的 Java 函数的写法:

String generateAnswerString(int count, int countThreshold) { if (count > countThreshold) { return "I have the answer."; } else { return "The answer eludes me."; } }

Java 支持三元运算符可以进一步简化。

String generateAnswerString(int count, int countThreshold) { return count > countThreshold ? "I have the answer." : "The answer eludes me."; }

Kotlin 的语法并不支持三元运算符,但可以做到同等的简化效果:

fun generateAnswerString(count: Int, countThreshold: Int): String { return if (count > countThreshold) "I have the answer." else "The answer eludes me." }

它同时还可以省略大括号和 return 关键字,采用赋值形式进一步简化。这样子的写法已经很接近于语言的日常表达,高级~

fun generateAnswerString(count: Int, countThreshold: Int): String = if (count > countThreshold) "I have the answer." else "The answer eludes me."

反编译 Class 之后发现其实际上仍采用的三元运算符的写法,这种语法糖会体现在 Kotlin 的很多地方。

public final String generateAnswerString2(int count, int countThreshold) { return count > countThreshold ? "I have the answer." : "The answer eludes me."; }4.3 高阶函数

介绍高阶函数之前,我们先看一个向函数内传入回调接口的例子。

一般来说,需要先定义一个回调接口,调用函数传入接口实现的实例,函数进行一些处理之后执行回调,借助Lambda 表达式可以对接口的实现进行简化。

interface Mapper { int map(String input); } class Temp { void main() { stringMapper("Android", input -> input.length() 2); } int stringMapper(String input, Mapper mapper) { // Do something ... return mapper.map(input); } }

Kotlin 则无需定义接口,直接将匿名回调函数作为参数传入即可。(匿名函数是最后一个参数的话,方法体可单独拎出,增加可读性)

这种接受函数作为参数或返回值的函数称之为高阶函数,非常方便。

class Temp { fun main() { stringMapper("Android") {input -> input.length 2} } fun stringMapper(input: String, mapper: (String) -> Int): Int { // Do something ... return mapper(input) } }

事实上这也是语法糖,编译器会预设默认接口来帮忙实现高阶函数。

4.4 Null 安全

可以说 Null 安全是 Kotlin 语言的一大特色。试想一下 Java 传统的 Null 处理无非是在调用之前加上空判断或卫语句,这种写法既繁琐,更容易遗漏。

void Function(Bean bean) { // Null check if (bean != null) { bean.doSometh(); } // 或者卫语句 if (bean == null) { return; } bean.doSometh(); }

而 Kotlin 要求变量在定义的时候需要声明是否可为空:带上 ? 即表示可能为空,反之不为空。作为参数传递给函数的话也要保持是否为空的类型一致,否则无法通过编译。

比如下面的 functionA() 调用 functionB() 将导致编译失败,但 functionB() 的参数在声明的时候没有添加 ? 即为非空类型,那么函数内可直接使用该参数,没有 NPE 的风险。

fun functionA() { var bean: Bean? = null functionB(bean) } fun functionB(bean: Bean) { bean.doSometh() }

为了通过编译,可以将变量 bean 声明中的 ? 去掉, 并赋上正常的值。

但很多时候变量的值是不可控的,我们无法保证它不为空。那么为了通过编译,还可以选择将参数 bean 添加上 ? 的声明。这个时候函数内不就不可直接使用该参数了,需要做明确的 Null 处理,比如:

  • 在使用之前也加上 ? 的限定,表示该参数不为空的情况下才触发调用
  • 在使用之前加上 !! 的限定也可以,但表示无论参数是否为空的情况下都触发调用,这种强制的调用即会告知开发者此处有 NPE 的风险

fun functionB(bean: Bean?) { // bean.doSometh() // 仍然直接调用将导致编译失败 // 不为空才调用 bean?.doSometh() // 或强制调用,开发者已知 NPE 风险 bean!!.doSometh() }

总结起来将很好理解:

  • 参数为非空类型,传递的实例也必须不为空
  • 参数为可空类型,内部的调用必须明确地 Null 处理

反编译一段 Null 处理后可以看到,非空类型本质上是利用 @NotNull的注解,可空类型调用前的 ? 则是手动的 null 判断。

public final int stringMapper(@NotNull String str, @NotNull Function1 mapper) { ... return ((Number)mapper.invoke(str)).intValue(); } private final void function(String bean) { if (bean != null) { boolean var3 = false; Double.parseDouble(bean); } }4.5 协程 Coroutines

介绍 Coroutines 之前,先来回顾下 Java 或 Android 如何进行线程间通信?有何痛点?

android开发技术,android开发详细教程(13)

比如:AsyncTask、Handler、HandlerThread、IntentService、RxJava、LiveData 等。它们都有复杂易错、不简洁、回调冗余的痛点。

比如一个请求网络登录的简单场景:我们需要新建线程去请求,然后将结果通过 Handler 或 RxJava 回传给主线程,其中的登录请求必须明确写在非 UI 线程中。

void login(String username, String token) { String jsonBody = "{ username: \"$username\", token: \"$token\"}"; Executors.newSingleThreadExecutor().execute(() -> { Result result; try { result = makeLoginRequest(jsonBody); } catch (IOException e) { result = new Result(e); } Result finalResult = result; new Handler(Looper.getMainLooper()).post(() -> updateUI(finalResult)); }); } Result makeLoginRequest(String jsonBody) throws IOException { URL url = new URL("https://example.com/login"); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("POST"); ... httpURLConnection.connect(); int code = httpURLConnection.getResponseCode(); if (code == 200) { // Handle input stream ... return new Result(bean); } else { return new Result(code); } }

Kotlin 的 Coroutines 则是以顺序的编码方式实现异步操作、同时不阻塞调用线程的简化并发处理的设计模式。

其具备如下的异步编程优势:

  • 挂起线程不阻塞原线程
  • 支持取消
  • 通过 KTX 扩展对 Jetpack 组件更好支持

采用协程实现异步处理的将变得清晰、简洁,同时因为指定耗时逻辑运行在工作线程的缘故,无需管理线程切换可直接更新 UI。

fun login(username: String, token: String) { val jsonBody = "{ username: \"\$username\", token: \"\$token\"}" GlobalScope.launch(Dispatchers.Main) { val result = try { makeLoginRequest(jsonBody) } catch(e: Exception) { Result(e) } updateUI(result) } } @Throws(IOException::class) suspend fun makeLoginRequest(jsonBody: String): Result { val url = URL("https://example.com/login") var result: Result withContext(Dispatchers.IO) { val httpURLConnection = url.openConnection() as HttpURLConnection httpURLConnection.run { requestMethod = "POST" ... } httpURLConnection.connect() val code = httpURLConnection.responseCode result = if (code == 200) { Result(bean) } else { Result(code) } } return result }4.6 KTX

KTX 是专门为 Android 库设计的 Kotlin 扩展程序,以提供简洁易用的 Kotlin 代码。

比如使用 SharedPreferences 写入数据的话,我们会这么编码:

void updatePref(SharedPreferences sharedPreferences, boolean value) { sharedPreferences .edit() .putBoolean("key", value) .apply(); }

引入 KTX 扩展函数之后将变得更加简洁。

fun updatePref(sharedPreferences: SharedPreferences, value: Boolean) { sharedPreferences.edit { putBoolean("key", value) }

这只是 KTX 扩展的冰山一角,还有大量好用的扩展以及 Kotlin 的优势值得大家学习和实践,比如:

  • 大大简洁语法的 let, also 等扩展函数
  • 节省内存开销的 inline 函数
  • 灵活丰富的 DSL 特性
  • 异步获取数据的 Flow 等
五、Jetpack

android开发技术,android开发详细教程(14)

Jetpack 单词的本意是火箭人,框架的 Logo 也可以看出来是个绑着火箭的 Android。Google 用它命名,含义非常明显,希望这些框架能够成为 Android 开发的助推器:助力 App 开发,体验飞速提升。

Jetpack 分为架构、UI、基础功能和特定功能等几个方面,其中架构板块是全新设计的,涵盖了 Google 花费大量精力开发的系列框架,是本章节着力讲解的方面。

架构以外的部分实际上是 AOSP 本身的一些组件进行优化之后集成到了Jetpack 体系内而已,这里不再提及。

  • 架构:全新设计,框架的核心
  • 以外:AOSP 本身组件的重新设计
    • UI
    • 基础功能
    • 特定功能

android开发技术,android开发详细教程(15)

Jetpack 具备如下的优势供我们在实现某块功能的时候收腰选择:

  • 提供 Android 平台的最佳实践
  • 消除样板代码
  • 不同版本、厂商上达到设备一致性的框架表现
  • Google 官方稳定的指导、维护和持续升级

如果对 Jetpack 的背景由来感兴趣的朋友可以看我之前写的一篇文章:「从Preference组件的更迭看Jetpack的前世今生」。下面,我们选取 Jetpack 中几个典型的框架来了解和学习下它具体的优势。

5.1 View Binding

通常的话绑定布局里的 View 实例有哪些办法?又有哪些缺点?

通常做法缺点findViewById()NPE 风险、大量的绑定代码、类型转换危险@ButterKnifeNPE 风险、额外的注解代码、不适用于多模块项目(APT 工具解析 Library 受限)KAE 插件NPE 风险、操作其他布局的风险、Kotlin 语言独占、已经废弃

AS 现在默认采用 ViewBinding 框架帮我们绑定 View。

来简单了解一下它的用法:

<!--result_profile.xml--> <LinearLayout ... > <TextView android:id="@ id/name" /> </LinearLayout>

ViewBinding 框架初始化之后,无需额外的绑定处理,即可直接操作 View 实例。

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) val binding = ResultProfileBinding.inflate(layoutInflater) setContentView(binding.root) binding.name.text = "Hello world" } }

原理比较简单:编译器将生成布局同名的绑定类文件,然后在初始化的时候将布局里的 Root View 和其他预设了 ID 的 View 实例缓存起来。事实上无论是上面的注解,插件还是这个框架,其本质上都是通过 findViewById 实现的 View 绑定,只是进行了封装。

ViewBinding 框架能改善通常做法的缺陷,但也并非完美。特殊情况下仍需使用通常做法,比如操作布局以外的系统 View 实例 ContentView,ActionBar 等。

优势局限Null 安全:预设 ID 的 View 才会被缓存,否则无法通过 ViewBinding 使用,在编译阶段就阻止了 NPE 的可能绑定布局以外的 View 仍需借助 findViewById类型安全:ViewBinding 缓存 View 实例的时候已经处理了匹配的类型依赖配置采用不同布局仍需处理 Null(比如横竖屏的布局不同)代码简洁:无需绑定的样板代码
布局专属:不混乱、布局文件为单位的专属类

5.2 Data Binding

一般来说,将数据反映到 UI 上需要经过如下步骤:

  1. 创建 UI 布局
  2. 绑定布局中 View 实例
  3. 数据逐一更新到 View 的对应属性

而 DataBinding 框架可以免去上面的步骤 2 和 3。它需要我们在步骤 1 的布局当中就声明好数据和 UI 的关系,比如文本内容的数据来源、是否可见的逻辑条件等。

<layout ...> <data> <import type="android.view.View"/> <variable name="viewModel" type="com.example.splash.ViewModel" /> </data> <LinearLayout ...> <TextView ... android:text="@{viewModel.userName}" android:visibility="@{viewModel.age >= 18 ? View.VISIBLE : View.GONE}"/> </LinearLayout> </layout>

上述 DataBinding 布局展示的是当 ViewModel 的 age 属性大于 18 岁才显示文本,而文本内容来自于 ViewModel 的 userName 属性。

val binding = ResultProfileBinding.inflate(layoutInflater) binding.viewModel = viewModel

Activity 中无需绑定和手动更新 View,像 ViewBinding 一样初始化之后指定数据来源即可,后续的 UI 展示和刷新将被自动触发。DataBinding 还有诸多妙用,大家可自行了解。

5.3 Lifecycle

监听 Activity 的生命周期并作出相应处理是 App 开发的重中之重,通常有如下两种思路。

通常思路具体缺点基础直接覆写 Activity 对应的生命周期函数繁琐、高耦合进阶利用 Application#registerLifecycleCallback 统一管理回调固定、需要区分各 Activity、逻辑侵入到 Application

而 Lifecycle 框架则可以高效管理生命周期。

使用 Lifecycle 框架需要先定义一个生命周期的观察者 LifecycleObserver,给生命周期相关处理添加上 OnLifecycleEvent 注解,并指定对应的生命状态。比如 onCreate 的时候执行初始化,onStart 的时候开始连接,onPause 的时候断开连接。

class MyLifecycleObserver( private val lifecycle: Lifecycle ) : LifecycleObserver { ... @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun init() { enabled = checkStatus() } @OnLifecycleEvent(Lifecycle.Event.ON_START) fun start() { if (enabled) { connect() } } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun stop() { if (connected) { disconnect() } } }

然后在对应的 Activity 里添加观察:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle) { ... MyLifecycleObserver(lifecycle).also { lifecycle.addObserver(it) } } }

Lifecycle 的简单例子可以看出生命周期的管理变得很清晰,同时能和 Activity 的代码解耦。

继续看上面的小例子:假使初始化操作 init() 是异步耗时操作怎么办?

init 异步的话,onStart 状态回调的时候 init 可能没有执行完毕,这时候 start 的连接处理 connect 可能被跳过。这时候 Lifecycle 提供的 State 机制就可以派上用场了。

使用很简单,在异步初始化回调的时候再次执行一下开始链接的处理,但需要加上 STARTED 的 State 条件。这样既可以保证 onStart 时跳过连接之后能手动执行连接,还能保证只有在 Activity 处于 STARTED 及以后的状态下才执行连接

class MyLifecycleObserver(...) : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun init() { checkStatus { result -> if (result) { enable() } } } fun enable() { enabled = true // 初始化完毕的时候确保只有在 STARTED 及以后的状态下执行连接 if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { if (!connected) { connect() } } } ... }5.4 Live Data

LiveData 是一种新型的可观察的数据存储框架,比如下面的使用示例,数据的封装和发射非常便捷:

class StockLiveData(symbol: String) : LiveData<BigDecimal>() { private val stockManager = StockManager(symbol) private val listener = { price: BigDecimal -> // 将请求到的数据发射出去 value = price } // 画面活动状态下才请求 override fun onActive() { stockManager.requestPriceUpdates(listener) } // 非活动状态下移除请求 override fun onInactive() { stockManager.removeUpdates(listener) } } class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { // 注册观察 StockLiveData("Tesla").run { observe(this@MainActivity, Observer { ... })} } }

支持异步传递数据以外,LiveData 还有很多优势:

  • 与 Lifecycle 框架深度绑定
  • 具有生命周期感知能力,数据不会发射给非活动状态的观察者
  • 观察者销毁了自动释放数据,避免内存泄露
  • 支持 Room 、Retrofit 框架
  • 支持合并多个数据源统一观察的 MediatorLiveData(省去多个 LiveData 多次 observe 的丑陋处理))

但必须要说 LiveData 的定位和使用有这样那样的问题,官方的态度也一直在变,了解之后多使用 Flow 来完成异步的数据提供。

5.5 Room

Android 上开发数据库有哪些痛点?

  • 需要实现 SQLite 相关的 Helper 实例并实装初始化和 CRUD 等命令
  • 自行处理异步操作
  • Cursor实例需要小心处理
    • 字段对应关系
    • index 对齐
    • 关闭

官方推出的 Room 是在 SQLite 上提供了一个抽象层,通过注解简化数据库的开发。以便在充分利用 SQLite 的强大功能的同时,能够高效地访问数据库。

android开发技术,android开发详细教程(16)

栏目热文

工厂android开发稳吗(android开发吃香吗)

工厂android开发稳吗(android开发吃香吗)

前言金九银十到来,又是一年秋招季。对于我来说已经是干Android程序员的第八个年头,经历过近几年的互联网低谷,才深知行...

2022-11-01 21:34:25查看全文 >>

android 实例教程(android 开发进阶教程)

android 实例教程(android 开发进阶教程)

前言在许多的Android App中,我们点击进入时,都可以看到一个欢迎页面,大概持续了几秒,然后跳转至主页面。以下是我...

2022-11-01 21:18:15查看全文 >>

android软件开发方法(android快速开发流程)

android软件开发方法(android快速开发流程)

每天都跟大家分享一些软件开发或者APP开发的小知识,我们总提到APP开发,那么APP究竟是怎么开发的?APP开发难不难?...

2022-11-01 20:58:16查看全文 >>

android底层开发教程(android开发学习步骤)

android底层开发教程(android开发学习步骤)

1.USB共享网络还有意义吗?企业临时办公场地的网络如何搭建?Hi,大家好,我是旋律果子,一个网络极客,也是linux开...

2022-11-01 21:37:24查看全文 >>

android开发技巧(android快速开发流程)

android开发技巧(android快速开发流程)

前言:在平时的项目开发中有时会因为需求的变更,导致会造成很多文件未能及时删除,最终在长时间的日积月累中导致很多文件都是无...

2022-11-01 21:27:54查看全文 >>

android开发实现语音播报(android语音控制app交互方法)

android开发实现语音播报(android语音控制app交互方法)

微信经过10余年的发展已经拥有了超过12亿的活跃用户,并且所包含的许多功能都与我们的日常生活息息相关,俨然已经成为了国内...

2022-11-01 21:01:13查看全文 >>

android开发实战(android开发快速入门)

android开发实战(android开发快速入门)

该课程包含:学习Kotlin基础语法,高级特性,使用Kotlin开发Android项目实战演练,学习技术不仅要知其然,还...

2022-11-01 21:06:12查看全文 >>

android零基础开发教程(android自学入门教程)

android零基础开发教程(android自学入门教程)

学习是非常艰难的为什么要这么说呢?首先,我们要来客观的看待一个状态,那就是——迷茫了,这种状态是什么?其实这种状态是比较...

2022-11-01 21:13:46查看全文 >>

win7系统怎么查询苹果笔记本型号(苹果电脑win系统怎么查型号)

win7系统怎么查询苹果笔记本型号(苹果电脑win系统怎么查型号)

我们都知道Mac电脑有各种型号,各个型号的配置都不一样。那么如何查看Mac电脑的详细配置呢?下面分享Mac电脑查看详细配...

2022-11-01 21:03:26查看全文 >>

怎么查看苹果电脑详细型号(苹果电脑怎么查型号和配置)

怎么查看苹果电脑详细型号(苹果电脑怎么查型号和配置)

北京时间10月19日,苹果召开了秋季第二场新品发布会,会中发布了全新的MacBook Pro,定位高端,是上代16英寸M...

2022-11-01 21:17:33查看全文 >>

文档排行