安卓笔记二
多线程
每个Android 应用的主线程,它不应该完成太多的⼯作,尤其不能被阻塞。任何长时间运⾏的计算和操作(例如解码位图、访问磁盘或执⾏⽹络请求)都应该使用后台任务。
后台任务两大主要类型:
CPU密集型,适合于使用并⾏计算算法及相关技术。
I/O密集型,适合于采用异步编程模型。
后台任务选型:
CPU密集,执⾏时间比较短的,使用⼯作线程或线程池,需要更新UI界面的,使用AsyncTask或Handler。
由用户发起的需要立即运⾏并且必须执⾏完毕的⼯作,使用服务。
I/O密集型的后台任务,推荐使用Kotlin协程或RxJava。
从⽹络后台下载,可以使用DownloadManager。
明确在未来某个时间运⾏的任务,使用AlarmManager。
对于可延迟的时间不需要确定的⼯作以及预计即使设备或应用重启也会运⾏的⼯作,使用WorkManager。
Kotlin标准库提供了简便的⽅法用于启动⼀个线程,可以直接用于Android中:
Android主线程负责响应用户操作,因此,它所完成的⼯作任务不能耗时太长。否则,用户会认为此应用失去响应⽽“死机”。
应该避免在主线程中完成以下耗时的任务:
(1)存取SD卡
(2)访问⽹络
(3)存取数据库
应该在主线程之外使用其他线程(称为⼯作线程)来完成这些⼯作。
线程访问UI控件
禁止UI控件的跨线程访问:Android多线程程序有⼀个限制,就是在⼯作线程中不能跨线程地直接访问UI控件。因此需要进行一些操作:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//程序挂掉了!
btnVisitUIControl.setOnClickListener {
Thread {
tvInfo.text = "跨线程直接访问UI控件"
}.start()
}
btnVisitUIControl2.setOnClickListener {
Thread {
runOnUiThread {
tvInfo.text = "跨线程安全访问UI控件"
}
}.start()
}
}
}
所以,不要在线程函数中保存对外部UI对象(比如Activity/Fragment或某个控件)的引用!
如果⼯作线程希望将当前⼯作的进度通知UI线程显示给用户,或者是⼯作线程⼰⼯作完毕,结果需要显示给用户,需要将更新UI界面的代码封装为Runnable对象,使用以下⽅法之⼀,“推送”到UI线程中去执⾏……
Activity对象.reuOnUiThread( Runnable )
View对象.post( Runnable )
View对象.postDelayed( Runnable, long )
AsyncTask
这个组件的最常用场景,就是将原先放在UI线程中执⾏的那些代码,移到⼯作线程中去执⾏。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//在UI线程中实例化一个异步任务对象
var myAsyncTask: MyAsyncTask? = null
btnStart.setOnClickListener {
myAsyncTask = MyAsyncTask()
//启动其执行,并传给它一个参数
myAsyncTask?.execute(100)
btnStart.isEnabled = false
btnCancel.isEnabled = true
}
btnCancel.setOnClickListener {
//取消正在运行的任务
myAsyncTask?.cancel(true)
//切换按钮状态
btnCancel.isEnabled = false
btnStart.isEnabled = true
}
}
//代表一个异步任务
inner class MyAsyncTask : AsyncTask<Int, Int, Int>() {
//所有需要在后台线程中完成的工作,写在这个方法中
override fun doInBackground(vararg params: Int?): Int {
//params保存了通过execute()方法传入的所有参数
val sleep = params[0]!!.toInt()
var result = 0
for (i in 0..100) {
try {
Thread.sleep(sleep.toLong())
result += i
} catch (e: InterruptedException) {
e.printStackTrace()
}
if (i % 5 == 0) {
//报告进度(之所以要按5取模,是为了不要更新得太频繁)
//调用此方法提供执⾏进度信息,供UI线程更新界面时使用
publishProgress(i)
}
//是否外界提出了取消的请求?
if (isCancelled) break
}
//返回处理结果
return result
}
//在任务执行过程调用publishProgress()方法报告进度,
//此方法被调用,并在UI线程中执行
//values代表传⼊的⼀个或多个参数,本例中是⼀个整数,如果希望能传送多种类型的信息,可以使用自定义⼀个数据类,然后传送⼀个对象作为实参
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
tvInfo.text = "已完成 ${
values[0]}%"
progressBar.progress = values[0] ?: 0
}
//在工作完成后,此方法被调用,在UI线程中执行
//此处⽅法的参数,是doInBackground()⽅法“返回(return)”的结果
override fun onPostExecute(result: Int?) {
super.onPostExecute(result)
tvInfo.text = "工作已完成,结果为:$result"
//切换相应按钮的状态
btnCancel.isEnabled = false
btnStart.isEnabled = true
}
//在doInBackground()方法执行前调用,
//主要用于执行一些初始化的工作,在UI线程中执行
override fun onPreExecute() {
super.onPreExecute()
}
//外界提出“取消请求”时,此方法被调用,在UI线程中执行
override fun onCancelled() {
super.onCancelled()
tvInfo.text = "工作己被取消"
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="启动后台任务"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="后台任务进度提示"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="#7B1FA2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnCancel" />
<Button
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="取消后台任务"
android:enabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnStart" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvInfo" />
</androidx.constraintlayout.widget.ConstraintLayout>
AsyncTask三个“泛型参数”的含义
AsyncTask< Params, Progress, Result>
1.Params 设定doInBackground(Params…)⽅法的参数类型。其值由execute(Params… params)⽅法传⼊
2.Progress 设定onProgressUpdate(Progress…)的参数类型;
3.Result 设定onPostExecute(Result)的参数类型。
当某⽅法不需要使用参数时,可以传⼊Void
,注意不是void
。
AsyncTask其实是单线程的:
默认情况下,⼀个AsyncTask会使用单线程来完成它所承担的⼯作任务。如果你在App中给它配置了⼀个⼯作任务队列,那么请注意前面的任务如果执⾏时间较长,后面的任务将会等待很长的时间。
如果希望AsyncTask能使用多线程来处理任务,可以使用AsyncTask类的executeOnExecutor()⽅法来启动它,这时,AsyncTask内部会使用线程池来实现并⾏处理。
如果你的AsyncTask类定义为Activity的内部类,这时请注意它会隐式引用外部的Activity对象,因此,只要它没有运⾏结束,包容它的外部Activity对象就⽆法被垃圾回收
故 :AsyncTask仅适合于执⾏那些并不需要花费太多时间就能完成的后台任务。
使用WeakReference解决资源泄露问题:
将AsyncTask定义为独立的类,将Activity注⼊进去,保存为弱引用,然后通过引用来保存它,是⼀种避免App崩溃的解决⽅案。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnStart.setOnClickListener {
MyAsyncTask(this).execute()
}
}
}
class MyAsyncTask constructor(activity: MainActivity)
: AsyncTask<Unit, Int, Unit>() {
//使用弱引用保存外部Activity的引用
var activityWeakReference = WeakReference(activity)
override fun doInBackground(vararg params: Unit?) {
for (i in 0..100) {
publishProgress(i)
Thread.sleep(100)
}
}
override fun onProgressUpdate(vararg values: Int?) {
//检索外部Activity是否还"活着"……
activityWeakReference.get()?.tvProgress?.text = "${
values[0]}%"
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<TextView
android:id="@+id/tvProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
android:textColor="#303F9F"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="tvProgress" />
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="启动后台任务"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Handler使用
AndroidApp 的主线程,关联有一个消息队列,有一个Looper 对象负责从队列中提取消息,进行处理。
在哪个线程中实例化Handler ,它就可以向哪个线程的消息队列中插入 Runnable对象(当然,前提是这个线程关联有消息队列,主线程是肯定有的。)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//在主线程中实例化Handler对象
val handler = Handler()
btnPostRunnable.setOnClickListener {
//showThreadInfo(handler)
showThreadInfo2()
}
}
//显示主线程和工作线程的id及名字
private fun showThreadInfo(handler: Handler) {
val mainThreadInfo = "主线程:${
getCurrentThreadInfo()}"
thread {
val workerThreadInfo = "工作线程:${
getCurrentThreadInfo()}"
//使用Handler对象将一个runnable对象发送到主线程的消息队列中
handler.post {
tvInfo.text = " $mainThreadInfo \n $workerThreadInfo"
}
}
}
//向主线程投寄消息的Handler 其实并不需要一定在主线程中实例代
//工作线程中也是可以 new 一个出来的,只要传一个特定的参数值即可。
//显示主线程和工作线程的id及名字
private fun showThreadInfo2() {
val mainThreadInfo = "主线程:${
getCurrentThreadInfo()}"
thread {
val workerThreadInfo = "工作线程:${
getCurrentThreadInfo()}"
//在工作线程内部,可以创建一个关联着主线程消息队列的Handler
val myCreatedHandler = Handler(Looper.getMainLooper())
//使用Handler对象将一个runnable对象发送到主线程的消息队列中
myCreatedHandler.post {
tvInfo.text = " $mainThreadInfo \n $workerThreadInfo"
}
}
}
//此函数显示当前线程的信息
private fun getCurrentThreadInfo(): String {
return "id =${
Thread.currentThread().id}," +
"name = ${
Thread.currentThread().name}"
}
}
Handler的创建场景很重要:
默认情况下,在哪个线程中new的Handler ,它就使用这个线程中的Looper,并在这个线程中执行。比如,在Activity的onCreate()方法中 new 一个 Handler ,则此Handler就使用主线程的 Looper 和消息队列,在主线程中执行。
如果必须在后台线程中new一个Handler,并且希望能使用它来将消息推送到主线程中去执行,则可以在new Handler时,给其构造方法传入“Looper.getMainLooper”这一参数。
使用Looper,可以为任意一个线程添加消息队列
自定义一个工作线程,并为它配置一个消息队列 。
const val TAG = "WorkerThead"
fun log(msg: String) {
Log.d(TAG, msg)
}
class WorkerThread : Thread() {
//用于向本线程的消息队列投递消息
private lateinit var handler: Handler
//引用本线程的消息循环对象
private lateinit var looper: Looper
override fun run() {
//创建消息队列,并启动消息循环
Looper.prepare()
handler = Handler()
log("工作线程${
name}己经开始运行")
//保存本线程Looper对象的引用
looper = Looper.myLooper()!!
//启动消息循环,线程己经准备好接收外界投递的任务
Looper.loop()
}
fun quit() {
looper.quit()//退出消息循环
log("工作线程${
name}己经退出")
}
//接收从外界投递的任务
fun post(task: () -> Unit) {
handler.post {
task()
}
}
}
class MainActivity : AppCompatActivity() {
var workerThread: WorkerThread? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//创建新的拥有消息队列的工作线程
btnNewWorker.setOnClickListener {
workerThread = WorkerThread()
workerThread?.start()
tvInfo.text = "已启动工作线程${
workerThread?.name}"
//禁用相应按钮
btnMainToWorker.isEnabled = true
btnStop.isEnabled = true
btnNewWorker.isEnabled = false
}
//发送任务给工作线程
btnMainToWorker.setOnClickListener {
for (i in 1..5) {
workerThread?.post {
log("任务$i, 运行于:${
Thread.currentThread().name}")
}
}
workerThread?.post {
log("Result: ${
(1..1_000_000L).sum()}")
}
tvInfo.text = "已投递6个任务给工作线程${
workerThread?.name}"
}
btnStop.setOnClickListener {
workerThread?.quit()
tvInfo.text = "工作线程${
workerThread?.name}己停止工作"
//设置界面各按钮的状态
btnMainToWorker.isEnabled = false
btnStop.isEnabled = false
btnNewWorker.isEnabled = true
}
}
}
Message
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据 ,包容以下属性:
What:用户定义的 int 型消息代码,用来描述消息。
obj:用户指定object类型,可以将任意数据放入,此对象将被传给消息处理者。
target:引用处理 消息的 Handler 。
arg1和arg2:可用于保存另外两个整数值。
可以调用Handler对象的obtainMessage()方法创建一个消息对象,然后给它的相应属性赋值(也就是把要传送的消息封装到这个对象中去)。
class MainActivity : AppCompatActivity() {
//消息的类型
val PROGRESS_INFO = 1 //工作进度消息类型标识
val WORK_FINISHED = 2 //工作结束消息类型标识
//此Handler在MainActivity创建时被创建,所以它关联着主线程的Looper
//它的handleMessage方法处理各种消息
val messageHandler = object : Handler() {
override fun handleMessage(msg: Message?) {
when (msg!!.what) {
PROGRESS_INFO -> tvInfo.text = "${
msg.arg1}%"
WORK_FINISHED -> tvInfo.text = "计算完成!"
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnNewThread.setOnClickListener {
thread {
for (i in 0..100) {
//创建一个消息对象
val msg = messageHandler.obtainMessage()
msg.what = PROGRESS_INFO //指明消息类型
msg.arg1 = i //这个值代表任务完成的百分比值
messageHandler.sendMessage(msg)
Thread.sleep(100) //模拟工作延时
}
//发一个空消息,指明任务结束
messageHandler.sendEmptyMessage(WORK_FINISHED)
}
}
}
}
在本例中,由于messageHandler是在主线程中创建的,所以handleMessage() 方法中的代码可以直接访问UI控件。如果这个Handler 是由工作线程创建的,则不能有代码直接访问UI控件。
创建信息时,调用Handler对象的obtainMessage方法。传入其他必要消息字段后,该方法会自动设置当前Handler对象作为本消息的target。也就是说通常情况下,谁创建的消息,谁就负责处理。
创建信息时,调用Handler对象的obtainMessage方法。传入其他必要消息字段后,该方法会自动设置当前 Handler 对象作为本消息的 target。也就是说通常情况下,谁 创建的消息,谁就负责处理。
一旦取得Message ,就可以调用 Handler对象的sendMessage()方法把这个Message 放置 在它所关联的线程消息队列 的尾部。
线程内部的Looper 对象取得 消息队列中的特定消息后,会将它发送给消息的目标Handler去处理。消息一般是在目标Handler的Handler.handleMessage实现方法中进行处理的。
使用WorkManager开发后台任务
WorkManager是 Android 提供的一个组件库(也是 Jetpack 的一个组成部分)。如果
某项工作任务期望在应用退出后(或者在手机重启时)仍然能够运行,那么就可以使用它来达成目的。
特性
1.可以设定特定的条件下才运行特定的任务,比如,只有在联网或充电情况下才运行。
2.可以定义两种类型的后台任务:一次性的和定时反复的周期性任务。集成了LiveData ,因此 App 可以实时查询特定任务的状态信息。
3.多个任务可进行并联和串联,其状态可查询,并且支持中途取消。
4.后台任务可以很方便地集成RxJava 或 Coroutine ,实现非阻塞的异步方法调用。
使用方法
在模块关联的build.gradle中添加以下依赖声明:
def work_version = "2.3.4"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
核心组件
Worker:定义要执行的后台任务
WorkerRequest:确定任务执行的场景:比如在什么情况下运行,是仅运行一次还还是运行多次等等。
WorkManager:它管理着一个任务队列,负责调度任务的执行。
WorkInfo:封装了特定任务的信息,可以用于查询其当前的状态。
class MainActivity : AppCompatActivity() {
lateinit var workManager: WorkManager
//取得一个WorkManager 的实例之后,就可以用它来管理与调度工作任务了
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
workManager = WorkManager.getInstance(this)
btnStart.setOnClickListener {
// runSimpleWorker()
// runSimpleWorkerWhenConnectedNetwork()
//请试着传入100和-100,以测试Worker不同状态
//(成功或失败)下的返回值
//runInputOutputWorker(100)
//runInputOutputWorker(-100)
// runStatusWorker()
// runCancelableWorker()
runWorkerChains()
}
}
private fun runSimpleWorker() {
//创建一个仅运行一次的SimpleWorker任务
//任务有一个id作为标识(它是一个UUID类型的值)
//可以再添加一个Tag作为附加标识
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.addTag("SimpleWoker")
.build()
//交给WorkManager,调度运行
workManager.enqueue(request)
tvInfo.text = "任务己进入WorkManager队列排队……\n"
}
//仅当联网时才运行后台任务
private fun runSimpleWorkerWhenConnectedNetwork() {
val myConstraint = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
//创建一个仅运行一次的任务
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setConstraints(myConstraint)
.build()
//交给WorkManager,调度运行
workManager.enqueue(request)
}
private fun runStatusWorker() {
val request = OneTimeWorkRequest.Builder(StatusWorker::class.java)
.build()
workManager.enqueue(request)
//获取工作任务状态对象,并“观察”它的变化
workManager.getWorkInfoByIdLiveData(request.id)
.observe(this, Observer {
//显示进度信息
tvInfo.text = "${
it.progress.getInt(STATUS_PROGRESS_INFO, 0)}%"
//任务结束后
if (it.state.isFinished) {
//取出结果并显示
val result = it.outputData.getLong(STATUSE_WORKER_RESULT, -1)
tvInfo.text = "Result: $result"
}
})
}
private fun runCancelableWorker() {
val cancelableRequest = OneTimeWorkRequest
.Builder(CancelableWorker::class.java)
.build()
workManager.enqueue(cancelableRequest!!)
workManager.getWorkInfoByIdLiveData(cancelableRequest!!.id)
.observe(this, Observer {
tvInfo.text = "${
it.progress.getInt(STATUS_PROGRESS_INFO, 0)}%"
if (it.state.isFinished) {
val result = it.outputData.getLong(STATUSE_WORKER_RESULT, -1)
tvInfo.text = "Result: $result"
if (it.state == WorkInfo.State.CANCELLED) {
tvInfo.text = "Canceled"
}
}
})
//在另一个线程中,3秒后取消操作
thread {
sleep(3000)
workManager.cancelWorkById(cancelableRequest.id)
}
}
fun createStepWorkerRequest(step: String): OneTimeWorkRequest {
val inputData = workDataOf(STEP_INFO to step)
val request = OneTimeWorkRequest.Builder(MultiStepWorker::class.java)
.setInputData(inputData)
.build()
workManager.getWorkInfoByIdLiveData(request.id).observe(this, Observer {
if (it.state.isFinished) {
val resultInfo = it.outputData.getString(STEP_RESULT)
tvInfo.text = resultInfo
}
})
return request
}
private fun runWorkerChains() {
val preStep1 = createStepWorkerRequest("预处理任务一")
val preStep2 = createStepWorkerRequest("预处理任务二")
val preStep3 = createStepWorkerRequest("预处理任务三")
val processStep1 = createStepWorkerRequest("正式处理步骤一")
val processStep2 = createStepWorkerRequest("正式处理步骤二")
val finishedStep = createStepWorkerRequest("收尾工作")
workManager.beginWith(listOf(preStep1, preStep2, preStep3))
.then(processStep1)
.then(processStep2)
.then(finishedStep)
.enqueue()
}
//运行一个有输入和输出的后台任务示例
private fun runInputOutputWorker(number: Int) {
//生成输入数据
val inputData = workDataOf(INPUT_NUMBER to number)
val request = OneTimeWorkRequest.Builder(InputOutputWorker::class.java)
.setInputData(inputData) //包容输入数据
.build()
workManager.enqueue(request)
//通过WorkRequest对象的Id值获取后台任务对象所关联的WorkInfo对象,观测其状态的变化
workManager.getWorkInfoByIdLiveData(request.id).observe(this, Observer {
//如果工作顺利完成
if (it.state == WorkInfo.State.SUCCEEDED) {
//取出结果
val result = it.outputData.getInt(OUTPUT_SUM, -1)
tvInfo.text = "1到 $number 的和为:$result"
} else {
//出错,显示出错信息
val result = it.outputData.getString(OUTPUT_ERROR)
tvInfo.text = result
}
})
}
}
定义一个工作任务
需要从Worker 类中派生出一个子类,然后,重写其 doWork 方法,在这里定义需要在后台执行的任务,并返回工作结果。
//一个后台任务,派生自Worker类,重写它的doWork方法
class SimpleWorker(context: Context, parameters: WorkerParameters)
: Worker(context, parameters) {
override fun doWork(): Result {
//在此定义要完成的工作
//以下这句,输出它的运行线程名
log("doWork方法运行于:【${
Thread.currentThread().name}】")
//工作完成之后,返回工作结果
return Result.success()
}
}
后台任务结束时的三种“结果”:doWork()可以有以下三种返回值:
•已成功完成: Result.success()
•已失败: Result.failure()
•需要稍后重试: Result.retry()
上述返回值中,success() 和 failure() 都可有一个类型为 Data 的参数,用于携带一些信息返回给外界。
任务的调度与运行
定义好Worker 之后,需要再创建一个 WorkRequest 对象,以确定此工作任务的运行方式
private fun runSimpleWorker() {
//创建一个仅运行一次的SimpleWorker任务
//任务有一个id作为标识(它是一个UUID类型的值)
//可以再添加一个Tag作为附加标识
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.addTag("SimpleWoker")
.build()
//可以选择为WorkRequest关联一个Tag从而支持后期取消或查询其状态。
//交给WorkManager,调度运行
workManager.enqueue(request)
tvInfo.text = "任务己进入WorkManager队列排队……\n"
}
之后任务就被组织为一个队列,顺次在后台线程中运行:
任务组成一个任务队列,WorkManager 负责从队列中取出任务,然后按照 WorkRequest 的要求分派特定的后台线程运行它。
工作约束
通过创建相应的工作约束,可以通知WorkManager,指明在何种条件下才运行这一工作任务。
//仅当联网时才运行后台任务
private fun runSimpleWorkerWhenConnectedNetwork() {
val myConstraint = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
//创建一个仅运行一次的任务
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setConstraints(myConstraint)
.build()
//交给WorkManager,调度运行
workManager.enqueue(request)
}
任务输入输出
后台任务通常都会有输入及输出。输入和输出使用Data 对象保存。
Kotlin提供了一个 workDataOf()方法创建一个包容有特定数据的Data 对象,Java则可以使用Data.Builder().putXXX()方法达到相同的目的。
Worker类的内部可通过调用 Worker.getInputData 访问 输入参数。将工作结果也放到Data 中,然后把这个Data对象包含到Result的Result.success()或Result.failure()中返回给外界。
const val INPUT_NUMBER = "inputNumber"
const val OUTPUT_SUM = "outputSum"
const val OUTPUT_ERROR = "outputError"
//一个有输入有输出的后台任务
class InputOutputWorker(context: Context, parameters: WorkerParameters) :
Worker(context, parameters) {
override fun doWork(): Result {
//从inputData属性中提取外部传入的参数
val inputNumber = inputData.getInt(INPUT_NUMBER, -1)
//数据如果是有效的……
if (inputNumber > 0) {
val result = (1..inputNumber).sum()
//将结果放在Data对象中
val outputData = Data.Builder().putInt(OUTPUT_SUM, result).build()
//向外界返回计算结果
return Result.success(outputData)
}
//如果使用workDataOf()这个快捷函数,要求设计jvmTarget = "1.8"
val outputData = workDataOf(OUTPUT_ERROR to "有效的数字必须是一个大于0的整数。")
//数据无效,返回出错信息
return Result.failure(outputData)
}
}
调用
//运行一个有输入和输出的后台任务示例
private fun runInputOutputWorker(number: Int) {
//生成输入数据
val inputData = workDataOf(INPUT_NUMBER to number)
val request = OneTimeWorkRequest.Builder(InputOutputWorker::class.java)
.setInputData(inputData) //包容输入数据
.build()
workManager.enqueue(request)
//通过WorkRequest对象的Id值获取后台任务对象所关联的WorkInfo对象,观测其状态的变化
workManager.getWorkInfoByIdLiveData(request.id).observe(this, Observer {
//如果工作顺利完成
if (it.state == WorkInfo.State.SUCCEEDED) {
//取出结果
val result = it.outputData.getInt(OUTPUT_SUM, -1)
tvInfo.text = "1到 $number 的和为:$result"
} else {
//出错,显示出错信息
val result = it.outputData.getString(OUTPUT_ERROR)
tvInfo.text = result
}
})
}
工作状态
工作状态通过WorkInfo 对象的 state 属性获取。
报告工作进度
Worker可以通过调用setProgressAsync()方法向外界报告工作进度。
val STATUS_PROGRESS_INFO = "progressInfo"
val STATUSE_WORKER_RESULT = "StatusWorkerResult"
class StatusWorker(context: Context, parameters: WorkerParameters)
: Worker(context, parameters) {
override fun doWork(): Result {
var sum: Long = 0
for (i in 1..100) {
//将进度信息保存到Data中
val progressInfo = workDataOf(STATUS_PROGRESS_INFO to i)
//将进度信息发送出去
setProgressAsync(progressInfo)
//用于“拖慢”程序的执行速度
Thread.sleep(300)
sum += i
}
//返回结果
val result = workDataOf(STATUSE_WORKER_RESULT to sum)
return Result.success(result)
}
}
查询Worker的工作进度
private fun runStatusWorker() {
val request = OneTimeWorkRequest.Builder(StatusWorker::class.java)
.build()
workManager.enqueue(request)
//获取工作任务状态对象,并“观察”它的变化
workManager.getWorkInfoByIdLiveData(request.id)
.observe(this, Observer {
//显示进度信息
tvInfo.text = "${
it.progress.getInt(STATUS_PROGRESS_INFO, 0)}%"
//任务结束后
if (it.state.isFinished) {
//取出结果并显示
val result = it.outputData.getLong(STATUSE_WORKER_RESULT, -1)
tvInfo.text = "Result: $result"
}
})
}
提前取消工作的方法
使用后台正在运行任务的id,调用 WorkManager.cancelWorkById()方法取消这一任务。
private fun runCancelableWorker() {
val cancelableRequest = OneTimeWorkRequest
.Builder(CancelableWorker::class.java)
.build()
workManager.enqueue(cancelableRequest!!)
workManager.getWorkInfoByIdLiveData(cancelableRequest!!.id)
.observe(this, Observer {
tvInfo.text = "${
it.progress.getInt(STATUS_PROGRESS_INFO, 0)}%"
if (it.state.isFinished) {
val result = it.outputData.getLong(STATUSE_WORKER_RESULT, -1)
tvInfo.text = "Result: $result"
if (it.state == WorkInfo.State.CANCELLED) {
tvInfo.text = "Canceled"
}
}
})
//在另一个线程中,3秒后取消操作
thread {
sleep(3000)
workManager.cancelWorkById(cancelableRequest.id)
}
}
//无须做任何特殊处理,任务就是可以被取消的。
class CancelableWorker(context: Context, parameters: WorkerParameters)
: Worker(context, parameters) {
override fun doWork(): Result {
var sum: Long = 0
for (i in 1..100) {
val progressInfo = workDataOf(STATUS_PROGRESS_INFO to i)
setProgressAsync(progressInfo)
Thread.sleep(300)
sum += i
}
val result = workDataOf(STATUSE_WORKER_RESULT to sum)
return Result.success(result)
}
//当任务被取消时,此方法会被调用
override fun onStopped() {
super.onStopped()
log("Worker has been stopped.")
}
}
实现“链式”任务
private fun runWorkerChains() {
val preStep1 = createStepWorkerRequest("预处理任务一")
val preStep2 = createStepWorkerRequest("预处理任务二")
val preStep3 = createStepWorkerRequest("预处理任务三")
val processStep1 = createStepWorkerRequest("正式处理步骤一")
val processStep2 = createStepWorkerRequest("正式处理步骤二")
val finishedStep = createStepWorkerRequest("收尾工作")
workManager.beginWith(listOf(preStep1, preStep2, preStep3))
.then(processStep1)
.then(processStep2)
.then(finishedStep)
.enqueue()
}
定义周期性工作
可以使用PeriodicWorkRequestBuilder 创建“周期性”的工作,不过这种工作,可以定义的最短重复间隔是15分钟。
val constraints = Constraints.Builder().setRequiresCharging(true).build()
//每隔一小时,运行 SaveImageToFileWorker 一次
val saveRequest = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
.setConstraints(constraints).build()
WorkManager.getInstance(myContext).enqueue(saveRequest)
fun createStepWorkerRequest(step: String): OneTimeWorkRequest {
val inputData = workDataOf(STEP_INFO to step)
val request = OneTimeWorkRequest.Builder(MultiStepWorker::class.java)
.setInputData(inputData)
.build()
workManager.getWorkInfoByIdLiveData(request.id).observe(this, Observer {
if (it.state.isFinished) {
val resultInfo = it.outputData.getString(STEP_RESULT)
tvInfo.text = resultInfo
}
})
return request
}
//一个模拟多步才能完成全部处理任务的Worker
class MultiStepWorker(context: Context, parameters: WorkerParameters) :
Worker(context, parameters) {
override fun doWork(): Result {
val step = inputData.getString(STEP_INFO)
val sleepTime = Random().nextInt(5000)
Thread.sleep(sleepTime.toLong())
val info=" 耗时 $sleepTime 毫秒完成 $step 【${
Thread.currentThread().name}】"
log(info)
val resultInfo = workDataOf(STEP_RESULT to info)
return Result.success(resultInfo)
}
}
Service
“服务Service”是 Android 中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台 ,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
服务与线程:
服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
创建Service:
第一步:继承Service 类,并重写其OnStartCommand、OnCreate、OnDestroy、OnBind等方法。
class MyService() : Service() { }
第二步:在AndroidManifest.xml
文件中的<application>
节点里对服务进行配置:
<service android:name=".MyService" />
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jinxuliang.helloservice">
<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=".services.MyBindService"
android:enabled="true"
android:exported="true"></service>
<service
android:name=".services.MyLoopService"
android:enabled="true"
android:exported="true" />
<service
android:name=".services.MyService"
android:enabled="true"
android:exported="true" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Service启动
服务不能自己运行,需要通过调用 Context.startService()或Context.bindService()方法启动服务。
使用startService方法启用服务,调用者与服务之间没有关联, 即使调用者退出了,服务仍然运行。
使用bindService 方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生, 必求同时 死”的特点。
class MyService : Service() {
//如果不使用绑定方式,则此方法可以不重写
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
//onCreate()方法会在第一次服务创建的时候调用
override fun onCreate() {
super.onCreate()
log("onCreate")
}
//onStartCommand()方法会在每次服务启动的时候调用
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
log("onStartCommand")
return super.onStartCommand(intent, flags, startId)
}
//onDestroy()方法会在服务销毁的时候调用。
override fun onDestroy() {
super.onDestroy()
log("onDestroy")
}
}
服务调用与停止
都是由Activity所提供的相应方法实现的
btnStartService.setOnClickListener {
val startIntent = Intent(this, MyService::class.java)
startService(startIntent) //启动服务
}
btnStopService.setOnClickListener {
val stopIntent = Intent(this, MyService::class.java)
stopService(stopIntent) //停止服务
}
Service 启动之后,独立于启动者的生命周期,即使用户用back键退出程序,此服务仍在运行,直至其任务完成。
如果不显式停止服务,服务将一直运行下去
显式停止服务,可调用Activity.stopService或Service自己的stopSelf方法。此方法一调用, Service 的 OnDestory 方法将被调用。但要注意,这并非意味着服务会马上停止运行。如果当前任务还没完成,服务会继续运行。
信息传递
需要向服务传送信息:使用intent
btnSendInfoToService.setOnClickListener {
//将一个整数传给MyLoopService
val intent = Intent(this, MyLoopService::class.java)
intent.putExtra(LOOP_COUNT, 5)
startService(intent)
}
从Service中取出信息:
Service的onStartCommand方法中有一个参数就是intent,可以从它那儿取出Activity传来的信息。
const val LOOP_COUNT = "LoopCount"
class MyLoopService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
//取出外界传入的参数
val loopCount = intent?.getIntExtra(LOOP_COUNT, -1) ?: 0
for (i in 0..loopCount) {
log("Loop $i")
}
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent?): IBinder? {
TODO("Not yet implemented")
}
}
一旦在项目的任何位置调用了Context 的 startService 方法,相应的服务就会启动起来,并回调 onStartCommand 方法。如果这个服务之前还没有创建过, onCreate 方法会先于 onStartCommand 方法执行。服务启动了之后会一直保持运行状态,直到 topService 或stopSelf 方法被调用。
每个服务都只会存在一个实例,即它是Singleton的。
服务的绑定与解绑
当一个Service 支持外界绑定时,它必须在外界绑定它时,返回一个实现了 IBinder 接口的对象给外界。外界拿到这个IBinder 对象之后,可以通过它来与 Service 通讯。
//供MyBinderService返回的IBinder对象
//Binder由Android提供,它实现了IBinder接口
class MyBinder:Binder(){
fun introduceMySelf(){
log("I'm instance of MyBinder.")
}
}
//一个最简单的支持绑定的Service
class MyBindService : Service() {
override fun onBind(intent: Intent): IBinder {
log("onBind")
//返回一个IBinder对象给外界
return MyBinder()
}
override fun onCreate() {
super.onCreate()
log("onCreate")
}
override fun onDestroy() {
super.onDestroy()
log("onDestory()")
}
}
class MainActivity : AppCompatActivity() {
//管理与MyBindService的连接
private val connection = object:ServiceConnection{
//当服务被Android杀死时,此方法被调用
override fun onServiceDisconnected(name: ComponentName?) {
log("onServiceDisconnected")
}
//当成功连接(即“绑定”)到MyBindService时,此方法被调用
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
log("onServiceConnected: ${
name?.shortClassName}")
//获取MyBindService返回的MyBinder对象
(service as MyBinder).introduceMySelf()
}
}
...
绑定&解绑:
btnBindService.setOnClickListener {
val bindIntent=Intent(this,MyBindService::class.java)
// 绑定服务,第3个参数将导致Service对象的onCreate()方法被自动调用
bindService(bindIntent, connection, BIND_AUTO_CREATE)
}
btnUnBindService.setOnClickListener {
//断开绑定
unbindService(connection)
}
生命周期:
调用Context 的 bindService来绑定一个服务, 这时就会自动调用服务中的onBind 方法。类似地,如果这个服务之前还没有创建过,onCreate方法会先于onBind方法执行。之后,调用方可以获取onBind方法里返回的IBinder 对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。
当调用了bindService方法后,又去调用unbindService方法,onDestroy方法将会执行,服务对象被销毁.
fun log(msg: String) {
Log.d("HelloService", msg)
}
class MainActivity : AppCompatActivity() {
//管理与MyBindService的连接
private val connection = object:ServiceConnection{
//当服务被Android杀死时,此方法被调用
override fun onServiceDisconnected(name: ComponentName?) {
log("onServiceDisconnected")
}
//当成功连接(即“绑定”)到MyBindService时,此方法被调用
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
log("onServiceConnected: ${
name?.shortClassName}")
//获取MyBindService返回的MyBinder对象
(service as MyBinder).introduceMySelf()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnStartService.setOnClickListener {
val startIntent = Intent(this, MyService::class.java)
startService(startIntent) //启动服务
}
btnStopService.setOnClickListener {
val stopIntent = Intent(this, MyService::class.java)
stopService(stopIntent) //停止服务
}
btnSendInfoToService.setOnClickListener {
//将一个整数传给MyLoopService
val intent = Intent(this, MyLoopService::class.java)
intent.putExtra(LOOP_COUNT, 5)
startService(intent)
}
btnBindService.setOnClickListener {
val bindIntent=Intent(this,MyBindService::class.java)
// 绑定服务,第3个参数将导致Service对象的onCreate()方法被自动调用
bindService(bindIntent, connection, BIND_AUTO_CREATE)
}
btnUnBindService.setOnClickListener {
//断开绑定
unbindService(connection)
}
}
}
服务与Activity之间的协作
当Activity需要与Service之间进行紧密协作时,通常是通过 “绑定服务”的 形式进行的。
//一个可动态接收Activity传送过来信息的Service
//完成累加求和的功能
class MyBindService : Service() {
//绑定时,向外界返回MyBinder对象
override fun onBind(intent: Intent): IBinder {
return MyBinder()
}
//引用Acitivty传入的方法引用
var outerShowInfo: ((String) -> Unit)? = null
//Kotlin内部类
inner class MyBinder : Binder() {
fun beginCalculate(endNumber: Int,
showInfo: (String) -> Unit) {
innerProcess(endNumber)
outerShowInfo = showInfo
}
}
//在内部完成各种处理工作
private fun innerProcess(endNumber:Int) {
//回调外界代码
outerShowInfo?.invoke("计算从 1 到 $endNumber 的和")
//注意,以下代码是在工作线程中完成计算任务的
thread {
var sum = 0
for (i in 1..endNumber) {
//回调外界代码
outerShowInfo?.invoke("正在处理:$i")
sum += i
Thread.sleep(100)
}
//回调外界代码
outerShowInfo?.invoke("处理结束,结果为:${
sum}")
}
}
}
//Activity实现了ServiceConnection接口
class MainActivity : AppCompatActivity(), ServiceConnection {
//引用MyBindService传回的MyBinder对象
var mybinder: MyBindService.MyBinder? = null
override fun onServiceDisconnected(name: ComponentName?) {
mybinder = null
}
//绑定Service时,保存Service传回的MyBinder对象引用
override fun onServiceConnected(name: ComponentName?,
service: IBinder?) {
if (service is MyBindService.MyBinder) {
mybinder = service
}
}//有了MyBinder对象的引用,Activity就能通过它的公有方法远程监控Service的工作
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnBind.setOnClickListener {
BindToService()
}
btnStartCalculate.setOnClickListener {
beginCalculate()
}
}
//开始计算
//建立Activity与Service之间的“监控”关系
private fun beginCalculate() {
mybinder?.apply {
val number = edtNumber.text.toString().toInt()
//将一小段代码以Lambda表达式的形式传给Service
//供其"回调"
beginCalculate(number) {
showInfo(it) //showInfo设计为可以跨线程调用
}
}
}
//绑定到MyBindService
private fun BindToService() {
val intent = Intent(this,
MyBindService::class.java)
//注意一下bindService的第二个参数是this
//这里Activity本身充当ServiceConnection对象。
bindService(intent, this, Context.BIND_AUTO_CREATE)
tvInfo.text = "MyBindService服务己经绑定"
edtNumber.isEnabled = true
btnStartCalculate.isEnabled = true
}
//可以被跨线程安全调用的方法
//为了实现跨线程显示信息
//需要调用runOnUiThread()方法将要显示的信息“推送”到 UI 线程中执行
fun showInfo(msg: String) {
runOnUiThread {
tvInfo.text = msg
}
}
}
示例所展示的,是Activity通过IBinder对象直接将一小段代码传给Service回调,这里利用的主要是Kotlin的语法特性。如果用Java可以在Service内部定义一个接口,让Activity实现它,然后通过IBinder对象的方法将Activity的引用传给Service,这样Service就能回调Activity中的方法了,这种方式Java和Kotlin都同时适用。
Service主要应用于以下场景:不需要显示Activity或Fragment也能运行的代码。最典型的是音乐播放器,它可以在手机上部的信息栏中显示相应的播放控制按钮,不需要显示Activity/Fragment也能播放或暂停音乐。
使用IntentService
避免启动过长的操作
默认情况下,服务代码与主线程是同一线程,所以,服务不应该执行过长的操作,否则可能会触发一个ANR(应用程序无响应)错误。
如果Service需要完成一个耗时较长的操作则应该启动一个新的线程完成这个工作。
fun log(msg:String){
Log.d("IntentService",msg)
}
class MyIntentService : IntentService("MyIntentService") {
//把需要在后台线程中执行的代码写到onHandleIntent()方法即可。
//需要的信息可以放到Intent中。
override fun onHandleIntent(intent: Intent?) {
//这里的代码,将在独立的线程中执行
log("onHandleIntent方法运行于线程【${
Thread.currentThread().id}】")
}
override fun onCreate() {
super.onCreate()
log("onCreate方法运行于线程【${
Thread.currentThread().id}】")
}
override fun onDestroy() {
super.onDestroy()
log("onDestroy方法运行于线程【${
Thread.currentThread().id}】")
}
//IntentService在执行完onHandleIntent()方法后会自动结束。
}
IntentService的使用方法与普通Service 一样,直接调用 startService 就能启动它
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
log("主线程【${
Thread.currentThread().id}】")
btnStartService.setOnClickListener {
//启动IntentService
val intent = Intent(this, MyIntentService::class.java)
startService(intent)
}
}
}
在实际开发中,如果没有特殊需求,优先选择从IntentService 类派生。只有 IntentService类不满足需求, 才使用手动创建Thread对象的方式。
文件操作与存取
class MainActivity : AppCompatActivity() {
val fileName = "test.txt"
val externalFileName = "extern_test.txt"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//内部存储的绝对路径
tvInternalStorageInfo.text ="内部存储的绝对路径:\n${
filesDir.absolutePath}"
btnSaveToFile.setOnClickListener {
openFileOutput(fileName, Context.MODE_PRIVATE).use {
outputstream ->
outputstream.write("你好,Android!".toByteArray(Charsets.UTF_8))
}
showToastShort("文件成功保存到$fileName")
}
btnLoadFromFile.setOnClickListener {
openFileInput(fileName).use {
val fileContent = it.bufferedReader(Charsets.UTF_8).use {
it.readText()
}
tvInfo.text = fileContent
}
showToastShort("成功从$fileName 中读取了数据")
}
val fileInfo=getExternalFilesDir(null)?.absolutePath ?: "无法读取外部存储器"
tvExternalStorageInfo.text = "外部存储区的绝对路径:\n${
fileInfo}"
btnSaveToExternal.setOnClickListener {
if (isExternalStorageWritable()) {
val externalFile = File(getExternalFilesDir(null), externalFileName)
FileOutputStream(externalFile).use {
it.write("保存到外部存储器中:Hello, Android".toByteArray(Charsets.UTF_8))
}
showToastShort("己经将数据写入到外部存储器上的 $externalFileName 文件中")
}
}
btnLoadFromExternal.setOnClickListener {
if (isExternalStorageReadable()) {
val externalFile = File(getExternalFilesDir(null), externalFileName)
FileInputStream(externalFile).use {
val fileContent = it.bufferedReader(Charsets.UTF_8).use {
it.readText()
}
tvInfo.text = fileContent
}
showToastShort("成功从外部存储器上的 $externalFileName 文件中读取了数据")
}
}
}
//是否外部存储区可写?
fun isExternalStorageWritable(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}
//是否外部存储区可读?
fun isExternalStorageReadable(): Boolean {
return Environment.getExternalStorageState() in
setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY)
}
}
fun Activity.showToastLong(msg: String) = Toast
.makeText(this, msg, Toast.LENGTH_LONG)
.show()
fun Activity.showToastShort(msg: String) = Toast
.makeText(this, msg, Toast.LENGTH_SHORT)
.show()
写入到内部存储区:
Activity的基类ContextWrapper中定义有一个openFileOutput()方法,传给它一个文件名,就能向这个文件中写入数据。
类似的,有一个openFileInput()方法,可以从文件中读取数据。
写入数据到外部存储区域:
当要存取外部存储区时,需要AndroidManifest.xml
中加入访问权限
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />