Service是Android中实现程序后台运行的解决方案,Service的运行不依赖任何用户界面,即使程序被切换到后台
1.Android多线程
1.1线程基本用法
定义一个线程需要创建一个类继承自Thread,然后重写父类run()方法
class MyThread : Thread(){
override fun run() {
//编写具体的逻辑
}
}
调用它的start()方法即可
MyThread().start()
当然继承方法耦合性有点高,更多地选择使用实现Runnable接口的方式来定义一个线程
class MyThread : Runnable {
override fun run(){
//编写具体的逻辑
}
}
启动方法也得到改变
val myThread = MyThread()
Thread(myThread).start()
使用Lambda的方式更为常见
Thread{
//编写具体的逻辑
}.start()
Kotlin提供了一种更加简单的开启线程方式
thread{
//编写具体的逻辑
}
这里的thread是一个Kotlin内置的顶层函数,只需要再Lambda表达式中编写具体的逻辑就可以了。
1.2在子线程中更新UI
修改activity_main.xml
<RelativeLayout 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=".MainActivity">
<Button
android:id="@+id/changeTextBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="改变内容"
></Button>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hello world"
android:textSize="20sp"
></TextView>
</RelativeLayout>
Android是不允许在子线程中进行UI操作,Android提供了一套异步处理机制。
修改MainActivity中的代码
class MainActivity : AppCompatActivity() {
val updateText=1
val handler = object : Handler(){
override fun handleMessage(msg: Message) {
//在这里可以执行UI操作
when(msg.what){
updateText ->textView.text = "Nice to meet you"
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
changeTextBtn.setOnClickListener {
thread {
val msg = Message()
msg.what = updateText
handler.sendMessage(msg)//将Message对象发送出去
}
}
}
}
首先我们定义一个整型updateText,用于表示更新TextView这个动作。新增Handler对象重写父类handleMessage()方法,对具体的Message进行处理。
创建了一个Message对象,并将what字段的值等于updateTex。调用Handler的sendMessage()方法将这条Message发送出去。Handler收到Message,并在handleMessage()方法中对它进行处理。【此时handleMessage()方法中的代码就是在主线程中运行的,所以我们可以方心的在这里进行UI操作】
异步消息处理机制流程图:
1.3使用AsyncTask
为了方便在子线程中对UI进行操作,Android提供了AsyncTask。
AsyncTask是一个抽象类,所以必须创建一个子类去继承它,继承是我们可以为AsyncTask类指定3个泛型参数:
1)Params。在执行AsyncTask时需要传入参数,可用于后台执行
2)Progress。如果需要在界面上显示当前进度,则使用在这里指定的泛型作为进度单位
3)Result.如果选哟对结果进行返回,则使用这里的泛型作为返回值类型
最简单自定义AsyncTask可以写如下形式
class DownloadTask : AsyncTask<Unint,Int,Boolean>(){
}
我们还需要重写AsyncTask中的几个方法才能。经常需要重写的方法有以下4个
1.onPreExecute()
这个方法在后台任务开始前调用,用于界面上初始化操作,比如显示一个进度条对话框
2.doInBackground( Params… )
这个方法所有代码都会在子线程中运行。任务完成可以通过return语句将任务的执行结果返回。如果AsyncTask的第三个泛型参数指定的时Unit,就可以不返回任务执行结果。如果需要更新UI元素,比如反馈当前任务的执行速度,可以调用publishProgress(Progress…)方法来完成
3.onProgressUpdate(Progress…)
当后台任务调用了publishProgress(Progress…)方法后,onProgressUpdate(Progress…)很快就会被调用,该方法携带参数时后台任务传过来的,这个方法中可以对UI进行操作
4.onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就会被调用,返回的数据会作为参数传递到此方法中。可以利用返回的数据进行一些UI操作,比如提醒任务执行结果
一个完整的自定义AsyncTask可以写成如下形式
class DownloadTask : AsyncTask<UInt, Int, Boolean>() {
override fun onPreExecute() {
progressDialog.show()//显示进度对话框
}
override fun doInBackground(vararg params: UInt?) = try{
while (true){
val downloadPercent = doDownload()//这是一个虚构的方法
publishProgress(downloadPercent)
if (downloadPercent >=100){
break
}
}
true
}catch (e : Exception){
false
}
override fun onProgressUpdate(vararg values: Int?) {
//在这里更新下载进度
progressDialog.setMessage("Downloadedd ${value[0]}%")
}
override fun onPostExecute(result: Boolean?) {
progressDialog.dissmiss()//关闭进程对话框
//在这里提示下载结果
if(result){
Toast.makeText(this,"下载完成",Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(this,"下载失败",Toast.LENGTH_SHORT).show()
}
}
}
Service基本用法
2.1定义一个Service
新建一个ServiceTest项目,然后右击com.example.servicetest->New->Service->Service
class MyService : Service() {
...
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
super.onDestroy()
}
}
这里我们重写了onCreate(),onStartCommand()和onDestroy()这3个方法
通常情况我们希望Service一旦启动就去执行某个动作,应该将逻辑写入onStartCommand()方法中。
注:每个Service都需要在AndroidManifest.xml文件中进行注册才能生效,Android Stuido自动帮我们注册了
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
...
</manifest>
2.2启动和停止Service
修改activity_main.xml
<LinearLayout 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=".MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/startServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启动Service"
></Button>
<Button
android:id="@+id/stopServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="关闭Service"
></Button>
</LinearLayout>
修改MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startServiceBtn.setOnClickListener {
val intent =Intent(this,MyService::class.java)
startService(intent)//启动Service
}
stopServiceBtn.setOnClickListener {
val intent =Intent(this,MyService::class.java)
stopService(intent)//停止Service
}
}
}
从Android8.0开始,应用后台功能被削弱。现在只有当应用保持前台可见状态情况下,service才能保证稳定运行。一旦应用进入后台,service随时可能被系统回收。
如果需要长期在后台执行一些任务,可以使用前台Service或者WorkManager
2.3Activity和Service通信
修改MyService中的代码
class MyService : Service() {
private val mBinder = DownloadBinder()
class DownloadBinder : Binder(){
fun startDownload(){
Log.d("MyService","startDownload executed")
}
fun getProgress() : Int{
Log.d("MyService","getProgress executed")
return 0
}
}
...
override fun onBind(intent: Intent): IBinder {
return mBinder
}
}
添加代码到activity_main.xml
...
<Button
android:id="@+id/bindServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="绑定Service"
></Button>
<Button
android:id="@+id/unbindServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="解绑Service"
></Button>
...
添加MainActivity中的代码
class MainActivity : AppCompatActivity() {
lateinit var downloadBinder: MyService.DownloadBinder
private val connection = object : ServiceConnection{
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
downloadBinder = service as MyService.DownloadBinder
downloadBinder.startDownload()
downloadBinder.getProgress()
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
bindServiceBtn.setOnClickListener {
val intent =Intent(this,MyService::class.java)
bindService(intent,connection,Context.BIND_AUTO_CREATE)
}
unbindServiceBtn.setOnClickListener {
unbindService(connection)
}
}
}
首先创建了一个ServiceConnect的匿名类实现,并在里面重写了onServiceConnected()方法和onServiceDisconnected()方法。onServiceConnected()方法会在Activity和Service成功绑定的时候调用,而onServiceDisconnected()方法会在Service的创建进程崩溃或者被杀掉的时候才会调用,不太常用。
bindService()方法接收3个参数,第一个是刚刚创建出来的Intent对象,第二个是前面创建出ServiceConnection实例,第三个是一个标志位,传入BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建Service。这会使MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
2.4前台SErvice
前台Service和普通Service最大区别是,它一直会有一个正在运行的图标在系统的状态栏显示。
修改MyService中的代码如下
class MyService : Service() {
...
override fun onCreate() {
super.onCreate()
Log.d("MyService","创建成功")
//前台Service
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.O){
val channel = NotificationChannel("my_service","前台Service通知",NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
val intent =Intent(this,MainActivity::class.java)
val pi=PendingIntent.getActivity(this,0,intent,0)
val notification = NotificationCompat.Builder(this,"my_service")
.setContentTitle("这是ContentTitle")
.setContentText("这是ContentText")
.setContentIntent(pi)
.build()
startForeground(1,notification)
}
...
这次修改了onCreate()方法,代码和第九章创建通知方法相似,只不过这次构建Notification对象后并没有使用NotificationManager将通知显示出来,而是调用startForeground()方法。第一个参数是通知的id,第二个参数是构建Notification对象。
从android9.0系统开始,使用前台Service必须在AndroidManifest.xml中进行权限声明才行
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicetest">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"></uses-permission>
</manifest>
2.5使用IntentService
如果直接在Service中处理一些耗时的逻辑,就很容易出现ANR(Application No Responding)的情况
所以这个时候就需要用到Android的多线程技术了,一个比较标准的Service就可以写成如下形式
class MyService : Service() {
...
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("MyService","启动成功")
thread{
//处理具体逻辑
stopSelf()
}
return super.onStartCommand(intent, flags, startId)
}
}
这种Service一旦启动,就会一直处于运行状态,必须调用stopService()或stopSelf()方法,或者被系统回收,Service才会停止
Android提供了一个IntentService类,这个类能解决忘记开线程,忘记调用stopSelf()方法
class MyIntentService : IntentService("MyIntentService") {
override fun onHandleIntent(intent: Intent?) {
//打印当前线程的id
Log.d("MyIntentService","Thread id is ${Thread.currentThread().name}")
}
override fun onDestory(){
super.onDestory()
Log.d("MyIntentService","onDestory executed")
}
}
首先传入一个字符串,字符串可以随意指定,只有调试的时候有用。在子类中实现onHandleIntent()这个抽象方法,这个方法可以处理一些耗时的逻辑,不用担心ANR的问题,因为这个方法已经实在子线程中运行的了。
Kotlin泛型的高级特性
3.1对泛型进行实化
3.1对泛型进行实化
kotlin允许将内联函数中的泛型进行实体化
inline fun <reified T> getGenericType() {
}
上述函数中的泛型T就是被实化的泛型,它满足了内联函数和reified关键字和这两个前提条件。
借助泛型实化,可以实现什么样的效果:
inline fun <reified T> getGenericType() = T::class.java
getGenericType()函数直接返回了当前指定函数的实际类型.T::class这种语法在java中是不合法的,而kotlin借助泛型实化就可以使用T::class.java这样的语法。
3.2泛型实化的应用
启动一个Activity可以这样写
val intent = Intent(context,TestActivity::class.java)
context.startActivity(intent)
我们还可以优化,新建reified.kt文件,内编写代码
inline fun <reified T> startActivity(context : Context){
val intent = Intent(Context.T::class.java)
context.startActivity(intent)
}
如果我们想启动TestActivity,可以这样写
startActivity<Testctivity>(context)
因为通常在启动Activity的时候可能会使用Intent附带的一些参数,我们可以修改reified.kt文件,添加一个新的startActivity()函数重载
inline fun <reified T> startActivity(context : Context,block : Intent.() -> Unit){
val intent = Intent(Context.T::class.java)
intent.block()
context.startActivity(intent)
}
调用startActivity()函数的时候就可以在Lambda表达式中为Intent传递参数
startActivity<TestActivity>(context){
putExtra("param1","data1")
putExtra("param2","data2")
}
3.3泛型的协变
假如定义了一个MyClass< T>的泛型类,其中A是B的子类型,同时MyClass< A>又是MyClass< B>的子类型,那么我们就可以成MyClass在T这个泛型上是协变的。
如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患。需要让MyClass< T>类中所有的方法都不能接收T类型的参数,T只能出现在out位置上
class SimpleData<out T>(val data:T?){
fun get():T?{
return data
}
}
由于泛型T不能出现在in位置上,因此我们不能使用set()方法为data参数赋值,所以这里改成构造方式来赋值。这里我们使用了val关键字,所以构造函数中的泛型T仍是只读的,因此这样的写是安全且合法的。
在泛型T前面加一个@UnsafeVariance注解
3.4泛型的逆变
定义一个Transformer接口用来执行一些转换操作
interface Transformer<in T> {
fun transform(t : T) : String
}
泛型T的声明前面加一个in关键字。这就意味现在T只能出现在in位置上,而不能出现out位置上。同时页意味着Transformer在泛型T上是逆变的。