- 服务的运行不依赖于任何的用户界面,因而非常适合于执行那些不需要和用户交互而且长期运行的任务
- 服务不会自动开启线程,所有的代码都默认运行在主线程当中
- 我们需要在服务内部手动创建子线程,并在这里执行具体任务,否则会出现主线程堵塞的情况
一、线程的基本用法
只需要一个类继承Thread
,然后重写父类的run()
就行:
class MyThread extends Thread{
@Override
public void run(){
// 具体逻辑
}
}
怎么启动这个线程呢?new
出实例,.start()
就行
new MyThread().start()
还可以使用继承的方式创建线程:
class MyThread implements Runnable{
@Override
public void run(){
// 具体逻辑
}
}
继承的方式启动线程:
MyThread myThread = new Thread();
new Thread(myThread).start();
或者一口气用匿名类的方式创建启动:
new Thread(new Runnable(){
@Override
public void run(){
// 具体逻辑
}
}).start();
二、在子线程中更新UI
新建一个空项目
day17_AndroidThreadTest
想要更新应用程序里的UI元素,必须在主线程中进行,否则就会出现异常。
1、一个崩溃的例子
主布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/change_text"
android:text="改变文本"/>
<TextView
android:id="@+id/some_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="你好, 呼吸君"
android:textSize="20sp"/>
</RelativeLayout>
修改主活动,使得可以变化问候文本:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button_change = findViewById(R.id.change_text);
textView = findViewById(R.id.some_text);
button_change.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
textView.setText("咋地啦?");
}
}).start();
break;
default:
break;
}
}
}
点击按钮就会奔溃:
2、更正:异步消息机制
可以使用一部消息机制解决这个子线程不能修改UI的问题,示例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView textView;
public static final int UPDATE_TEXT = 1;
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
// 这里进行UI操作
textView.setText("咋滴啦?");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button_change = findViewById(R.id.change_text);
textView = findViewById(R.id.some_text);
button_change.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message); //发送Message对象
}
}).start();
break;
default:
break;
}
}
}
- 定义一个整型常量
UPDATE_TEXT
表示更新TextView
这个动作 - 新增一个
Handler
对象,并重写了父类的handleMessage()
方法,在这里对Message
处理 - 如果发现
Message.what
等于UPDATE_TEXT
,就进行更新TextView
- 在子线程中未直接更新
UI
,而是创建了一个Message(android.os.Message)
对象,并指定了what
字段的值 - 调用
Handler.sendMessage()
将Message
对象发送出去,Handler
收到后会调用handleMessage()
对其处理,此时就切换到主线程了
3、异步消息机制的原理
Android 的异步消息处理主要由4部分组成:
a、Message
在线程中传递的消息,用于在不同线程间交换数据。其常用字段有what
,arg1
,agr2
,obj
等
b、Handler
顾名思义,“处理者”。用于发送sendMessage()
和处理handleMessage()
消息
c、MessageQueue
消息队列,用于存放所有通过Handler
发送的消息,每个线程中只会有一个MessageQueue
对象
d、Looper
是每个线程中MessageQueue
的管家,调用Looper.loop()
会进入消息循环中,将存在消息队列的消息取出一条,传递到handleMessage()
方法中。每个线程只会有一个Looper
对象
说白了,异步消息机制的原理就是:子线程发个通知让主线程更新UI
同理,runOnUiThread()
也是这个原理
4、使用AsyncTask
为了更方便的在子线程中对UI进行操作,Android还提供了一个工具——AsyncTask
,原理和上文了一样,不过封装起来很好用
使用方法:
一、创建一个子类去继承它,继承时可以为Async
类指定三个泛型参数,用途如下:
-
Params
。执行AsyncTask
时需要传入的参数,可用于后台任务 -
Progress
。后台任务执行时,需要在界面上显示任务进度,使用这里的泛型作为进度单位 -
Result
。任务执行完毕,对需要的结果进行返回,使用这里的泛型作为返回值类型
class DownloadTask extends AsyncTask<Void, Integer, Boolean>{
...
}
第一个参数为Void
,表示执行AsyncTask
的时候不需传入参数给后台任务;
第二个参数为Integer
,表示使用整型数据来作为进度显示单位
第三个参数为Boolean
,表示使用布尔参数来反馈执行结果
二、接着需要重写AsyncTask
的几个任务才能完成对任务的定制,经常重写的4个方法:
-
onPreExecute()
在后台任务开始之前调用,用于完成界面上的初始化操作,比如显示进度条对话框 -
doInBackground(Params...)
这个方法中的代码都会在子线程中运行,在这里处理耗时任务。任务一旦完成就可以通过return
语句将执行结果返回,如果第三个泛型参数指定是Void
,就可以不返回执行结果。
这个方法中不可以执行UI操作,要执行UI相关操作,可以调用publishProgress(Progress...)
方法 -
onProgressUpdate(Progress...)
当后台调用了publishProgress(progress...)
,该方法很快就会被调用,其携带的参数就是在后台任务中传递过来的。这个方法可以对UI进行操作 -
onPostExecute(Result)
当后台任务执行完毕并return
后,这个方法就会被调用。返回的数据会传递到此方法中,可以对UI进行操作。
举个例子:
public class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show(); // 显示进度条
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
while (true){
int donloadPercent = download(); // 虚构的方法,返回下载进度
publishProgress(donloadPercent);
if(donloadPercent>=100){
break;
}
}
}catch (Exception e){
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
// 更新下载进度
progressDialog.setMessage("Downloaded " + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean aBoolean) {
progressDialog.dismiss(); // 关闭进度条
if (aBoolean){
Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
}
}
}
简单来说,doInBackground()
执行耗时任务,onProgressUpdate()
执行UI操作,onPostExecute()
任务收尾
启动这个任务:
new DownloadTask().execute();