服务是 Android 中实现程序后台的解决方案,它非常适合执行那些不需要和用户交互而且还要求长期运行的任务。不过需要注意的是,服务并不是运行在一个独立的进程当中,而是依赖于创建服务是所在的引用程序进程,当某个应用程序进程被杀掉时,所有依赖于该进程的服务都会停止运行。
另外,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中,即 需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就可能有主线程被阻塞的情况。
1 Android 多线程编程
当我们执行一些耗时操作的时候,比如请求一些网络请求时,服务器未必会立刻响应请求,如果不将这些逻辑放置在子线程当中,就会导致主线程被阻塞。
1.1 线程的基本用法
Android 多线程编程其实与 Java 的多线程都是使用相同的语法,即定义一个线程只需创建一个类继承自 Thread
,并重写 run
方法,里面就可以编写耗时逻辑。如:
class MyThread extends Thread {
@override
public void run() {
// 具体逻辑
}
}
然后 new
出 MyThread
实例,再调用 start()
方法即可,如:
new MyThread().start();
可是使用继承的方式导致的 耦合性 高,所以更多时候会使用实现 Runnable
接口的方式,如:
class MyThread implements Runnable {
@Override
public void run() {
// 处理具体逻辑
}
}
此时启动的线程的方式就如下:
MyThread myThread = new MyThread();
new Thread(myThread).start();
1.2 异步消息处理方法
Android 的 UI 是线程不安全的,即如果想要更新应用程序中的 UI 元素就需要在主线程中进行,否则会出现异常,所以就需要使用使用一些异步消息处理的使用方法。
1.2.1 异步消息处理机制
Android 中的异步消息处理主要由 4 个部分组成,分别是:
-
Message
:Message
是在线程之间传递的消息,可以携带少量的消息,用于在不同的线程中交换消息,消息的携带可以放置在Message
的what
字段、arg1
字段、arg2
字段(这三个字段携带整型数据)和obj
字段(携带Object
对象) -
Handler
:Handler
主要用于发送和处理消息,其中sendMessage()
方法用来发送消息,handleMessage()
方法用来处理信息 -
MessageQueue
:消息队列,用来存放所有通过Handler
发送的消息,这些消息会存在于消息队列中,等待被处理,而每个线程中只有一个MessageQueue
对象 -
Looper
:每个线程中的MessageQueue
的管家,其中的loop()
方法,就会进入无限循环中,此时每当MessageQueue
中存在一个消息就会将它取出并传递到Handler
的handleMessage()
方法中,而每个线程中只有一个Looper
对象
在了解完异步消息处理的组成部分之后,就可以梳理一下它的流程:
- 首先在主线程中创建
Handler
对象,并重写handleMessage()
方法 - 创建子线程,当子线程需要通知主线程进行某些操作比如 UI 操作时,就创建一个
Message
对象,并通过Handler
对象将该消息发送出去 - 此时,这条消息会被添加到
MessageQueue
队列中等待被处理,而Looper
则会一直尝试从MessageQueue
中取出待处理消息,最后分发到Handler
的handleMessage()
方法中
public class MainActivity extends AppCompatActivity {
// 用于消息处理者识别具体是哪条消息
public static final int UPATE_TEXT = 1;
// 消息处理者,注意需要在主线程中创建 handler 对象
private Handler handler = new Handler() {
public void handleMessage(Message message) {
switch (message.what) {
case UPATE_TEXT:
// 修改 UI
((TextView) findViewById(R.id.text)).setText("1234");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
// 创建线程
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
// 发送消息
Message message = new Message();
// 设置消息
message.what = UPATE_TEXT;
// 可以设置 obj 携带对象
message.obj = (TextView) findViewById(R.id.text);
handler.sendMessage(message);
}
}).start();
}
});
}
}
在上一章中的
runOnUiThread()
方法其实就是一个异步消息处理机制的接口封装
1.2.2 AsyncTask
为了更方便在子线程中进行 UI 操作,Android 还提供了一些工具如 AsyncTask
。AsyncTask
的实现原理基于异步消息处理机制,Android 只是帮忙做了封装。AsyncTask
是一个抽象类,要使用它就必须创建一个子类继承它,继承的时候可以指定 3 个泛型参数:
-
Params
:在执行AsyncTask
时需要传入的参数,可在后台任务中使用 -
Progress
:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位 -
Result
:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型
同时还需要重写 AsyncTask
的 4 个方法才能正常地使用 AsyncTask
:
-
onPreExecute()
:这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作 -
doInBackground(Params...)
:这个方法中的所有代码都会在子线程中运行(即该方法不能进行 UI 操作),任务完成后可以通过return
语句返回执行结果,此时如果AsyncTask
的第三个泛型参数设置为void
,则可以不返回任务执行结果 -
onProgressUpdate(Progress...)
:在后台任务中调用publishProgress(Progress...)
方法之后,onProgressUpdate(Progress...)
这个方法很快就会被调用,该方法中携带的参数就是在后台任务中传递过来的,在这个方法中可以对 UI 进行操作 -
onPostExecute(Result)
:当后台任务执行完毕之后并通过return
语句进行了返回时,这个方法就会很快被调用,返回的数据会作为参数传递到此方法中,此时也可以进行 UI 操作
例子代码如下:
/**
* 设置了三个泛型参数,意思是:
* 无需传入参数给后台任务
* 使用整型类型作为进度显示
* 返回布尔类型作为反馈结果
*/
class TestTask extends AsyncTask<Void, Integer, Boolean> {
/**
* 后台任务开始时调用
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.d("后台服务输出台", "后台服务开始");
}
/**
* 后台任务具体逻辑
* @param voids
* @return
*/
@Override
protected Boolean doInBackground(Void... voids) {
Log.d("后台服务输出台", "后台服务执行中。。。");
publishProgress(null);
return null;
}
/**
* 在 doInBackground 方法中调用 publishProgress() 方法后调用,本方法在主线程中执行
* 可以执行 UI 操作
* @param values
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// 执行 UI 操作
((TextView) findViewById(R.id.text)).setText("AsyncTask 更新 UI");
Log.d("后台服务输出台", "返回主线程中执行某些逻辑");
}
/**
* 在 doInBackground 方法执行完毕并通过了 return 语句返回时调用
* @param aBoolean
*/
@Override
protected void onPostExecute(Boolean aBoolean) {
Log.d("后台服务输出台", "后台服务执行完成");
super.onPostExecute(aBoolean);
}
}
调用 new TestTask().execute();
即可启动任务。
2 服务
2.1 创建、启动和停止服务
Android Studio 可以使用快捷方式创建服务 Service
,右键项目点击 new
-> Service
-> Service
即可。
其中弹出框中的 Exported
属性表示是否允许当前程序之外的其它程序访问这个服务,Enabled
属性表示是否启动这个服务。
在 Service
中可以重写三个常用的生命周期方法,分别是:
-
onCreate()
:服务创建时调用,只在服务第一次创建的时候调用 -
onStartCommand()
:服务启动时调用,每次启动服务都会调用 -
onDestory()
:服务销毁时调用,这里可以回收不再使用的资源
public class TestService extends Service {
/**
* 服务创建时调用
*/
@Override
public void onCreate() {
super.onCreate();
Log.d("TestService 服务输出台", "创建服务");
}
/**
* 服务启动时调用
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("TestService 服务输出台", "服务启动");
return super.onStartCommand(intent, flags, startId);
}
/**
* 服务销毁时启动
*/
@Override
public void onDestroy() {
Log.d("TestService 服务输出台", "关闭服务");
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
服务也有着作为 Android 四大组件的共有特点,那就是需要在 AndroidManifest.xml
中进行注册才能生效,不过如果使用快捷方式创建服务,那么 AS 就会自动地帮忙注册,如:
<service
android:name=".services.TestService"
android:enabled="true"
android:exported="true"></service>
而启动和关闭服务主要是借助 Intent
实现,就类似打开新的活动。
首先构建一个 Intent
对象,并传入到 startService()
方法来构建和启动服务,同样的使用 stopService()
来停止服务,startService
和 stopService
方法都是已定义在 Context
类中,所以在活动中可以直接调用者两个方法。
而服务自身也可以在任何位置中调用 stopSelf()
方法能够让自身服务停止下来。
如:
// 开启服务
Intent startIntent = new Intent(MainActivity.this, TestService.class);
startService(startIntent);
// 关闭服务
Intent stopIntent = new Intent(MainActivity.this, TestService.class);
stopService(startIntent);
2.2 活动和服务进行通信
上述代码虽然服务是在活动启动的,但启动活动之后,此时服务或许一直处于运行状态,但活动与服务之间就没有关系了,服务具体运行的逻辑,活动就控制不了。
那如何能在活动中指挥服务呢?
- 首先需要创建一个
Binder
的子类,它用于声明一系列活动需要调用服务运行的逻辑 - 然后重写服务中
onBind
方法了,它就用来返回上面的Binder
子类 实例 - 接着就需要绑定活动和服务了,在绑定之后活动就可以调用
Binder
实例的一系列逻辑了 - 绑定的第一步就是,在创建一个
ServiceConnection
类,重写onServiceConnected()
方法和onServiceDisconnected()
方法,前者会在活动与服务成功绑定时调用,而后者当服务所在进程crash
或者被kill
的时候才会被呼叫,以及断开的时候调用,,在onServiceConnected
方法中可以通过向下转型得到由服务初始化而来的Binder
实例,有了这个实例,就可以实现活动指挥服务了 - 而实际绑定的逻辑就是使用
Context
中声明的bindService
方法即可,该方法接受三个参数,分别是:Intent
对象,ServiceConnection
对象,标志位(传入BIND_AUTO_CREATE
表示在活动和服务进行绑定之后自动创建服务,此时服务的onCreate()
会执行,而onStartCommand()
则不会执行)
在服务类中声明 Binder
类并初始化:
public class TestService extends Service {
// 初始化 binder 实例
private TestBinder testBinder = new TestBinder();
// 声明 binder 子类
public class TestBinder extends Binder {
public void startDownload() {
Log.d("binder 输出台:", "开始模拟下载");
}
public void stopDownload() {
Log.d("binder 输出台", "终止模拟下载");
}
}
/**
* 返回 binder 实例
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
return testBinder;
}
/**
* 服务创建时调用
*/
@Override
public void onCreate() {
super.onCreate();
Log.d("TestService 服务输出台", "创建服务");
}
/**
* 服务启动时调用
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("TestService 服务输出台", "服务启动");
return super.onStartCommand(intent, flags, startId);
}
/**
* 服务销毁时启动
*/
@Override
public void onDestroy() {
Log.d("TestService 服务输出台", "关闭服务");
super.onDestroy();
}
}
绑定服务代码:
public class MainActivity extends AppCompatActivity {
// 存储初始化后的 TestBinder
private TestService.TestBinder testBinder;
// 初始化 ServiceConnection 对象,主要声明绑定服务时的一些生命周期函数
// 在这里可以获取到服务中初始化的 binder 实例
private ServiceConnection connection = new ServiceConnection() {
/**
* 绑定成功
* @param name
* @param service
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获取到服务中的 Binder 对象实例
testBinder = (TestService.TestBinder) service;
// 连接成功后,可以立即调用 Binder 实例的方法
testBinder.startDownload();
}
/**
* 当 service 所在进程 crash 或者被 kill 的时候,onServiceDisconnected 才会被呼叫
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d("服务解绑", "服务解绑了");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button4 = (Button) findViewById(R.id.btn5);
button4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 绑定服务
Intent intent = new Intent(MainActivity.this, TestService.class);
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
Button button5 = (Button) findViewById(R.id.btn6);
button5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 解绑服务
unbindService(connection);
}
});
}
}
注:任何一个服务在整个应用程序范围内都是通用的,即一个服务和一个活动进行绑定之后,还可以和任何一个其他活动进行绑定,绑定之后,这些活动获取到的
Binder
实例都是相同的。
当调用startService()
方法之后,再调用stopService()
方法,此时服务中的onDestroy()
方法就会被执行,类似的,调用bindSerivce()
方法后再调用unbindService()
方法,服务中的onDestroy()
方法也会执行,而一个服务只要被启动或者被绑定之后,就会一直处于运行状态,所以如果服务开启且被绑定,此时就需要调用stopService
和unbindService()
方法,onDestory()
方法才会执行
2.3 前台服务
服务几乎都是后台运行的,同时它的系统优先级低,当系统出现内存不足的情况时,就可能会回收正在后台运行的服务,如果想避免这样的情况发生,可以使用前台服务。它会一直有一个正在运行的图标在系统的状态栏显示,类似于通知的效果。
创建一个前台服务只需在声明服务类时修改它的 onCreate()
方法即可,在方法里创建一个通知并调用 startForeground()
方法,该方法接受两个参数,第一个参数是通知的 id
,第二个就是通知 Notification
对象实例本身了。
@Override
public void onCreate() {
super.onCreate();
Log.d("TestService 服务输出台", "创建服务");
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this, null)
.setContentTitle("this is content title")
.setContentText("this is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pi)
.build();
// 调用 startForeground 将本服务设置为前台服务
startForeground(1, notification);
}
2.4 IntentService
如果想要实现让一个服务在执行完毕后自动停止的功能,可以这么写(只显示 onStartCommand()
方法中的代码):
/**
* 服务启动时调用
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("TestService 服务输出台", "服务启动");
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
// 暂停服务
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
Android 专门提供了一个 IntentService
类,它可以达到简单地创建一个异步、会自动停止的服务。
创建一个 IntentService
类的子类,它需要提供一个无参的构造函数,并在其内部调用父类的有参构造函数,实现 onHandleIntent()
抽象方法,这个方法中的逻辑都是在子线程中运行。
例子代码如下:
public class MyIntentService extends IntentService {
//
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// 打印当前线程的 id
Log.d("MyIntentService", "线程id是:" + Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}
}