探究Service
Service并不是运行在一个独立的进程当中的,而是依赖于创建Service时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行。
另外,也不要被Service的后台概念所迷惑,实际上Service并不会自动开启线程,所有的代码都是默认运行在主线程当中的。
我们需要在Service的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。
多线程编程
基本用法
- 继承Thread,然后重写父类的run()方法,并调用start()方法启动;
- 使用Runnable接口,并调用start方法;
- thread顶层函数(Kotlin),在里面写Lambda即可。
更新UI
Android的UI是线程不安全的。要更新应用程序里的UI元素,必须在主线程中进行,否则就会出现异常,因此常常使用异步消息处理的方式来更新UI元素。
Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。
- Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据。
- Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。
- MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。
- Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到**Handler的handleMessage()**方法中。
异步消息处理流程:
- 首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。
- 然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。
- 之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage()方法中。
- 由于Handler在主线程中创建,所以handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。
安卓也使用AsyncTask工具来在子线程中进行UI操作,其基本使用步骤如下:
- 继承AsyncTask类,指定三个泛型参数(传入参数、进度单位、结果返回);
- 重写AsyncTask的方法,如onPreExecute(),doInBackground(),onProgressUpdate(),onPostExecute();
- 调用execute()启动任务。
1 | class DownloadTask : AsyncTask<Unit, Int, Boolean>() { |
Service
创建
- 继承Service类;
- 按功能重写生命周期回调函数;
- 在AndroidManifest.xml文件中注册。
启动和停止
启动和停止借助Intent实现。
现在只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。
如果你真的非常需要长期在后台执行一些任务,可以使用前台Service或者WorkManager。
区别:onCreate()方法是在Service第一次创建的时候调用的,而onStartCommand()方法则在每次启动Service的时候都会调用。
和Activity通信
使用binder类实现通信。
- 在service中创建binder类,根据自己的需要写方法,并实例化;
- 在service的onBinder函数中返回自己的实例化binder;
- 在Activity中借助Intent,使用bindService()绑定Service;
- 使用unbindService()可以解绑。
生命周期
根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁。所以,这种情况下要同时调用stopService()和 unbindService()方法,onDestroy()方法才会执行。
前台Service
前台Service和普通Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。
前台Service的创建很大程度上和通知的创建类似,唯一不同是调用startForeground()进行通知显示。此外,前台Service需要进行权限声明。
IntentService
本质是简化Android多线程编程,提供的一个异步的、会自动停止的Service。
- 这里首先要求必须先调用父类的构造函数,并传入一个字符串,这个字符串可以随意指定,只在调试的时候有用;
- 实现onHandleIntent方法,处理耗时逻辑;
- 可以重写了onDestroy()方法,进行结尾工作。
[!CAUTION]
以下内容晦涩难懂,出于自己单方面理解的内容居多,不保证下面的理解是正确的。
如果你发现不正确的地方,可以在评论区指出。
Kotlin课堂
泛型的高级特性
主要学习泛型的实化、协变和逆变。
弄懂泛型之前,首先区别一件事情,一般的类型转化。
在进行非泛型的赋值时,子类对象可以赋值给父类对象的,但是父类对象不能赋值给子类对象。其实原因很好懂,子类包含比父类更多的信息和功能,子类赋值给父类无非就是将多余的信息和功能给禁用掉;相反,父类赋值给子类,则需要增添对于的信息和功能才行,很明显这是不可能的。同时,父类转向子类或者不同子类之间相互转换也是不被允许的(强制转换暂时不讨论)。
而在泛型类型中,Kotlin 不允许类型的替换或转换,因为泛型的类型在编译时已经是固定的,不会发生类型推断,因此不加特殊语法,不能实现协变和逆变的功能。
实化
Java的泛型擦除机制:即对于泛型的约束只在编译时期存在,运行的时候不进行约束(这是兼容性导致的)。所有基于JVM的语言,它们的泛型功能都是通过类型擦除机制来实现的,包括Kotlin。
简而言之,不允许将子类的泛型对象赋值给父类的泛型类型声明。
类型擦除就是在编译时期将泛型位置替换为泛型上界,如果没有设置泛型上界就替换为object。
由于Kotlin有内联函数,因此可以稍微改善泛型擦除机制的问题。使用reified关键字可以将泛型实化,通俗来讲就是将泛型类型使用传入的类型进行替换,更加偏重于内联的功能。
1 | // 以下写法就是合理的 |
协变
协变通俗简短来讲就是允许子类对象给父类的泛型类型声明,同时设置为可读。
错误的根本原因:泛型不变性
1 | class SimpleData<T> { |
关键字是out。
逆变
协变通俗简短来讲就是允许父类对象给子类的泛型类型声明,同时设置为可写。
错误的根本原因:泛型不变性
1 | // 例子1: |
关键字是in。