文章目录
只看后浪催前浪,当悟新人胜旧人。——释文向《过苕溪》
前言
一个月不学习就相当于一个月没学到知识😏。本文首先回顾一下Java中线程的相关知识,由此引出Android中的异步任务AsyncTask。接着本文总结四大组件里最后两种:服务Service和广播Broadcast,包括它们的作用和使用方法。一、线程的相关知识
1.线程的生命周期
相信各位对Java中线程的相关知识早已滚瓜烂熟了,那现在就结合线程的生命周期回顾一下线程的相关方法。众所周知,通常线程具有新建,就绪,运行,阻塞,终止五个阶段。
查阅Thread的内部枚举值Thread.State,了解一个Java线程在从创建到停止的整个流程:
(1)NEW:(新建阶段)
线程实例对象创建好了,但此线程尚未开始运行。
有两种方法创建一个线程实例,各有优点:
①自定义一个继承Thread的类,实现自己的构造方法,并必须重写Thread类的 run() 方法来实现线程的功能。由于是直接继承而来的,故易于实现对本线程状态的控制,但也不能再继承其他类了。
②自定义一个实现了Runnable接口的类,实现接口的 run() 方法,然后可以创建此类的实例,并通过Thread的构造方法 Thread (Runnable target) 创建实例时作为参数传入。由于是自定义的类来实现接口,故与线程本身耦合不大,有额外的继承能力。
(2)RUNNABLE:(就绪+运行阶段)
线程可运行。在线程实例对象创建好了之后,使用其 start () 方法让自身进入就绪的列表中,等待下一次CPU的调度开始运行。
(3)BLOCKED:(阻塞阶段)
线程阻塞。线程在等待获取监视器锁来访问同步资源时会进入此状态。
(4)TIMED_WAITING:(阻塞阶段)
线程定时等待。 可以调用以下方法之一让线程暂停指定的时间:sleep, join, wait 。
(5)WAITING:(阻塞阶段)
不定时等待状态。 调用以下方法之一让线程处于等待状态:join, wait 。
(6)TERMINATED:(终止阶段)
线程已执行完 run() 方法,进入停止状态。
2.Android中线程之间的通信
当我们通过子线程完成后台的耗时操作之后,需要将操作结果回馈到手机页面的控件里,但Android规定只能由主线程(即UI线程)操控页面上的控件,而不允许其他线程直接操作控件,故需要一种线程间相互传递消息的方式。
实时操作系统中线程之间通信的普遍方式有消息传递,共享内存等。Android系统基于消息传递机制设计了一个简单高效的线程之间的通信方式:Message+Handler模式,一个线程要实现双向通信则必须具备以下四个元素,缺一不可:
(1)消息Message
Message作为通信数据的包裹,作用类似于Intent机制下的Bundle。查看参考文档,其可直接访问的内部成员有:
①public int arg1
②public int arg2:如果通信数据很简单,为整数值,则可以直接赋值arg1和arg2。
③public Object obj:若通信数据复杂,则赋值给obj。
④public Messenger replyTo:可选,可以发送对此消息的回复,用于多进程通信。
⑤public int sendingUid:可选,指示发送消息的uid的字段。
⑥public int what:用户定义的消息标识代码,以便Hnadler可以识别此消息的内容。
创建消息实例的方法有两种:
①使用Message 的构造函数或者Message obtain () ,将从全局消息池中返回一个新的Message实例。
②消息接收所在线程里的Handler调用 obtainMessage(),从全局消息池中返回一个新的Message,然后设置此消息的Handler属性为Message.target = this,比上一个办法更高效。
(2)处理器Handler
Handler用于发送和处理与本线程MessageQueue相关的Message和Runnable。 每个Handler实例都与创建它的线程的MessageQueue相关联,当创建Handler实例时,它将绑定正在创建它的线程的线程/消息队列,此后,通过自身的回调函数来处理Message和Runnable。
创建一个Handler实例有两种方式:使用构造函数创建 或者 使用静态类+弱引用创建。第一种方式代码量少,但存在内存泄漏的风险,第二种方式就没有内存泄漏的风险。在下面的例子里计数线程使用第一种方式创建实例,主线程使用第二种方式。
Handler有两个主要用途:
①执行任务Runnable,其使用到的主要方法有:
final boolean post(Runnable r):将Runnable添加到消息队列中。
final boolean postAtFrontOfQueue(Runnable r):添加到消息队列的开头位置。
final boolean postAtTime(Runnable r, long uptimeMillis)和final boolean postDelayed(Runnable r, long delayMillis):见名知意,Runnable被添加到消息队列中,在指定的时间过后运行。
final void removeCallbacks(Runnable r):删除消息队列中的Runnable。
final void removeCallbacks(Runnable r, Object token):删除消息队列中带有Object 标记的Runnable。
②创建、发送消息和处理消息,其主要方法有:
final Message obtainMessage(int what, Object obj) 和 final Message obtainMessage():见名知意,不同的方式创建消息。
sendMessage(Message) 和 sendMessageAtTime(Message, long):见名知意,不同的方式发送消息。
void handleMessage(Message msg):Handler实例必须实现这个方法才能处理消息。
(3)消息循环Looper
用于为指定线程运行的消息循环,与消息循环交互的大多数是通过本线程里的Handler。 新建的线程默认没有消息循环,需要自己调用 Looper.prepare(); 创建,然后调用 Looper.loop(); 让它处理消息,直到循环停止。
常见用法如下:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// TODO:
}
};
Looper.loop();
}
}
注意loop()方法内部为死循环,即此语句之后的代码都不会执行!故要想在子线程实现循环需要自己想办法,其中一种方法见下面的例子里的计数器线程。
(4)消息队列MessageQueue
管理由Looper派发的消息的队列。 消息不会直接添加到MessageQueue,而是通过与Looper关联的Handler对象添加。
这4个类相互作用如下:
①在线程X初始化阶段,调用Looper的静态方法 prepare ,将创建一个唯一的本线程专用的消息循环Looper和消息队列MessageQueue。
②然后创建一个本线程专有的处理器Handler,重写其 handleMessage 方法,用于消息循环Looper取出消息Message之后可以回调这个方法。即本线程处理其他线程发送而来的消息的具体实现需要在此方法里实现。
③调用Looper的静态方法 loop ,不断的从消息队列中获取消息,然后通知本线程的Handler来实现回调。
④查看源码ActivityThread.java中主线程入口,得知主线程早已调用了 Looper.prepareMainLooper(); 和 Looper.loop(); 故主线程只需创建一个Handler即可与其他线程实现双向通信。
接下来便实现这样一个功能:在子线程中每隔1S时间动态改变一个计数值。在主线程里不断更新页面上的进度条控件来表示计数值。主线程可以控制计数值增大或者减小。
页面控件布局如下:
主线程的主要程序如下:
//设置计数方向的按钮组
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (counterThread != null && counterThread.isRunning){
Message msg = counterThread.counterHandler.obtainMessage();
//设置消息类型
msg.what = MSG_TO_COUNTER_DIR;
//填空消息内容
if (checkedId == R.id.rb_increase){
msg.obj = 1;//设定增加方向为正数
}
else{
msg.obj = -1;
}
//使用需要通信的线程的消息处理器来发送消息
counterThread.counterHandler.sendMessage(msg);
}
}
//启停计数器的开关
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Message msg = new Message();
//设置消息类型
msg.what = MSG_TO_COUNTER_STATUS;
isRunning = isChecked;
//填充消息
msg.obj = isRunning;
if (isRunning){
if (counterThread == null){//首次创建计数器线程
counterThread = new CounterThread();
//在子线程启动消息传递机制
new Thread(counterThread).start();
try {
sleep(100);//等待子线程的counterHandler创建完毕,防止空指针异常
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counterThread.counterHandler.sendMessage(msg);
tv_desc.setText("计数器线程已经开启");
}else{
if (counterThread != null){
counterThread.counterHandler.sendMessage(msg);
tv_desc.setText("计数器线程停止");
}
}
}
// 声明一个弱引用的处理器对象,采用的是第二种方式:静态类+弱引用
private final WeakHandler mainThreadHandler = new WeakHandler(this);
// 定义一个弱引用的处理器,其内部只持有目标页面的弱引用
private static class WeakHandler extends Handler {
// 声明一个目标页面的弱引用
public static WeakReference<MainActivity> mActivity;
public WeakHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
// 在收到消息时触发
public void handleMessage(@NonNull Message msg) {
// 从目标页面的弱引用中获得一个实例
MainActivity act = mActivity.get();
if (act != null) {
if (msg.what == act.MSG_TO_MAIN){//判断消息类型
//取值,然后由此刷新控件,这里是在主线程中运行的
int progress = (int)msg.obj;
act.pb_counter.setProgress(progress);
String desc = String.format("当前进度:%d",progress);
act.tv_desc.setText(desc);
//继续向子线程发送运行消息来达到无限循环效果
Message message = counterThread.counterHandler.obtainMessage();
message.what = act.MSG_TO_COUNTER_STATUS;
message.obj = act.isRunning;
counterThread.counterHandler.sendMessage(message);
}
}
}
}
计数器线程如下:
//计数器线程使用实现Runnable接口的方式
public class CounterThread implements Runnable{
public Handler counterHandler;//消息处理器
private boolean isRunning = false;
private int counter=50;//计数值
private int dir = 0;//计数方向
@Override
public void run() {
//prepare可以创建本线程专属的消息队列和消息循环
Looper.prepare();
//创建本线程的消息处理器,采用第一种方式,通过构造函数创建
counterHandler=new Handler(){
//在此方法处理本线程接收到的消息
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){//判断消息类型
case MSG_TO_COUNTER_DIR://计数方向
dir = (int)msg.obj;
break;
case MSG_TO_COUNTER_STATUS://计数器状态
isRunning = (boolean)msg.obj;
if(isRunning)
{
counter += dir;
Message message = mainThreadHandler.obtainMessage();
message.what = MSG_TO_MAIN;
message.obj = counter;
mainThreadHandler.sendMessage(message);
try {
sleep(1000);//模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
//消息传递机制启动,此语句内部为死循环,之后的代码将不会执行
Looper.loop();
}
最终效果如下:
其中计数线程能循环计数的方法为:当计数线程执行完一次循环后,将计数结果通过消息发送给主线程,主线程在 handleMessage 中处理完消息之后,发送循环命令的消息到计数线程,计数线程在 handleMessage里再次运行循环流程,由此来实现循环计数。
上面代码的缺点很明显:这并不是独立在子线程里完成的循环,而是有主线程的调度才能模拟出来循环效果。显然,这种方式适合于子线程的一个流程下来耗的时间久的情况。
3.异步任务AsyncTask
异步任务Asynctask< Params, Progress, Result>是一个内部封装了线程双向通信模型的的轻量级模板类,即一个内部已创建了子线程和一套消息传递机制,我们只需要按部就班的重写几个方法就可用于处理耗时的操作,然后就可以与主线程双向通信,无需了解其内部具体运作流程,使用简单。接下来将使用这个模板类来代替前文的代码流程。
继承异步任务模板类的子类需要指定3个参数类型:Params ,Progress和 Result 和实现4个方法:onPreExecute , doInBackground , onProgressUpdate和onPostExecute。
(1)继承异步任务模板类
3个参数说明:
①Params:任务启动时的输入参数的类型,可设置为String类型或自定义的数据结构。
②Progress:任务抗行过程中的进度数据的类型,一般设置为 Integer类型。
③Result:任务执行结束的结果的类型,可设置为String类型或自定义的数据结构。
大家都知道模板类只要指定参数的类型即可继承,而异步任务并不是必须三个参数都指定类型才行,要将某个参数标记为未使用,只需将其设置为类型Void:
private class MyTask extends AsyncTask<Void, Void, Void> { ... }
当一个异步任务执行时,它需要回调4个方法:
①void onPreExecute ():在执行异步任务执行之前的主线程(UI线程)上执行,即在doInBackground(Params…) 之前调用。
②Result doInBackground (Params… params):在 onPreExecute() 完成执行后立即在后台线程上调用。需要在后台运行的业务代码、网络请求等耗时异步处理都放在该方法中。变参Params对应 AsyncTask<Params, Progress, Result> execute (Params… params) 方法的输入参数,输出参数对应 void onPostExecute (Result result) 方法的输入参数。
注意,该方法运行于分线程,不能操作界面,其他方法都能操作界画。
在方法中还可以使用 void publishProgress(Progress…) 发布一个或多个进度数据。这些数据发送到在UI线程的 void onProgressUpdate(Progress…) 输入参数中。
③void onProgressUpdate(Progress…):在调用 void publishProgress(Progress…) 后在UI线程上回调。此方法用于在主线程显示在后台任务的进度。
④void onPostExecute (Result result):后台任务完成后在UI线程上调用。 Result 是 Result doInBackground (Params… params) 的返回值,作为参数传递到此方法的输入参数里。
(2)启停异步任务
启动一个异步任务有两种方法:
①AsyncTask<Params, Progress, Result> execute (Params… params) :开始执行异步处理任务。这种方式创建的为一个普通线程,系统资源开销大。
②AsyncTask<Params, Progress, Result> executeOnExecutor (Executor exec, Params… params) 以指定的线程池模式执行任务。 这种方式消耗的系统资源少,推荐使用。AsyncTask内置的线程池模式下两个:
Executor SERIAL_EXECUTOR:表示同步线程池,各任务按照代码调用的先后顺序依次排队等待执行。execute 方法默认使用 SERIAL_EXECUTOR。
Executor THREAD_POOL_EXECUTOR:表示异步线程池,各任务间没有先后顺序。即有可能某任务在后面调用却先执行。
取消异步任务:
通过调用 boolean cancel (boolean mayInterruptIfRunning) 可以取消任务,会在UI线程回调void onCancelled (Result result) ,表示取消任务并返回。取消任务之后但任务可能不会马上停止,若想立即停止处理,则可在doInBackground方法中调用 boolean isCancelled () 来判断。
(3)获取任务状态
AsyncTask.Status getStatus ():
返回此任务的当前状态。其返回值如下:
FINISHED:表示 onPostExecute(Result)已完成。
PENDING:表示该任务尚未执行。
RUNNING:表示任务正在运行。
下面的例子将使用异步任务来代替前文的代码实现相同的功能。
异步任务如下:
public class CounterTask extends AsyncTask<Integer,Integer,Void> {
private int counter;//计数值
private int dir;//计数方向
public CounterTask() {
super();
}
// 准备启动任务
@Override
protected void onPreExecute() {
super.onPreExecute();
counter = dir = 0;
mListener.onBegin();
}
// 这个方法在后台线程运行
@Override
protected Void doInBackground(Integer... integers)
{
//输入的第一个参数为计数值的初始值,第二个为计数方向
counter = integers[0];
dir = integers[1];
while(!isCancelled()){//使用此语句判断异步任务是否取消
counter += dir;
publishProgress(counter);//输出任务进度
try {
sleep(1000);//模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
// 线程已经完成处理
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
mListener.onFinish(aVoid);
}
// 任务处理进度
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
mListener.onUpdate(values[0]);
}
// 线程已经取消
@Override
protected void onCancelled(Void aVoid) {
super.onCancelled(aVoid);
mListener.onCancel(aVoid);
}
private OnProgressListener mListener; // 声明一个进度更新的监听器对象
// 设置进度更新的监听器
public void setOnProgressListener(OnProgressListener listener) {
mListener = listener;
}
// 定义一个进度更新的监听器接口
public interface OnProgressListener {
// 在线程处理结束时触发
void onFinish(Void aVoid);
// 在线程处理取消时触发
void onCancel(Void aVoid);
// 在线程处理过程中更新进度时触发
void onUpdate(int progress);
// 在线程处理开始时触发
void onBegin();
}
}
通过往异步任务里设置一个监听器接口,然后在主线程注册监听器并且实现其回调方法,可以了解异步任务的运行状态。
页面布局不变,主线程中有关控件的监听器的回调方法如下:
//设置计数方向的按钮组
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (counterTask == null)
return;
if (!counterTask.isCancelled()) {
counterTask.cancel(false);
}
//每个异步任务只能被执行一次,故若需要重复执行,则需要重新创建一个全新的任务
counterTask = new CounterTask();
counterTask.setOnProgressListener(this);
if (isRunning){
if (checkedId == R.id.rb_increase) {
dir = 1;//1为增加方向
}
else if(checkedId == R.id.rb_decrease){
dir = -1;//-1为减少方向
}
counterTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
pb_counter.getProgress(),dir);//以同步线程池模式运行任务
}
}
//启停计数器的开关
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
isRunning = isChecked;
if (isRunning){
counterTask = new CounterTask();
counterTask.setOnProgressListener(this);
counterTask.execute(pb_counter.getProgress(),dir);//使用普通的子线程方式运行任务
tv_desc.setText("计数器线程已经开启");
}else{
counterTask.cancel(false);
tv_desc.setText("计数器线程停止");
}
}
@Override
public void onFinish(Void aVoid) {
tv_desc.setText("异步任务完成");
}
@Override
public void onCancel(Void aVoid) {
tv_desc.setText("异步任务取消");
}
@Override
public void onUpdate(int progress) {
pb_counter.setProgress(progress);
tv_desc.setText("异步任务当前进度:"+progress);
}
@Override
public void onBegin() {
tv_desc.setText("异步任务开始");
}
此程序设计思路为:由于异步任务的 doInBackground 方法在子线程中运行,故在这里实现循环代码。异步任务与主线程通信采用的是定义一个监听器OnProgressListener,主线程注册监听器之后就可以得到异步任务的输出信息了。然而我并不知道主线程往运行中的异步任务传递数据的方法━━( ̄ー ̄*|||━━(除了接下来的广播之外的方法),故我这里只有采用保存通信前的异步任务的运行状态,然后根据主线程需要通信的数据和上一个异步任务的状态重新创建一个异步任务。之所以要重新创建,查看开发文档:The task can be executed only once (an exception will be thrown if a second execution is attempted.)即一个异步任务不能被执行两次。
此程序的缺点十分明显:主线程往运行中的异步任务传递数据十分困难。故这种方法适于那些主线程只需要往异步任务传递一次参数然后异步任务循环执行的情况。
二、服务Service
1.服务简介
服务Service是Android四大组件之一。
查看参考文档,类结构如下:
public abstract class Service
extends ContextWrapper implements ComponentCallbacks2
直接子类:
AbstractInputMethodService, AccessibilityService, CallScreeningService。。。。
间接子类:
InputMethodService
提起服务,大家总是会不自觉的在前面加上“后台”二字,没错,见名知意,服务常用于不需要和用户交互的情况下长时间运行的操作或者为其他进程提供功能。但是服务是在与它所属的应用程序在同一进程中的主线程中运行, 这意味着,如果服务要做任何耗时操作,手机页面都会卡住(ANR),所以必须自己创建子线程来配合服务完成耗时工作,或者使用Android已经封装好线程的异步服务IntentService。
它分为系统服务和用户自定义的服务:
(1)系统服务就是上面提到的Service的类结构中它的直接子类和一个间接子类,所有的系统服务的名字都定义在在Context的内部成员里,调用Context的getSystemService方法即可得到指定的系统服务。
(2)用户自定义的服务就是继承Service这个抽象类,然后根据项目需求重写它的方法,接着在配置文件AndroidManifest.xml声明组件即可。
2.服务的生命周期
(1)用户在代码里调用startService将创建或者启动一个服务,系统将检查该服务(如果是创建服务,则创建并回调服务的onCreate方法),然后使用提供的输入参数回调其onStartCommand方法,该服务将在此时持续运行,直到程序中调用stopService或stopSelf。 请注意,对startService的多次调用不会嵌套(尽管会对onStartCommand进行多次回调),因此无论启动多少次,服务只需程序调用stopService或stopSelf停止一次 。
(2)对于已经启动的服务,用户根据从onStartCommand返回的值,可以指定运行两个额外的主要操作模式。也可以使用bindService获取该服务的间接引用。
(3)下面是 Service生命周期有关的回调方法说明:
void onCreate ():服务第一次创建时由系统回调。
int onStartCommand (Intent intent, int flags, int startId):每当用户调 startService(Intent) 显式启动服务时由系统回调,返回值说明如下:
START_STICKY:粘性的服务。如果服务进程被杀掉,就保留服务的状态为开始状态,但不传送Intent对象。随后系统尝试重新创建服务,由于状态为开始状态,因此创建服务后一定会回调onStartCommand 方法。
START_NOT_STICKY:非粘性的服务,如果服务被异常杀掉,则不会自动重启该服务。
START_REDELIVER_INTENT:重传Intent的服务,服务挂掉后,系统自动重启该服务然后传入Intent对象。
IBinder onBind (Intent intent):绑定服务,其返回值相当于一个与服务通信的监听器。
void onRebind (Intent intent):重新绑定,当onUnbind(Intent)取消绑定的服务再次绑定时,回调此方法。
boolean onUnbind (Intent intent):取消绑定,返回true表示本服务可以再次绑定,false表示只能绑定一次。
void onDestroy ():销毁服务,清理它所拥有的任何资源。
3.服务的3种使用方式
服务有三种使用方式:普通启停、立即绑定、延迟绑定,每种方式的生命周期不同。
(1)普通启停
页面布局如下:
普通启停是最简单的用法。首先我们使用鼠标右键使用菜单的方式创建服务NormalService:
接着重写其所有方法,不添加新功能,只对外输出当前方法名字,就像这样:
@Override
public void onCreate() { // 创建服务
Log.d(TAG, "onCreate");
MainActivity.showText("onCreate");
super.onCreate();
}
在MainActivity中实现按钮的点击方法来启停服务:
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_start) { // 点击了启动服务按钮
// 创建一个通往普通服务的意图
mIntent = new Intent(this, NormalService.class);
startService(mIntent); // 启动指定意图的服务
} else if (v.getId() == R.id.btn_stop) { // 点击了停止服务按钮
stopService(mIntent); // 停止指定意图的服务
}
}
依次点击两个按钮,查看其回调的方法有3个:
(2)立即绑定
页面布局和上一个例子差不多。
同样的方式创建服务BindImmediateService,相比之前的普通服务类增加了一个粘合剂对象用于间接获取服务的引用:
// 创建一个粘合剂对象
private final IBinder mBinder = new LocalBinder();
// 定义一个当前服务的粘合剂,用于将该服务黏到活动页面的进程中
public class LocalBinder extends Binder {
public BindImmediateService getService() {
return BindImmediateService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
MainActivity.showText("onBind");
return mBinder;// 绑定服务。返回该服务的粘合剂对象
}
在MainActivity中实现绑定和解绑服务的做法与普通方式不同,先创建一个
ServiceConnection服务连接对象,在里面可以获取服务的引用,然后调用bindService方法或 unbindService方法进行绑定解绑操作。MainActivity主要代码代码如下:
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_start_bind) { // 点击了绑定服务按钮
// 创建一个通往立即绑定服务的意图
mIntent = new Intent(this, BindImmediateService.class);
// 绑定服务。如果服务未启动,则系统先启动该服务再进行绑定
boolean bindFlag = bindService(mIntent, mFirstConn, Context.BIND_AUTO_CREATE);
Log.d(TAG, "bindFlag=" + bindFlag);
} else if (v.getId() == R.id.btn_unbind) { // 点击了解绑服务按钮
if (mBindService != null) {
// 解绑服务。如果先前服务立即绑定,则此时解绑之后自动停止服务
unbindService(mFirstConn);
mBindService = null;
}
}
}
// 声明一个服务对象
private BindImmediateService mBindService;
//定义一个服务连接器对象
private ServiceConnection mFirstConn = new ServiceConnection() {
// 获取服务对象时的操作
public void onServiceConnected(ComponentName name, IBinder service) {
//间接获取服务的引用
mBindService = ((BindImmediateService.LocalBinder) service).getService();
Log.d(TAG, "onServiceConnected");
}
// 无法获取到服务对象时的操作
public void onServiceDisconnected(ComponentName name) {
mBindService = null;
Log.d(TAG, "onServiceDisconnected");
}
};
依次点击按钮,这个方法共回调了4个方法:
(3)延时绑定
页面布局和延迟绑定服务类和上面的类似,不再赘述。
按钮的点击方法实现如下:
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_start) { // 点击了开始服务按钮
// 创建一个通往延迟绑定服务的意图
mIntent = new Intent(this, BindDelayService.class);
startService(mIntent); // 启动服务
} else if (v.getId() == R.id.btn_bind) { // 点击了绑定服务按钮
boolean bindFlag = bindService(mIntent, mFirstConn, Context.BIND_AUTO_CREATE); // 绑定服务
Log.d(TAG, "bindFlag=" + bindFlag);
} else if (v.getId() == R.id.btn_unbind) { // 点击了解绑服务按钮
if (mBindService != null) {
unbindService(mFirstConn); // 解绑服务
mBindService = null;
}
} else if (v.getId() == R.id.btn_stop) { // 点击了停止服务按钮
stopService(mIntent); // 停止服务
}
}
依次点击启动,绑定,解绑,绑定,解绑,停止按钮,回调方法如下:
(4)异步服务IntentService
查看IntentService的发文档:
public abstract class IntentService
extends Service
①IntentService抽象类是Service的子类,该服务具有自己的线程和一套线程间双向通信机制来处理耗时的工作,用户通过startService(Intent)发送携带消息的Intent到IntentService,该服务由此使内部的工作线程处理每个Intent(不会阻塞主线程),并在处理完全部的Intent时自行停止。
②使用IntentService可以不去考虑复杂的线程通信流程和服务自身的生命周期,使用起来非常简单。要使用它,只需要需要继承IntentService并重写onHandleIntent(Intent)方法 ,异步服务只能用普通方式启停,不能绑定。它的子类只要注意:在构造函数里需要指定内部线程的唯一名称。onStartCommand方法要调用父类的onStartCommand来向内部线程传递Intent。耗时操作需要放在onHandleIntent方法里,它是在子线程中运行的,而 onStartCommand方法位于主线程。
三、广播Broadcast
1.广播Broadcast简介
(1)广播Broadcast是Android四大组件之一,它是一种通信机制,用于不同的组件、线程、进程间的通信,是四大组件中使用起来最简单的。
(2)如同现实生活中的广播一样,这个组件包括广播发送者和广播接收者两个部分,发送者将通信数据打包进一个隐式Intent中,通过广播机制发送到系统里,广播接收者可以注册广播监听器来接特定的广播,由此回调相关方法来处理广播,因此广播接收者可以有很多个。
(3)对于发送者来说,广播不需要考虑接收者的情况,接收者只要注册了广播监听器就接收广播,若一个都没有就丢弃此次广播。
对于接收者来说,若不指定过滤条件则会收到各式各样的广播,所以接收者要自行设置过滤条件,才能对指定类型的广播进行解包处理。
2.广播Broadcas使用方式
(1)当不需要跨进程广播,即只限于在本APP内部广播时,借助本地广播管理器LocalBroadcastManager和广播接收器BroadcastReceiver可以在本APP内部安全、有效的传递即时消息,LocalBroadcastManager需要用到的方法如下:
①void registerReceiver(BroadcastReceiver receiver, IntentFilter filter):注册和IntentFilter匹配的本地广播接收器。
②boolean sendBroadcast(Intent intent):发送一条广播。
③void unregisterReceiver(BroadcastReceiver receiver):注销指定BroadcastReceiver。
BroadcastReceiver只用到一个方法:
void onReceive (Context context,Intent intent):当BroadcastReceiver接收Intent广播时回调此方法, 此方法始终在主线程中运行,除非使用registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)将其安排在其他线程上运行。
下面是一个广播程序的模板,在MainActivity中的onStart方法中注册广播接收器,则必须在对应的onStop方法中注销(onResume中注册对应的onPause中注销):
// 声明广播接收器
private UserBroadcastReceiver mReceiver;
// 定义一个广播接收器,用于处理广播事件
private class UserBroadcastReceiver extends BroadcastReceiver {
// 一旦接收到特定条件的广播,马上触发接收器的onReceive方法
public void onReceive(Context context, Intent intent) {
if (intent != null && intent.getAction().equals("唯一标识符")) {
// 从广播消息中取出数据然后处理
//TODO:
}
}
}
@Override
public void onStart() {
super.onStart();
// 创建广播接收器
mReceiver = new UserBroadcastReceiver();
// 创建一个意图过滤器,只处理指定事件来源的广播
IntentFilter filter = new IntentFilter("唯一标识符");
// 注册广播接收器,注册之后才能正常接收广播
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
}
@Override
public void onStop() {
super.onStop();
// 注销广播接收器,注销之后就不再接收广播
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
}
采用这种在代码中注册的广播为动态注册,即不需要在配置文件里进行说明。
(2)广播的范围更广泛时,使用Context的三个方法即可:
void sendBroadcast (Intent intent)
Intent registerReceiver (BroadcastReceiver receiver, IntentFilter filter):
void unregisterReceiver (BroadcastReceiver receiver):类比LocalBroadcastManager的三个方法,它们的使用方式是一样的。
总结
总结了Java中线程的相关知识,异步任务AsyncTask,服务Service和广播Broadcast的作用和使用方法。⊙﹏⊙∥
最后问大家一个问题:你目前目前遇到的最幸运的三件事是什么?