服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使当程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。不过需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。另外,也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
线程的基本用法
Android 多线程编程其实并不比 Java 多线程编程特珠,基本都是使用相同的语法。比如说,定义一个线程只需要新建一个类继承自 Thread,然后重写父类的 run()方法,并在
里面编写耗时逻辑即可
class MyThread extends Thread {
@Override
public void run() {
// 处理具体的逻辑
}
}只需要 new 出 MyThread 的实例,然后调用它的 start()方法,这样 run()方法中的代码就会在子线程当中运行了
new MyThread().start();
用继承的方式耦合性有点高,更多的时候我们都会选择使用实现 Runnable接口的方式来定义一个线程
class MyThread implements Runnable {
@Override
public void run() {
// 处理具体的逻辑
}
}MyThread myThread = new MyThread();
new Thread(myThread).start();可以看到, Thread 的构造函数接收一个 Runnable 参数, 而我们 new 出的 MyThread正是一个实现了 Runnable 接口的对象,所以可以直接将它传入到 Thread 的构造函数里。接着调用 Thread 的 start()方法,run()方法中的代码就会在子线程当中运行了如果你不想专门再定义一个类去实现 Runnable 接口, 也可以使用匿名类的方式,这种写法更为常见
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
}
}).start();
在子线程中更新 UI
Android 的 UI 也是线程不安全的。也就是说,如果想要更新应用程序里的 UI 元素,则必须在主线程中进行,否则就会出现异常
package com.example.service;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener{
public static final int UPDATE_TEXT=1;
TextView txt;
Button btn;
private Handler handler=new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
txt.setText("Nice");
break;
default:
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt=(TextView) findViewById(R.id.textView1);
btn=(Button) findViewById(R.id.button1);
btn.setOnClickListener(this);
}
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
switch (arg0.getId()) {
case R.id.button1:
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Message message=new Message();
message.what=UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
break;
default:
break;
}
}
}
这里我们先是定义了一个整型常量 UPDATE_TEXT,用于表示更新 TextView 这个动作。然后新增一个 Handler 对象,并重写父类的 handleMessage 方法,在这里对具体的
Message 进行处理。如果发现 Message 的 what 字段的值等于 UPDATE_TEXT,就将TextView 显示的内容改成 Nice to meet you。下面再来看一下 Change Text 按钮的点击事件中的代码。可以看到,这次我们并没有在子线程里直接进行 UI 操作,而是创建了一个 Message(android.os.Message)对象,并将它的 what 字段的值指定为 UPDATE_TEXT,然后调用 Handler 的 sendMessage()方法将这条 Message 发送出去。很快,Handler 就会收到这条 Message,并在handleMessage()方法中对它进行处理。 注意此时 handleMessage()方法中的代码就是在主线程当中运行的了,所以我们可以放心地在这里进行 UI 操作。接下来对 Message 携带的 what 字段的值进行判断,如果等于 UPDATE_TEXT,就将 TextView 显示的内容改成Nice to meet you。
解析异步消息处理机制
Android 中 的 异 步 消 息 处 理 主 要 由 四 个 部 分 组 成 , Message 、 Handler 、MessageQueue 和 Looper。
- Message
Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了 Message 的 what 字段,除此之外还可
以使用 arg1 和 arg2 字段来携带一些整型数据, 使用 obj 字段携带一个 Object 对象。 - Handler
Handler 顾名思义也就是处理者的意思, 它主要是用于发送和处理消息的。 发送消息一般是使用 Handler 的 sendMessage()方法, 而发出的消息经过一系列地辗转处理
后,最终会传递到 Handler 的 handleMessage()方法中
- MessageQueue
MessageQueue 是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一MessageQueue 对象。 - Looper
Looper 是每个线程中的 MessageQueue 的管家, 调用 Looper 的 loop()方法后,就会进入到一个无限循环当中, 然后每当发现 MessageQueue 中存在一条消息, 就会
将它取出,并传递到 Handler 的 handleMessage()方法中。每个线程中也只会有一个Looper 对象。了解了 Message、Handler、MessageQueue 以及 Looper 的基本概念后,我们再来对异步消息处理的整个流程梳理一遍。首先需要在主线程当中创建一个 Handler 对象,并重写 handleMessage()方法。 然后当子线程中需要进行 UI 操作时, 就创建一个 Message对象,并通过 Handler 将这条消息发送出去。之后这条消息会被添加到 MessageQueue的队列中等待被处理, 而 Looper 则会一直尝试从 MessageQueue 中取出待处理消息, 最后分发回 Handler 的 handleMessage()方法中。由于 Handler 是在主线程中创建的,所以此时 handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行 UI 操作了。整个异步消息处理机制的流程示意图如图
使用 AsyncTask
首先来看一下 AsyncTask 的基本用法,由于 AsyncTask 是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们可以为 AsyncTask 类指定三个泛型参数,
- Params
在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用。
2. Progress
后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
- Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。因此,一个最简单的自定义 AsyncTask 就可以写成如下方
class DownloadTask extends AsyncTask