后台默默的劳动者----服务
服务是什么
- 服务是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期执行的任务,它的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然可以保持运行。
- 服务不是运行在一个独立的线程中,而是依赖于创建服务时所在的应用程序进程,当某个应用程序的进程被杀掉,所有依赖于该进程的服务也会停止运行。
- 注意:不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认在主线程中进行的,这意味着,我们要在服务内部手动创建子线程,来防止出现主线程被阻塞的情况。
Android多线程编程
- 刚才说在服务中要手动创建线程,来防止一些问题的发生,那么接下来就要介绍一下Android的多线程编程。
- Android的多线程和java的多线程编程几乎用的都是相同的语法
2.1 线程的基本用法
2.1.1 建立线程的3种方法
- 第一种:新建一个类MyThread继承Thread,然后重写父类的run()方法,所有耗时的操作在run()方法里做即可,然后只需要new出一个MyThread实例调用start方法即可开启这个线程。
public class MyThread extends Thread {
@Override
public void run() {
super.run();
//在这里进行耗时操作
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread().start(); //在这里开启线程
}
}
- 第二种:选择实现Runnable接口的方式来定义一个线程。当使用这种方法定义线程时,相应启动线程的方式也要有所改变。
public class MyThread implements Runnable {
@Override
public void run() {
//在这里进行耗时操作
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyThread myThread = new MyThread(); //创建MyThread对象
new Thread(myThread).start(); //Thread的构造中接收一个Runnable接口对象,然后调用start方法开启线程。
}
}
- 第三种:使用匿名类的方式创建线程,这种方式比较常见。
new Thread(new Runnable() {
@Override
public void run() {
//在这里书写逻辑
}
}).start();
2.2在子线程中更新UI
- 我们知道Android的UI是线程不安全的,如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。但是有时候我们必须在子线程中执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件,那么这时我们该怎么做呢
2.2.1 运用解析异步消息处理机制
Android异步消息处理机制的组成
- 它主要由4个部分组成:Message,Handler,Looper和MessageQuene。每个的作用分别为:
- Message:他是在线程之间传递信息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。比如:它的内部包含what字段,arg1和arg2字段携带一些整形数据,还有obj字段来携带一个Object对象。
- Handler:顾名思义,这是处理者的意思。它用于发送消息和处理消息。发送消息一般使用他的sendMessage()方法,它发出的消息经过一系列辗转,最后会传递到Handler的handleMessage()方法中。
- MessageQueue:这是一个消息队列。它主要用于存放所有通过Handler发送的消息,这部分消息会一直存放在MessageQueue中等待被处理。每个线程只会有一个MessageQueue对象。
- Looper:这是每个线程中MessageQueue的管家,调用Looper的loop()方法之后,就会进入一个无限循环当中,之后每当发现MessageQueue中存在一个消息,就会把它取出,并传递到Handler的handleMessage()方法中。注: 每个线程中也只有一个Looper对象。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView textView ;
public static final int UPDATE_TEXT = 1;
private Handler handler = new Handler(){
public void handleMessage(Message msg){
switch (msg.what){
case UPDATE_TEXT:
textView.setText("this is change");
break;
default:break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.text_item);
Button change = (Button)findViewById(R.id.change);
change.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.change:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
break;
default:break;
}
}
}
- 一个完整的异步消息处理流程 : 首先在主线程中创建一个Handler对象,并重写handleMessage()方法。然后在子线程中需要进行UI操作时,就创建一个Message对象,然后通过handler对象的sendMessage()方法将message对象发送出去。这个消息会添加到MessageQueue队列中等待被处理,而Looper会一直在MessageQueue中取出待处理消息,最后分发回Handler的handerMessage()方法中。因为Handler是在主线程中创建的,所以我们在handerMessage中可以随意地进行UI操作,不会出现问题。
- 以下为流程示意图:
2.2.2 使用AsyncTask
- AdyncTask是Android提供的从子线程切换到主线程的一个非常好用的工具。它的背后实现原理也是基于异步消息处理机制,只不过Android帮我们做了很好的封装。
2.2.2.1 AsyncTask的基本用法
- AsyncTask是一个抽象类,我们要使用它,就必须创建一个子类去继承他。
- 继承时我们为AsyncTask类指定三个泛型参数分别为Params,Progress,Result。其中Params的用途是:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。Progress用途:在后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。Result用途:当任务执行完毕,对结果进行返回,使用这里的泛型作为返回值。
- 继承之后,我们经常要重写4个方法。分别为
- onPreExecute():这个方法会在后台任务开始执行之前被调用,用于一些页面的初始化操作,比如显示一个进度条对话框等。
- doInBackground():这个方法中的所有代码都会在子线程运行,所以我们应该在这里进行所有耗时的任务,这个方法传入的时Params参数,如果AsyncTask的第三个参数时void,那么我们就不必返回任务执行结果。注意/;在这个方法不可以进行UI操作,如果要更新UI元素,比如反馈当前任务的执行进度,可以调用publishProgress(progress)方法来完成。
- onProgressUpdate(Progress…): 当在后台任务中调用了publishProgress()方法后,这个方法就会被很快调用,该方法携带的参数就是后台传递过来的,这个方法中可以进行UI操作,可以利用参数的数值对界面元素进行相应的更新。
- onPostExecute():当后台任务执行结束,通过return返回之后,这个方法会很快被调用,参数是后台任务的返回值,可以在这里根据返回值来做一些相应的操作,也可以是UI操作。
- 想要启动这个任务,只需要new一个这个类,然后调用execute()方法即可。
new DownloadTask().execute();
- 如下是一个比较完整的AsyncTask自定义:
public class DownloadTask extends AsyncTask<Void, Integer,Boolean> {
@Override
protected Boolean doInBackground(Void... voids) {
try{
while(true){
int downloadPercent = doDownload(); //这是一个虚构的方法
publishProgress(downloadPercent); //传进度,去别的方法里更改UI
if(downloadPercent >= 100){
break;
}
}
}catch (Exception e){
e.printStackTrace();
}
return true;
}
@Override
protected void onPreExecute() {
progressDialog.show(); // 显示一个进度对话框
}
@Override
protected void onPostExecute(Boolean aBoolean) {
progressDialog.dismiss(); //关闭进度框
if(aBoolean){
Toast.makeText(context,"download success",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context,"download failed",Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("Downloaded" + values[0] + "%"); //更新进度
}
}
服务的基本用法
3.1 定义一个服务
- 右击包名,new一个Service,将Exported和Enabled属性勾中,他们的作用分别是表示除了当前程序允许其他程序访问此服务和是否启用这个服务。
- 这个服务是继承自Service的,其中有一个onBind()方法,这个方法我们一会会说。
- 在这个服务中,我们要重写三个方法。
- onCreate():这个方法会在服务创建时调用。
- onStartCommand():这个方法会在每次启用服务的时候调用。
- onDestory():这个方法会在服务销毁时候调用。
- 通常情况,如果我们希望服务一旦启动就去执行某个动作,就可以将逻辑写在onStartCommand()里,而当服务销毁时,我们应该在onDestroy()中回收不用的资源。
3.2 启动的停止服务
- 我们应用Intent对象可以在活动和服务之间来回穿梭,然后在调用startService()和stopService()并传入相对应的intent对象即可启动和停止某服务。如果我们想在服务内部停止这个服务,我们需要在服务中任何一个位置上调用stopSelf()方法,这样这个服务就停止了。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button)findViewById(R.id.start);
Button stopService = (Button)findViewById(R.id.stop);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.start:
Intent intent = new Intent(this,MyService.class);
startService(intent);
break;
case R.id.stop:
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);
break;
default:break;
}
3.3 活动和服务进行通信
- 想让活动和服务的关系紧密一些,就要借助我们之前忽略的onBind()方法了。
- 首先,我们在定义的服务类中创建一个内部类DownloadBinder并继承Binder,在这里面定义想在服务中调用的方法,我们在这里写两个开始下载和查看下载进度的方法。然后在服务类中创建DownloadBinder实例,并在onBind方法中return这个实例即可。
- 接下来我们要在活动中做一些事情,来让他与服务取得联系,我们在活动中先建立一个服务内部类DownloadBinder的对象。在创建一个ServiceConnection匿名类并建立其对象,在里面重写onServiceConnected方法和onServiceDisConnected方法,这两个方法分别在活动与服务成功绑定与断开时候调用。在onServiceConnected方法中,我们通过向下转型可以得到downloadBinder实例,通过这个实例我们可以调用在服务中内部类中写的两个方法。
- 准备工作做好了,接下来,我们在一个点击事件中,构造一个Intent对象,然后调用bindService方法将活动和服务绑定,这个方法接收三个参数,第一个是刚刚建立的intent对象,第二个是前面创建的ServiceConnection实例,第三个是一个标志位,我们传入BIND_AUTO_CREATE,表示绑定之后自动创建服务,这会让服务中的onCreate执行,但onStartCommond不会执行。解绑服务用unbindService传入ServiceConnection实例即可。
在服务中
private DownloadBinder mBinder;
class DownloadBinder extends Binder {
public void startDownload(){
}
public int getProgress(){
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
主活动
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (MyService.DownloadBinder) iBinder;
downloadBinder.getProgress();
downloadBinder.startDownload();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button)findViewById(R.id.start);
Button stopService = (Button)findViewById(R.id.stop);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
Button bindService = (Button)findViewById(R.id.bind);
Button unbindService = (Button)findViewById(R.id.unbind);
bindService.setOnClickListener(this);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.start:
Intent intent = new Intent(this,MyService.class);
startService(intent);
break;
case R.id.stop:
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);
break;
case R.id.bind:
Intent bindIntent = new Intent(this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE);
break;
case R.id.unbind:
unbindService(connection);
break;
default:break;
}
}
}
服务的生命周期
- 服务也是Android四大组件之一,所以他也有他自己的生命周期。它主要的生命周期方法有如下几个:
- onCreate():和Activity的onCreate()方法一样,是第一个被执行的生命周期方法,可以在这做一些初始化操作,并且只会被执行一次;哪怕会启动多次服务或绑定多次服务。启动和绑定状态均会经历该状态 。
- onStartCommond():调用startService()启动服务后,将会执行该方法。一旦执行此方法,服务即会启动并可在后台无限期运行。如果实现此方法,则在服务工作完成后,需要通过调用stopSelf()或stopService()来停止服务。(如果只想绑定服务,则无需实现该方法。)调用一次startService,将会调用一次该方法。
- onBind():调用bindService()绑定服务后,将会执行该方法。在该方法的实现中,必须通过返回IBinder提供一个饥饿哭,供客户端用来与服务进行通信。请务必实现此方法,但如果不希望允许绑定,则应返回null。(使用绑定服务时,一般是用于客户端,如Activity与Service有交互的情况,那么服务端返回的这个IBinder,客户端就会得到一个这样的“遥控器”,可以通过该“遥控器”来控制服务)。
- onUnbind():当所有绑定的客户端解除绑定时,系统将会调用该方法。
- onDestory():当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源。
当组件需要停止一个启动服务时,需要调用stopService();解除绑定服务时,需要调用unbind
Service()。
服务的更多技巧
5.1 使用前台服务
- 因为服务几乎都是在后台运行的,一直以来它都是默默的做着工作。但是服务的系统优先级还是比较低的,当系统内存不足的时候就可能会回收掉正在后台运行的服务,如果想让服务一直运行,可以使用前台服务。
- 前台服务相对于普通服务最大的区别是他有一个正在运行的图标,类似于通知的形式
使用前台服务的步骤
- 在服务类的onCreate()中建立Intent对象,传入前台活动
- 根据intent对象创建一个PendingIntent对象
- 建立一个通知并为通知创建打开会跳转服务的PendingIntent
- 最后调用startForefround()方法,这个方法接收两个参数,第一个是id,类似于notify()方法的第一个参数,第二个参数是构建出来的notification对象。
public void onCreate() {
super.onCreate();
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel("1","My_Channel", NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentTitle("this is title")
.setContentText("this is text")
.setContentIntent(pi)
.build();
startForeground(1,notification);
}
5.2 使用IntentService
- 在开始我们说过,服务中的代码都是默认运行在主线程当中,如果直接在服务中处理一些耗时的逻辑,就很容易出现ANR的情况,这时我们就要用到Android多线程的技术了。但是这种服务一旦启动,就会一直处于运行状态,必须调用stopService()或者stopSelf()方法才会停止。
- 为了可以创建一个异步的,可以自动停止的服务,Android提供了一个IntentService类。
使用IntentService的步骤
- 新建一个类MyIntentService继承IntentService
- 提供一个无参的构造方法,在构造方法中引用父类的构造方法。
- 在子类中实现onHandleIntent()这个抽象方法,在这个方法中实现一些耗时的逻辑,不用担心ANR问题,因为这个方法是在子线程中的。
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
//在这里处理耗时操作
}
@Override
public void onDestroy() {
super.onDestroy();
}
}