【第一行代码学习笔记】第十章 后台:Service

第10章:Service


10.1 Service的概念

Service用来使程序在后台运行。它依赖于创建Service时所在的应用程序进程。当某个应用进程被杀掉时,所有依赖于该进程的Service都会停止。

所有代码默认运行在主线程中,Service不会自动开启线程。我们需要在Service内部手动创建子线程,并在这里执行具体任务。

10.2 Android多线程编程

Java多线程编程中,当执行一些耗时操作时,如果不将其放在子线程里运行,就会导致主线程被阻塞(例如发起一条网络请求,而网速过慢时)。

10.2.1 线程的基本用法

有这么几种和java离类似的语法。

/* 第一种:继承自Thread类 */
class MyThread: Thread {
    override fun run() {
        // 编写具体逻辑
    }
}
// 启动线程
MyThread.start() 

/* 第二种:实现Runnable接口 */
class MyThread: Runnable {
    override fun run() {
    // 编写具体逻辑
    }
}
// 启动线程
val myThread = MyThread()
Thread(myThread).start()

/* 第三种:使用Lambda */
Thread {
    // 编写具体逻辑
}.start()

Kotlin提供了一种很简单的开启线程的方式:

thread {
    // 编写具体逻辑
}

10.2.2 在子线程中更新UI

Android的UI是线程不安全的。如果要更新UI元素,必须在主线程中更新。如果直接在子线程中更新UI,则会直接崩溃。这时就需要异步消息处理机制了!

线程不安全:在没有提供加锁机制保护的情况下,多个线程对共享资源进行写操作,可能导致数据不一致或脏数据的问题。如果多个线程同时修改同一份资源,可能会出现竞态条件,导致程序功能不能正确完成。

class MainActivity: AppCompatActivity() {

    lateinit var binding : ActivityMainBinding
    val updateText = 1 // 用于表示TextView这个动作

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.changeTextBtn.setOnClickListener {
            thread {
                val msg = Message()
                msg.what = updateText
                handle.sendMessage(msg) // 发出消息
            }
        }
    }

    val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) { // 接收到消息后做什么
            // 进行UI操作
            when (msg.what) {
                binding.updateText -> textView.text = "Nice to meet you"
            }
        }
    }

}

10.2.3 异步消息处理机制的原理

  1. Message:
    Message是在线程之间传递的消息,可以携带少量信息。可以使用Message的what字段;使用arg1字段和arg2字段携带整形数据;使用obj字段携带一个Object对象。

  2. Handler:
    主要用于发送和处理消息的。发送是Handler.sendMessage();发出的消息最后会传递到Handler的handleMessage()方法中

  3. MessageQueue:
    消息队列,存放所有通过Handler发送的消息。每个线程中只会有一个MessageQueue对象。

  4. Looper:
    调用Looper的loop()方法后会进入一个循环,每当发现MessageQueue中存在一条消息就会将其取出,并传递到handleMessage()方法中。每个线程中只会有一个Looper对象。

10.2.3 AsyncTask

AsncTask有三个泛型参数:

  • Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用;
  • Progress:后台任务执行时,如果需要在界面上显示当前进度,则使用这里指定的类型作为进度单位;
  • Result:后台任务执行完毕后如果需要返回结果,则指定这里的泛型作为返回值类型;

经常需要重写的方法有一下四个:

  • onPreExecyte():后台任务开始执行前调用,用于初始化。例如显示一个进度条对话框等;
  • doInBackground(Params...):此方法中所有代码都会在子线程中运行,应该在这里进行耗时任务。但不能进行UI更新。更新UI需要调用publishProgress(Progress...)方法。
  • onProgressUpdate(Progress...):调用publishProgress(Progress...)方法后,onProgressUpdate()会很快被调用。该方法中携带的参数就是后台任务中传递过来的。在此方法中可以对UI进行更新。
  • onPostExecute(Result):当后台任务执行完毕并通过return语句进行返回时,这个方法很快会被调用。返回的数据会作为参数传递到此方法中,可以进行一些UI操作。

简单来说就是:在doInBackground(Params...)中执行具体的耗时任务;在onProgressUpdate(Progress...)中进行UI操作;在onPostExecute(Result)中执行一些任务的收尾工作。

想要启动任务,只需调用.execute()方法即可。

10.3 Service的基本用法

10.3.1 定义Service

class MyService : Service() {
    // 用Activity控制Service
    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }
    // 第一次创建Service时执行
    override fun onCreate() {
        super.onCreate()
    }
    // 每次运行Service时执行
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }
    // 关闭Service时执行
    override fun onDestroy() {
        super.onDestroy()
    }
}

10.3.2 启动和停止Service

通过startService()stopService()方法传入intent来启动和停止Service:

    binding.startServiceBtn.setOnClickListener {
        val intent = Intent(this,  MyService::class.java)
        startService(intent)
    }
    binding.stopServiceBtn.setOnClickListener {
        val intent = Intent(this,  MyService::class.java)
        stopService(intent)
    }

10.3.3 Activity和Service通信

借助onBind()方法可以通过Activity来指挥Service。
修改MyService.kt中的代码,添加一个下载功能:

    class MyService : Service() {

        private val mBinder = DownloadBinder()

        class DownloadBinder: Binder() {
            // 开始下载
            fun startDownload() {
                Log.d("MainActivity", "StartDownload")
            }
            // 查看下载进度
            fun getProcess(): Int {
                Log.d("MainActivity", "getProcess executed.")
                return 0
            }
        }

        override fun onBind(intent: Intent): IBinder {
            return mBinder
        }
}

接着在MainActivity中分别设置两个将Activity与Service绑定/解绑的按钮:

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.getProcess()
        }
        override fun onServiceDisconnected(name: ComponentName?) {
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        binding.startServiceBtn.setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            bindService(intent, connection, Context.BIND_AUTO_CREATE) // 绑定Service
        }
        binding.stopServiceBtn.setOnClickListener {
            unbindService(connection) // 解绑Service
        }
    }
}

其中bindService(intent, connection, flags)方法接收三个参数。第三个参数是标志位,Context.BIND_AUTO_CREATE表示在Activity和Service绑定后自动创建Service,即执行MyService中的onCreate。

10.4 Service的生命周期

在任何位置调用Context的startService()方法,就会调用onStartCommand()方法。若这个Service还未被创建过,那么会先执行onCreate()方法。

每个Service只会存在一个实例,因此无论调用多少次startService(),只需调用一次stopService()或者stopSelf(),Service就会停止。

另外,还可以调用Context的bindService()来获取一个Service的持久连接,这时会回调Service中的onBind()方法。若这个Service还未被创建过,那么会先执行onCreate()方法。

调用stopService()unBindService()都会执行onDestroy()。如果既调用了startService()方法,又调用了bindService(),那么根据Android机制,一个Service只要被启动或者被绑定了之后都会处于运行状态。因此此时想要结束Service,就要同时调用stopService()unBindService()方法。

10.5 Service的更多技巧

10.5.1 前台Service

普通Servic随时可能会被系统回收。如果希望Service保持2运行,就可以考虑使用前台Service。前台Service和普通Service的最大区别就是,它会一直有一个正在运行的图标在系统的状态栏显示。

创建一个前台Service:

    override fun onCreate() {
        super.onCreate()

        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        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, PendingIntent.FLAG_IMMUTABLE)
        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(114514, notification) // startForeground(id, Notification); 调用此方法使MyService成为前台Service
    }

同时需要在AndroidManifest.xml中声明相关权限:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

书上只声明了第一行,这是使用前台Service的权限。但是如果不声明第二行的通知权限,应用就无法发布通知。因此必须添加这行代码之后,再在手机设置中讲ServiceTest的通知权限打开(如果不声明,设置中该应用的通知权限无法更改)。

10.5.2 使用IntentService

注意:该方法现已被废弃。最好使用WorkManager

前面说过Service中如果处理一些耗时逻辑,很容易导致ANR (Application Not Responding) 的情况。此时就需要用到多线程编程的技术,开启一个子线程:

class MyService : Service() {
    ...
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        thread {
            // 处理具体逻辑
        }
        return super.onStartCommand(intent, flags, startId)
    }
}

如果想要实现让Service在执行完毕后自动停止,应该在thread里加一个stopSelf()方法:

class MyService : Service() {
    ...
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        thread {
            // 处理具体逻辑
            stopSelf()
        }
        return super.onStartCommand(intent, flags, startId)
    }
}

但是总有人会忘记开启子线程,或者忘记调用stopSelf()方法。怎么办呢?

Android专门提供了一个IntentService类:

class MyIntentService : IntentService("MyintentService") { // 这个字符串可以随意制定,只在调试时有用
    override fun onHandleIntent(intent: Intent?) {
        Log.d("MyIntentService", "Thread id is ${Thread.currentThread().name}")
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyIntentService", "onDestroy")
    }
}

这样就可以集开启线程和自动停止于一身。

  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
LINUX设备驱动第三版_ 前言 第一章 设备驱动程序简介 设备驱动程序的作用 内核功能划分 设备和模块的分类 安全问题 版本编号 许可证条款 加入内核开发社团 本书概要 第二章 构造和运行模块 设置测试系统 Hello World模块 核心模块与应用程序的对比 编译和装载 内核符号表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 第三章 字符设备驱动程序 scull的设计 主设备号和次设备号 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 第四章 调试技术 内核中的调试支持 通过打印调试 通过查询调试 通过监视调试 调试系统故障 调试器和相关工具 第五章 并发和竞态 scull的缺陷 并发及其管理 信号量和互斥体 completion 自旋锁 锁陷阱 除了锁之外的办法 快速参考 第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet 工作队列 快速参考 第八章 分配内存 kmalloc函数的内幕 后备高速缓存 get_free_page和相关函数 vmalloc及其辅助函数 per-CPU变量 获取大的缓冲区 快速参考 第九章 与硬件通信 I/O端口和I/O内存 使用I/O端口 I/O端口示例 使用I/O内存 快速参考 第十章 中断处理 准备并口 安装中断处理例程 实现中断处理例程 顶半部和底半部 中断共享 中断驱动的I/O 快速参考 第十一章 内核的数据类型 使用标准C语言类型 为数据项分配确定的空间大小 接口特定的类型 其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 SBus NuBus 外部总线 快速参考 第十三章 USB驱动程序 USB设备基础 USB和Sysfs USB urb 编写USB驱动程序 不使用urb的USB传输 快速参考 第十四章 Linux设备模型 kobject、kset和子系统 低层sysfs操作 热插拔事件的产生 总线、设备和驱动程序 类 各环节的整合 热插拔 处理固件 快速索引 第十五章 内存映射和DMA Linux的内存管理 mmap设备操作 执行直接I/O访问 直接内存访问 快速参考 第十六章 块设备驱动程序 注册 块设备操作 请求处理 其他一些细节 快速参考 第十七章 网络驱动程序 snull设计 连接到内核 net_device结构细节 打开和关闭 数据包传输 数据包的接收 中断处理例程 不使用接收中断 链路状态的改变 套接字缓冲区 MAC 地址解析 定制 ioctl 命令 统计信息 组播 其他知识点详解 快速参考 第十八章 TTY驱动程序 小型TTY驱动程序 tty_driver函数指针 TTY线路设置 ioctls proc和sysfs对TTY设备的处理 tty_driver结构详解 tty_operations结构详解 tty_struct结构详解 快速参考 参考书目 9112405-1_o.jpg (85.53 KB, 下载次数: 50)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值