移动开发笔记(十二)多线程 Service kotlin:泛型的高级特征

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



    
    
    

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操作】
异步消息处理机制流程图:
移动开发笔记(十二)多线程 Service kotlin:泛型的高级特征_第1张图片
1.3使用AsyncTask
为了方便在子线程中对UI进行操作,Android提供了AsyncTask。
AsyncTask是一个抽象类,所以必须创建一个子类去继承它,继承是我们可以为AsyncTask类指定3个泛型参数:
1)Params。在执行AsyncTask时需要传入参数,可用于后台执行
2)Progress。如果需要在界面上显示当前进度,则使用在这里指定的泛型作为进度单位
3)Result.如果选哟对结果进行返回,则使用这里的泛型作为返回值类型
最简单自定义AsyncTask可以写如下形式

class DownloadTask : AsyncTask(){
}

我们还需要重写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() {

    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自动帮我们注册了



    
        

       ...


2.2启动和停止Service
修改activity_main.xml



   
    


修改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

...
	
	    
...

添加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中进行权限声明才行


    

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  getGenericType() {

}

上述函数中的泛型T就是被实化的泛型,它满足了内联函数和reified关键字和这两个前提条件。
借助泛型实化,可以实现什么样的效果:

inline fun  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  startActivity(context : Context){
val intent = Intent(Context.T::class.java)
context.startActivity(intent)
}

如果我们想启动TestActivity,可以这样写

startActivity(context)

因为通常在启动Activity的时候可能会使用Intent附带的一些参数,我们可以修改reified.kt文件,添加一个新的startActivity()函数重载

inline fun  startActivity(context : Context,block : Intent.() -> Unit){
	val intent = Intent(Context.T::class.java)
	intent.block()
	context.startActivity(intent)
}

调用startActivity()函数的时候就可以在Lambda表达式中为Intent传递参数

startActivity(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(val data:T?){
	fun get():T?{
	return data		
}	
}

由于泛型T不能出现在in位置上,因此我们不能使用set()方法为data参数赋值,所以这里改成构造方式来赋值。这里我们使用了val关键字,所以构造函数中的泛型T仍是只读的,因此这样的写是安全且合法的。
在泛型T前面加一个@UnsafeVariance注解
3.4泛型的逆变
定义一个Transformer接口用来执行一些转换操作

interface Transformer {
	fun transform(t : T) : String
}

泛型T的声明前面加一个in关键字。这就意味现在T只能出现在in位置上,而不能出现out位置上。同时页意味着Transformer在泛型T上是逆变的。

你可能感兴趣的:(移动开发笔记(十二)多线程 Service kotlin:泛型的高级特征)