当前位置: 首页 > news >正文

【Android入门】8、Service 后台线程、多线程、IntentService

文章目录

  • 十、Service 后台线程
    • 10.1 Android 多线程编程
      • 10.1.1 异步消息处理机制
      • 10.1.2 AsyncTask
    • 10.2 Service 用法
      • 10.2.1 启停 Service
      • 10.2.2 Activity 和 Service 通信
      • 10.2.3 前台 Service
      • 10.2.4 IntentService

十、Service 后台线程

10.1 Android 多线程编程

比较笨重的方法如下

class MyThread : Runnable {
 override fun run() {
 // 编写具体的逻辑
 }
}
// Thread的构造函数接收一个Runnable参数,而我们创建的MyThread实例正是一个实现了Runnable接口的对象,所以可以直接将它传入Thread的构造函数里。
val myThread = MyThread()
// 接着调用Thread的start()方法,run()方法中的代码就会在子线程当中运行了
Thread(myThread).start()

也可以如下用lambda表达式的代码

Thread {
 // 编写具体的逻辑
}.start()

新建一个 AndroidThreadTest 项目, 布局如下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
	<Button
	android:id="@+id/changeTextBtn"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:text="Change Text" />
	<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" />
</RelativeLayout>

业务代码如下

class MainActivity : AppCompatActivity() {
    // 定义了一个整型变量updateText,用于表示更新TextView这个动作
    val updateText = 1
    // 新增一个Handler对象,并重写父类的handleMessage()方法,在这里对具体的Message进行处理
    val handler = object : Handler(Looper.getMaininLooper()) {
        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对象发送出去
            }
        }
    }
}

10.1.1 异步消息处理机制

要由4个部分组成:Message、Handler、MessageQueue和 Looper

  • Message: 在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据。如Message的what字段,除此之外还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段携带一个Object对象
  • Handler: 处理者, 它主要是用于发送和处理消息的。发送消息一般
    是使用Handler的sendMessage()方法、post()方法等,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()方法中
  • MessageQueue: 消息队列,用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列待被处理。每个线程中只会有一个MessageQueue对象
  • Looper: 每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有一个Looper对象

流程如下

  • 首先需要在主线程当中创建一个Handler对象,并重写
    handleMessage()方法。
  • 然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。
  • 之后这条消息会被添加到MessageQueue的队列中等待被处理
  • Looper则会一直尝试从MessageQueue中取出待处理消息
  • 最后分发回Handler的handleMessage()方法中。
  • 由于Handler的构造函数中我们传入了Looper.getMainLooper(),所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。
    在这里插入图片描述

10.1.2 AsyncTask

AsyncTask是比Message更好用的机制, 更方便的从子线程切换到主线程, 是Android官方通过封装Message机制的更好的API
其三个泛型参数如下

  • Params。在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
  • Progress。在后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
  • Result。当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
// 这里我们把AsyncTask的
// 第一个泛型参数指定为Unit,表示在执行AsyncTask的时候不需要传入参数给后台任务。
// 第二个泛型参数指定为Int,表示使用整型数据来作为进度显示单位。
// 第三个泛型参数指定为Boolean,则表示使用布尔型数据来反馈执行结果
class DownloadTask : AsyncTask<Unit, Int, Boolean>() {
	...
}

通常需要重写其4个函数, 实现业务功能

  • onPreExecute(): 会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
  • doInBackground(Params…): 会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成,就可以通过return语句将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Unit,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress (Progress…)方法来完成
  • onProgressUpdate(Progress…): 当在后台任务中调用了publishProgress(Progress…)方法后,此方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
  • onPostExecute(Result): 当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据进行一些UI操作,比如说提醒任务执行的结果,以及关闭进度条对话框等。

通过DownloadTask().execute()即可调用, 若向execute方法传参, 则这些参数会传给doInBackground()方法
其定义如下

class DownloadTask : AsyncTask<Unit, Int, Boolean>() {
    override fun onPreExecute() {
        progressDialog.show() // 显示进度对话框
    }
    override fun doInBackground(vararg params: Unit?) = 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("Downloaded ${values[0]}%")
    }
    override fun onPostExecute(result: Boolean) {
        progressDialog.dismiss()// 关闭进度对话框
        // 在这里提示下载结果
        if (result) {
            Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT).show()
        }
    }
}

10.2 Service 用法

AndroidStudio新建一个Service, 其会自动在manifest.xml中添加service的注册, 我们需重写onCreate, onStartCommand, onDestroy三个方法

class MyService : Service() {
    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the 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()
    }
}

10.2.1 启停 Service

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
        }
    }
}
class MyService : Service() {
    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }
    override fun onCreate() {
        super.onCreate()
        Log.d("MyService", "onCreate executed")
    }
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Log.d("MyService", "onStartCommand executed")
        return super.onStartCommand(intent, flags, startId)
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyService", "onDestroy executed")
    }
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/startServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Service" />
    <Button
        android:id="@+id/stopServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop Service" />
</LinearLayout>

为了防止手机变卡顿, Service随时都会都会被系统回收, 若真的需要后台执行, 可用前台ServiceWorkManager.

10.2.2 Activity 和 Service 通信

通过onBind()方法, 例如希望在MyService里提供一个下载功能,然后在Activity中可以决定何时开始下载,以及随时查看下载进度

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和Service绑定了之后,就可以调用该Service里的Binder提供的方法了

10.2.3 前台 Service

class MyService : Service() {
    ...
    override fun onCreate() {
        super.onCreate()
        Log.d("MyService", "onCreate executed")
        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("This is content title")
            .setContentText("This is content text")
            .setSmallIcon(R.drawable.small_icon)
            .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.large_icon))
            .setContentIntent(pi)
            .build()
        startForeground(1, notification)
    }
    ...
}

10.2.4 IntentService

特点: 开启线程和自动停止

  • Service中的代码都是默认运行在主线程当中的,如果直接在Service里处理一些耗时的逻辑,就很容易出现ANR(Application NotResponding)的情况。
  • 所以这个时候就需要用到Android多线程编程的技术了,我们应该在Service的每个具体的方法里开启一个子线程,然后在这里处理那些耗时的逻辑。
  • 并且根据IntentService的特性,这个Service在运行结束后应该是会自动停止的
class MyIntentService : IntentService("MyIntentService") {
	override fun onHandleIntent(intent: Intent?) {
		// 打印当前线程的id
		Log.d("MyIntentService", "Thread id is ${Thread.currentThread().name}")
	}
	override fun onDestroy() {
		super.onDestroy()
		Log.d("MyIntentService", "onDestroy executed")
	}
}
class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        startIntentServiceBtn.setOnClickListener {
            // 打印主线程的id
            Log.d("MainActivity", "Thread id is ${Thread.currentThread().name}")
            val intent = Intent(this, MyIntentService::class.java)
            startService(intent)
        }
    }
}

相关文章:

  • 面向医学图像语义分割-MedISeg
  • puzzle(017.9)HueBots
  • SIM卡被锁怎么办
  • 腾讯云服务器有那么多的型号标准型,计算型,内存型等等,到底该如何选择?
  • 【Android入门】6、ContentProvider:跨程序的数据共享:访问其他 App、被其他 App 访问
  • 文献学习(part102-A)--Autoencoders
  • SS-Model【6】:U2-Net
  • 创新战略|工业企业如何应对颠覆式变革带来的挑战?
  • HashMap不安全后果及ConcurrentHashMap线程安全原理
  • 22_access 阶段
  • 如何位图转换矢量图或者数字油画底稿
  • 阿里巴巴面试题- - -多线程并发篇(三十八)
  • python java php一起考研资料文件下载系统 微信小程序
  • 超好理解的子网和超网
  • Linux常用命令练习
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • [译]CSS 居中(Center)方法大合集
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • Bytom交易说明(账户管理模式)
  • ES6核心特性
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • Hibernate最全面试题
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • Java方法详解
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • mongodb--安装和初步使用教程
  • ng6--错误信息小结(持续更新)
  • Python 反序列化安全问题(二)
  • vue--为什么data属性必须是一个函数
  • XForms - 更强大的Form
  • 从输入URL到页面加载发生了什么
  • 给Prometheus造假数据的方法
  • 爬虫模拟登陆 SegmentFault
  • 前端知识点整理(待续)
  • 全栈开发——Linux
  • 树莓派 - 使用须知
  • 跳前端坑前,先看看这个!!
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • #QT(TCP网络编程-服务端)
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • $refs 、$nextTic、动态组件、name的使用
  • (ZT)薛涌:谈贫说富
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (六)Hibernate的二级缓存
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (四)c52学习之旅-流水LED灯
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)