您的位置:首页 > 移动开发 > Android开发

学习笔记(十一)服务

2016-05-03 17:01 393 查看

一. 什么是服务

  服务 Service 是 Android 中实现程序在后台运行的的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。

  服务不会自动开启线程,所有的代码都是默认运行在主线程当中的。我们需要在服务的内部手动创建子线程,去执行耗时的逻辑,否则就有可能出现主线程被阻塞的情况。

二. 线程

1. 线程的基本用法

Thread

新建类继承 Thread 类,然后重写父类的
run()
防范,并在里面编写耗时的逻辑即可:

class MyThread extends Thread{

@Override
public void run(){
//处理具体的逻辑
}
}


启动这个线程:

new MyThread().start();


Runnable

使用继承的方式耦合性有点高,更多的时候我们选择使用实现 Runnable 接口的方式定义一个线程:

class MyThread implements Runnable{

@Override
public void run(){
//处理具体的逻辑
}
}


启动这个线程:

MyThread myThread = new MyThread();
new Thread(myThread).start();


匿名内部类

如果不想专门定义一个类去实现 Runnable 接口,也可以使用匿名内部类的方式,这种写法更为常见:

new Thread(new Runnable(){

@Override
public void run(){
//处理具体的逻辑
}

}).start();


2. 在子线程中更新 UI

  Android 的 UI 是线程不安全的。想要更新应用程序里的 UI,必须在主线程中进行,否则会出现异常。Android 提供了一套异步消息处理机制,完美地解决了在子线程中进行 UI 操作的问题。

为什么还说 Android 的 UI 操作并不是线程安全的?

一个Android程序创建之初,一定会有一个线程呈现的是单线程模型,即 MainThread ,也是在这个主线程里向 UI 组件去分发事件,你的应用也会和 UI 组件去产生交互,所以我们说主线程就是 UI 线程.

UI 线程的作用是什么呢?就是刷新界面对吧?对,它是通过 Android 的 invalidate() 方法去刷新的,,但是 invalidate 不能再非 UI 线程去调用,(原理就是你通过其他线程去调用这个方法,而 UI 线程也在调用这个方法,所以就会导致线程不安全了,而且在 Android 里面,这样做是不被允许的)

所以上结论: Android UI 是通过 invalidate() 方法去刷新界面的,而 invalidate() 方法是线程不安全的,所以 UI 线程就就是不安全的.

作者:黄庆谦

链接:http://www.zhihu.com/question/36283467/answer/75692448

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3. 异步消息处理机制

基本使用方法

  因为不能在子线程中进行 UI 操作,所以要新建一个 Message 对象,然后调用 Handler 的
sendMessage()
方法将这条 Message 发送出去,然后经过辗转的处理,在主线程中的 Handler 接收这条 Message,调用 Handler 的
handleMessage()
方法进行 UI 操作:

public class MainActivity extends AppCompatActivity implements OnClickListener {

private TextView text;
private Button changeText;
public static final int UPDATE_TEXT = 1;//定义一个常量表示更新TestView这个动作

private Handler handler = new Handler(){
public void handleMessage(Message msg){
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);
changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}

@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();
break;
default:
break;
}
}
}


解析异步消息处理机制

  Android 中的异步消息处理主要由四个部分组成,MessageHandlerMessageQueueLooper

Message

Message 是线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。

Handler

Handler 也就是处理者的意思,主要用于发送和处理消息。发送消息一般是使用
sendMessage()
方法,而发出的消息经过一系列的辗转处理后,最后会传递到 Handler 的
handleMessage()
方法中。

MessageQueue

MessageQueue 就是消息队列,它主要是用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待着被处理。每个线程只会有一个 MessageQueue 对象。

Looper

Looper 是每个线程中 MessageQueue 的管家,调用
loop()
方法后,每当发现 MessageQueue 中存在一条消息,它就会将消息取出并传递给 Handler 的
handleMessage()
处理。每个线程也只会有一个 Looper 对象。

流程示意图:



4. 使用 AsyncTask

  为了更加方便我们在子线程中对 UI 进行操作, Android 还提供了一个工具类 AsyncTask ,把异步消息处理机制封装在其中。

  AsyncTask 是一个抽象类,必须要创建一个子类去继承它,在继承时
10c32
我们可以为 AsyncTask 类指定三个泛型参数:

Params

在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用。

Progress

在后台任务执行时,如需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。

Result

指定返回值类型。

经常需要去重写的方法有以下四个:

onPreExecute()

这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作。

doInBackground(Params…)

这个方法中的所有代码都会在子线程中运行,我们在这里去处理所有的耗时任务。但是要注意,这个方法是不可以进行 UI 操作的。如果需要更新 UI 元素,可以调用
publishProgress(Progress...)
方法来完成。

onProgressUpdate(Progress…)

当在后台任务中调用了
publishProgress(Progress...)
方法后,这个方法就会很快被调用。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。

onPostExecute(Result)

当后台任务执行完毕并通过 return 语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作

因此,一个比较完成的自定义 AsyncTask 就可以写成如下形式:

class DownloadTask extends AsyncTask <Void,Integer,Boolean> {

@Override
protected void onPreExecute() {//后台任务开始执行前调用,用于进行一些界面上的初始化操作
ProgressDialog.show();//显示进度对话框
}

@Override
protected Boolean doInBackground(Void... params) {//在子线程中进行一些耗时的操作
try{
while(true){
int downloadProgress = doDownload();//这是一个虚构的方法
publishProgress(downloadPercent);//传入更新ui的数据
if(downloadPercent >= 100){
break;
}
}
}catch(Exception e){
return false;
}
return true;
}

@Override
protected void onProgressUpdate(Integer... values) {//更新UI操作
//在这里更新下载进度
progressDialog.setMMessage("Downloading " + values[0] + "%");

}

@Override
protected void onPostExecute(Boolean aBoolean) {//任务结束
progressDialog.dismiss();//关闭对话进度框
//在这里提示下载结果
if(result){
Toast.makeText(context,"Download succeeded",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
}
}
}


最后,要启用 BackTask,直接写:

new BackTask().execute();


总之,使用 AsyncTask 的诀窍就是,在
doInBackground()
中去执行具体的耗时任务,在
onProgressUpdate()
方法中进行 UI 操作,在
onPostExecute()
方法中去执行一些任务的收尾工作。

三. 服务的基本用法

1. 定义一个服务

四大组件都需要在 AndroidManifest.xml 中进行注册才能生效,因此 Service 也需要注册。

public class MyService extends Service {

@Override
//Service中唯一一个抽象方法,所以必须要在子类实现
public IBinder onBind(Intent intent) {
return null;
}

//以下是每个服务中最常用到的三个方法

@Override
//在服务创建的时候调用,只会在创建时调用一次
public void onCreate() {
super.onCreate();
Log.d("MyService","执行onCreate");
}

@Override
//每次服务启动的时候都会调用,我们应该在这里写入执行任务的代码
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService","执行onStartCommand");
return super.onStartCommand(intent, flags, startId);
}

@Override
//服务被销毁的时候调用,我们应该在这里回收那些不再使用的资源
public void onDestroy() {
super.onDestroy();
Log.d("MyService","执行onDestroy");
}
}


2. 启动和停止服务

startService()
stopService()
都是定义在 Context 类中,所以在活动里可以直接调用。

  

启动一个服务:

Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);


停止一个服务:

Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);//或者在MyService任何一个位置调用 stopSelf() 方法,让服务自己停止


3. 活动和服务进行通信

  我们可以利用
onBind()
方法实现活动和服务之间的通信,使得我们可以在活动中操作服务。比如我们希望在 MyService 里提供一个下载功能,然后在活动中可以决定何时开始下载以及随时查看进度。实现这个功能的思路是创建一个专门的 Binder 对象来对下载功能进行管理。

  

(1)定义内部类 DownloadBinder 继承自 Binder,在子类内部定义可以给关联的活动访问的方法。

(2)实例化这个内部类,在
onBind()
方法里返回这个实例。因为我们前面服务中的
onBind()
方法返回值为 null,这样子是不行的。要想实现绑定操作,必须返回一个实现了 IBinder 接口类型的实例,该接口描述了与远程对象进行交互的抽象协议,有了它才能与服务交互。

public class MyService extends Service {

private DownloadBinder mBinder = new DownloadBinder();

class DownloadBinder extends Binder {

public void startDownload() {
Log.d("MyService", "开始下载");
}

public int getProgress() {
Log.d("MyService", "获取下载进度");
return 0;
}
}

@Override
public IBinder onBind(Intent intent) {
Log.d("MyService", "onBind");
return mBinder;
}
......
}


(3)在活动中,建造一条桥梁,创建一个 ServiceConnection 的匿名类,重写
onServiceDisconnected()
onServiceConnected()
两个方法。其中
onServiceConnected()
方法会接收上面的 IBinder,通过向下转型,得到了 DownloadBinder 的实例,之后我们就可以在活动中根据需要调用 DownloadBinder 中任何 public 方法,实现了活动操作服务。

private MyService.DownloadBinder downloadBinder;
//匿名类
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};


(4)在点击事件调用
bindService()
unbindService()
来绑定和解绑服务。

    @Override
public void onClick(View v){
switch (v.getId()){
case R.id.start:
Intent startIntent = new Intent(this,MyService.class); startService(startIntent);
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);//绑定服务
//BIND_AUTO_CREATE表示在活动和服务进行绑定之后自动创建服务
break;
case R.id.unbind:
if(connection != null){
unbindService(connection);//解绑服务
}
break;
default:
break;
}
}


注意:任何一个 Service 在整个 APP 中都是通用的,即在 APP 内任何 Activity 都可以和该 Service 绑定,并且可以获得相同的 Binder。

四. 服务的生命周期



注:左边的图是被启动的服务(started service)的生命周期,右边的图是被绑定的服务(bound service)的生命周期。

  

  通过实现这些方法,你可以监视两个嵌套的生命周期循环:

整个生命时间,服务从 onCreate() 函数被调用一直到 onDestroy() 函数返回。就像 activity 一样,服务在 onCreate() 函数中做初始化操作,在 onDestroy() 函数中释放剩余的资源。

onCreate() 和 onDestroy() 函数会被所有的服务所调用,无论他是被 startService() 函数创建的还是被 bindService() 函数创建的。

活跃的生命时间,开始于 onStartCommand() 或 onBind() 函数被调用。每个函数都会分别被传入 intent。

如果是被启动的服务,他的活跃生命时间会和整个生命时间一同结束(在 onStartCommand() 函数返回后,服务依然是活跃状态)。如果是被绑定的服务,活跃生命时间会在 onUnbind() 函数返回后结束。

注意:尽管一个启动的服务会被 stopSelf() 函数或 stopService() 函数所停止,但是没有响应的回调函数(不存在 onStop() 回调函数)。所以,当服务被停止销毁的过程中,只有 onDestroy() 一个回调函数会响应。如果 startService() 和 bindService() 都调用了,必须同时调用 unbindService()和 stopService(),才会执行 onDestory() 方法。

参考:http://blog.csdn.net/oracleot/article/details/18818575

五. 服务的更多技巧

1. 前台服务

  因为服务的优先级比较低,当系统内存不足时,Service 就有可能被回收。如果希望 Service 可以一直保持运行的状态,而不会由于系统内存不足导致被回收,就可以考虑使用前台服务。前台服务会有一个一直显示在状态栏的图标,类似于通知,创建方法也和创建通知类似。

  方法很简单,在 Service 的
onCreate()
方法中,使用通知 Notification,不同的是这里调用
startForeground()
方法来启动通知而不是调用
notify()
方法。

@Override
public void onCreate() {
super.onCreate();
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new Notification.Builder(this) //这里没有分号
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("这是标题")
.setContentText("这是内容")
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingIntent)
.build();//分号在这里
startForeground(1, notification);
Log.d("MyService", "执行onCreate");
}


2.使用 IntentService

  服务中的代码都是默认运行在主线程当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现 ANR (Application Not Responding) 程序未响应的情况。默认情况下,在 Android 中 Activity 最长执行时间是 5 秒,BroadcastReceiver 最长执行时间是 10 秒。我们应该在服务的每个具体方法里开启一个子线程,然后在这里面去处理那些耗时的逻辑。

  服务一旦启动之后,就会一直处于运行状态,必须调用
stopSelf()
或者
stopService()
方法才能让服务停止。

  为了可以简单地创建一个异步的,会自动停止的服务,Android 专门提供了一个 IntentService 类。

public class MyIntentService extends IntentService {

//首先要提供一个无参的构造函数,并且必须在其内部调用父类的有参构造函数,
//然后再实现onHandleIntent()这个抽象方法。
public MyIntentService(){
super("MyIntentService");
}

@Override
protected  void onHandleIntent(Intent intent){
//子线程代码
Log.d("MyIntentService","Thread id is " + Thread.currentThread().getId());
}

@Override
public void onDestroy(){
//任务处理完毕后,该Service会自动调用onDestory()方法,自动停止。
super.onDestroy();
Log.d("MyIntentService","执行onDestroy");
}
}


当然,该 Service 也需要注册。

六. 后台执行的定时任务

  Android 的定时任务有两种实现方式,一种是使用 Java API 里的 Timer 类,但是它会受到 CPU 休眠的影响而无法正常运行;另一种是使用 Android 的 Alarm 机制,它有唤醒 CPU 的功能。

Alarm 机制的用法

(1)主要就是借助 AlarmManager 类来实现的。AlarmManager 和 NotificationManager 类似,都是通过调用 Context 的
gerSystemService()
方法来获取实例的。

AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);


(2)接下来调用 AlarmManager 的
set()
方法就可以来设置一个定时任务了。它接收三个参数:

第一个参数是一个整型参数,指定 AlarmManager 的工作类型,有四个值可以选:

ELAPSED_REALTIME 表示让定时任务的触发时间从系统开机开始算起,但不会唤醒 CPU

ELAPSED_REALTIME_WAKEUP 表示让定时任务的触发时间从系统开机开始算起,但会唤醒 CPU

RTC 表示让定时任务的触发时间从 1970 年 1 月 1 日 0 点开始算起,但不会唤醒 CPU

RTC_WAKEUP 表示让定时任务的触发时间从 1970 年 1 月 1 日 0 点开始算起,但会唤醒 CPU

第二个参数就是定时任务触发的时间,以毫秒为单位

SystemClock.elapsedRealtime() 可以获取到系统开机至今所经历时间的毫秒数

SystemClock.currentTimeMillis() 可以获取到 1970 年 1 月 1 日 0 点至今所经历的时间的毫秒数

第三个参数是一个 PendingIntent,这里调用 getBroadcast() 方法来获取一个能够执行广播的 PendingIntent。

int anMinute = 60 * 1000 ;//比如设定一个任务每分钟执行一次,单位为毫秒
long triggerAtTime = SystemClock.elapsedRealtime() + anMinute;
Intent i = new Intent(this,AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,0,i,0);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);


首先,是 LongRunningService 中运行着计时任务;

之后, 1min 之后触发 alarm 中的 pendingIntent,该 pendingIntent 发送一个广播;

广播接收器 AlarmReceiver 接收到广播,onReceiver()运行,又回到 LongRunningService 继续,达到循环。

public class LongRunningService extends Service {

@Override
public IBinder onBind(Intent intent){
return null;
}

@Override
public int onStartCommand(Intent intent,int flags,int startId){
new Thread(new Runnable() {
@Override
public void run() {
Log.d("LongRunningService","执行在" + new Date().toString());
}
}).start();
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anMinute = 60 * 1000 ;
long triggerAtTime = SystemClock.elapsedRealtime() + anMinute;
Intent i = new Intent(this,AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,0,i,0);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
return super.onStartCommand(intent,flags,startId);
}
}


public class AlarmReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent){
Intent i = new Intent(context,LongRunningService.class);
context.startService(i);
}
}


public class MainActivity extends AppCompatActivity {
//打开程序的时候启动LongRunningService
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this,LongRunningService.class);
startService(intent);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android 学习笔记