IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 从零开始学安卓(kotlin)八——线程、Service -> 正文阅读

[移动开发]从零开始学安卓(kotlin)八——线程、Service

Service

Service是Android中实现程序后台运行的解决方案,它非常适合执行那些不需要和用户交互而且还要求长期运行的任务。
Service并不是运行在一个独立的进程当中的,而是依赖于创建Service时 所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运 行。

Android多线程编程

线程的基本用法

使用继承的方式耦合性有点高,我们会更多地选择使用实现Runnable接口的方式来定义 一个线程

class MyThread : Runnable {
    override fun run() {
		// 编写具体的逻辑 
	}
}

启动线程的方法

val myThread = MyThread()
Thread(myThread).start()

如果你不想专门再定义一个类去实现Runnable接口,也可以使用Lambda的方式,这种写法更为常见

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

开启线程的方式(推荐)

Kotlin还给我们提供了一种更加简单的开启线程的方式
这里的thread是一个Kotlin内置的顶层函数,我们只需要在Lambda表达式中编写具体的逻辑 就可以了,连start()方法都不用调用,thread函数在内部帮我们全部都处理好了。

thread {
	// 编写具体的逻辑
}

在子线程中更新UI

Android确实是不允许在子线程中进行UI操作的。但是有些时候,我们必须在子线程里执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件,对于这种情况,Android提供了一套异步消息处理机制,完美地解决了在子线程中进行UI操作的问题。Android异步消息处理的基本用法,使用这种机制就可以出色地解决在子线程中更新UI的问题。

val updateText = 1
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对象发送出去
		} 
	}
}

解析异步消息处理机制

Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和 Looper。

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

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

AsyncTask(推荐)

首先来看一下AsyncTask的基本用法。由于AsyncTask是一个抽象类,所以如果我们想使用 它,就必须创建一个子类去继承它。在继承时我们可以为AsyncTask类指定3个泛型参数,这3 个参数的用途如下。
Params。在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
Progress。在后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛 型作为进度单位。
Result。当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
需要重写AsyncTask中的几个方法才能完成对任务的定制。经常需要重写的方法有以下4个。

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

简单来说,使用AsyncTask的诀窍就是,
在doInBackground()方法中执行具体的耗时任务,
在onProgressUpdate()方法中进行UI操作,
在onPostExecute()方法中执行一些任务的收尾工作。

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()
        }
	} 
}

启动这个任务

DownloadTask().execute()

可以给execute()方法传入任意数量的参数,这些参数将会传递到DownloadTask 的doInBackground()方法当中。只需要调用一下 publishProgress()方法,就可以轻松地从子线程切换到UI线程了。

Service的基本用法

后台Service

onBind()方法是Service中唯一的抽象方法,所以必须在子类里实现。
重写了onCreate()、onStartCommand()和onDestroy()这3个方 法,它们是每个Service中最常用到的3个方法了。其中onCreate()方法会在Service创建的 时候调用,onStartCommand()方法会在每次Service启动的时候调用,onDestroy()方法 会在Service销毁的时候调用。
通常情况下,如果我们希望Service一旦启动就立刻去执行某个动作,就可以将逻辑写在 onStartCommand()方法里。而当Service销毁时,我们又应该在onDestroy()方法中回收 那些不再使用的资源。

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 {
        return super.onStartCommand(intent, flags, startId)
	}
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyService", "onDestroy executed")

	}
}

现在只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用 进入后台之后,Service随时都有可能被系统回收。

Activity和Service进行通信

目前我们希望在MyService里提供一个下载功能,然后在Activity中可以决定何时开始 下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。

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提供的 方法了。

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?) {
		bindServiceBtn.setOnClickListener {
		val intent = Intent(this, MyService::class.java)
		bindService(intent, connection, Context.BIND_AUTO_CREATE) // 绑定Service
		}
	
		unbindServiceBtn.setOnClickListener { 
			unbindService(connection) // 解绑Service
		} 	
	}
}

Service的生命周期

虽然每调用一次 startService()方法,onStartCommand()就会执行一次,但实际上每个Service只会存在 一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService() 或stopSelf()方法,Service就会停止。

前台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)
}

使用前台Service必须在AndroidManifest.xml文件中进行权 限声明才行

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

使用IntentService(推荐)

Service中的代码都是默认运行在主线程当中 的,如果直接在Service里处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。
所以这个时候就需要用到Android多线程编程的技术了,我们应该在Service的每个具体的方法 里开启一个子线程,然后在这里处理那些耗时的逻辑。

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

例子

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")
	} 
}
Log.d("MainActivity", "Thread id is ${Thread.currentThread().name}")
val intent = Intent(this, MyIntentService::class.java)
startService(intent)
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-04-24 09:34:21  更:2022-04-24 09:35:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 0:03:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码