第十章:后台默默地劳动者——探究服务
后台功能属于四大组件之一,其重要程度不言而喻,那么我们自然要好好学习一下它的用法了
10.1 服务是什么
服务是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另一个应用程序,服务仍然能够保持正确运行。
需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程,当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
服务实际上并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
10.2 Android多线程编程
10.2.1 线程的基本用法
Android多线程编程其实并不比Java多线程编程特殊,基本都是使用相同的语法。
1)新建一个类继承自Thread,然后重写run()方法
class MyThread extends Thread {
@Override
public void run() {
//处理具体的逻辑
}
}
启动线程: new MyThread().start();
2)实现Runnable接口的方式来定义一个线程
class MyThread implements Runnable {
@Override
public void run() {
//处理具体的逻辑
}
}
启动线程:MyThread myThread = new Thread();
new Thread(myThread).start();
3)匿名类的方式去实现Runnable接口
new Thread(new Runnable() {
@Override
public void run() {
//处理具体的逻辑
}
}).start();
10.2.2 在子线程中更新UI
和许多其他的GUI库一样,Android的UI也是线程不安全的。因此如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。
Android提供了一套异步消息处理机制,用来解决在子线程中进行UI操作的问题。
//修改MainActivity中的代码
public class MainActivity extends AppCompatActivity implements View.OnClickListaner {
public static final int UPDATE_TEXT = 1;
private TextView text;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
//对具体的Message信息进行处理
switch (msg.what) {
case UPDATE_TEXT:
//在这里可以进行UI操作
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text); //显示文字
Button changeText = (Button) findViewById(R.id.change_text); //点击按钮实现换文字
changeText.setOnClickListener(this);
}
/*
这里创建了一个Message(android.os.Message)对象,并将它的what字段的值指定为UPDATE_TEXT
然后调用Handler的sendMessage()方法将这条Message发送出去
很快,Handler就会收到这条Message,并在HandlerMessage()方法中对它进行处理(注意这里handlerMessage()方法中的代码就是在主线程中运行的)
*/
@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();
default:
break;
}
}
}
10.2.3 解析异步消息处理机制
Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。
1)Message:Message是在线程之间传递的消息,它可以在内部携带少量的信息,用户在不同线程之间进行交换数据。(eg:Message.what携带的是what字段,还可以携带一些整型数据)
2)Handler:处理者,主要用于发送和处理消息的,发送消息一般是使用Handler的sendMessage方法,经过一些列的辗转后最终传递到Handler的handleMessage方法中。
3)MessageQueue:MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息,这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
4)Looper:每个线程中的MessageQueue的管家,调用Loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法当中。每个线程中也只会有一个Looper对象。
异步消息处理的整个流程:
首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发会Handler的handleMessage()方法中。由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们就可以安心地进行UI操作了。这就是整个异步消息处理机制。
10.2.4 使用AsyncTask
AsyncTask工具更方便在子线程中进行UI操作,它的实现原理也是基于异步消息处理机制的,只是Android帮我们做了很好的封装而已。
AsyncTask的基本用法:
AsyncTask是个抽象类,要使用它的话需要创建一个子类去继承,在继承时可以为AsyncTask指定3个泛型参数,这3个参数的用途为:
1)Params:执行AsyncTask时需要传入的参数,可用于在后台任务中使用。2)Progress:后台执行任务时需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
3)Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值的类型//一个比较完整的AsyncTask如下 class DownloadTask extends AsyncTask<Void,Integer,Boolean> { @Override //这个方法在后台任务开始执行之前调用,进行一些界面上的初始化操作 protected void onPreExcute() { progressDialog.show(); //显示进度对话框 } @Override //这个方法在子线程中运行,在这里去处理所有的耗时任务,处理完成return返回 //这里要处理UI的话,可以用过调用publishProgress方法来完成 protected Boolean doInBackground(Void... params) { try { while(true) { int downloadPercent = doDownload(); //doDownload是一个假方法,用于计算当前的下载速度并返回(虚构的方法) //publishProgress方法通过handler发送信息,在主线程中接收消息的地方会执行到onProgressUpdate()方法去更新UI publishProgress(downloadPercent); if (downloadPercent > = 100) { break; } } } catch (Exception e) { return false; } return true; } @Override //在后台调用publishProgress方法后,会调用这个方法,这个方法中携带的参数就是后台任务中传递过来的参数 protected void onProgressUpdate(Integer... values) { //在这里更新下载进度 progressDialog.setMessage("Downloaded"+values[0]+"%"); } @Override //这个方法是当后台执行完毕并通过return返回语句返回时,来调用的,携带的参数就是返回的数据,可以进行一些UI的操作 protected void onPostExecute(Boolean result) { progressDialog.dismiss(); //关闭进度对话框 //在这里提示下载结果 if (result) { Toast.makeText(context,"Download succeeded",Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show(); } } }