Android学习羁绊之Service

Posted 姓chen的大键哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android学习羁绊之Service相关的知识,希望对你有一定的参考价值。

Service(服务)是android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

服务依赖于创建服务时所在的应用程序进程。当某个应用程序进程被终止时,所有依赖于该进程的服务也会停止运行

服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的,需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况

文章目录

Android多线程编程

线程的基本用法

Android多线程编程与Java多线程编程基本都是使用相同的语法。创建线程有以下几种方式:

  1. 新建一个类继承Thread类并重写run() 方法
public class MyThread extends Thread 
	@Override
	public void run() 
		// 处理具体的逻辑
	

要启动这个线程,通过如下方式:

new MyThread().start();
  1. 实现Runnable接口,并重写run() 方法
public class MyThread implements Runnable 
	@Override
	public void run() 
	// 处理具体的逻辑
	

要启动这个线程,通过如下方式:

MyThread myThread = new MyThread();
new Thread(myThread).start();
  1. 通过匿名内部类的方式创建线程
new Thread(new Runnable() 
	@Override
	public void run() 
		// 处理具体的逻辑
	
).start();

异步消息处理机制

Android不允许在子线程中进行UI操作,若要在子线程中进行UI操作,则需要使用Android提供的异步消息处理机制来实现。

基本用法

使用异步消息处理机制在子线程中更新UI

//创建Handler对象,并重写handleMessage方法
private Handler handler = new Handler() 
	public void handleMessage(Message msg) 
		//在这个方法中进行UI操作
	
;

new Thread(new Runnable() 
	@Override
	public void run() 
		//创建Message对象,并设置要发送的信息
		Message message = new Message();
		message.what = UPDATE_TEXT;
		// 将Message对象发送出去
		handler.sendMessage(message);
	
).start();

异步消息处理机制组成

Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper

  • Message:用于在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。Message提供了what字段来携带一个整型数据,还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段携带一个Object对象。
  • Handler:主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage() 方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage() 方法中
  • MessageQueue:消息队列,主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象
  • Looper:用于管理每个线程中的MessageQueue,调用Looper的loop() 方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage() 方法中。每个线程中只有一个Looper对象。

异步消息处理流程

异步消息处理按一下流程进行:

  1. 在主线程当中创建一个Handler 对象,并重写handleMessage() 方法
  2. 当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去
  3. 发送的消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage() 方法中

整个异步消息处理机制的流程如下图所示

一条Message经过这样一个流程的辗转调用后,从子线程进入到了主线程,从不能更新UI变成了可以更新UI,这就是异步消息处理的核心思想

Activity中的runOnUiThread() 方法其实就是一个异步消息处理机制的接口封装,背后的实现原理与上图相同

AsyncTask

Android还提供了AsyncTask类来在子线程中更新UI,借助AsyncTask,即使对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程

AsyncTask是一个抽象类,想使用它,就必须要创建一个子类去继承它,在继承时可以为AsyncTask类指定3个泛型参数,这3个参数的用途如下:

  • Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用
  • Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
  • Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型

自定义AsyncTask写法如下:

class MyTask extends AsyncTask<Void, Integer, Boolean> 
	@Override
	protected void onPreExecute() 
		//后台任务开始执行之前调用的逻辑
	
	@Override
	protected Boolean doInBackground(Void... params) 
		//后台任务执行逻辑
		return false;
	
	@Override
	protected void onProgressUpdate(Integer... values) 
		···
	
	@Override
	protected void onPostExecute(Boolean result) 
		···
	

自定义AsyncTask还需要重写AsyncTask中的4个方法才能完成对任务的定制。

  • onPreExecute():这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作
  • doInBackground(Params…):这个方法中的所有代码都会在子线程中运行,在这里去处理所有的耗时任务
  • onProgressUpdate(Progress…):当在后台任务中调用了publishProgress(Progress…) 方法后,onProgressUpdate (Progress…) 方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
  • onPostExecute(Result):当后台任务执行完毕并通过return 语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作

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

想要启动这个任务,只需编写以下代码即可:

new MyTask().execute();

Service基本用法

定义Service

新建一个类继承Service

public class MyService extends Service 
	public MyService() 
	
	@Override
	public IBinder onBind(Intent intent) 
		throw new UnsupportedOperationException("Not yet implemented");
	

onBind() 方法是Service中唯一的一个抽象方法,必须要在子类里实现
重写Service中的另外一些方法来处理任务的逻辑

public class MyService extends Service 
	...
	@Override
	public void onCreate() 
		super.onCreate();
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) 
		return super.onStartCommand(intent, flags, startId);
	
	@Override
	public void onDestroy() 
		super.onDestroy();
	

  • onCreate():在服务创建的时候调用
  • onStartCommand():在每次服务启动的时候调用
  • onDestroy():在服务销毁的时候调用

onCreate() 方法是在服务第一次创建的时候调用的,而onStartCommand() 方法则在每次启动服务的时候都会调用

通常情况下,如果希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在onStartCommand() 方法里。而当服务销毁时,我们又应该在onDestroy() 方法中去回收那些不再使用的资源。

要使用Service还需要在AndroidManifest.xml文件中进行注册才能生效:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="com.example.servicetest">
	<application
		android:allowBackup="true"
		android:icon="@mipmap/ic_launcher"
		android:label="@string/app_name"
		android:supportsRtl="true"
		android:theme="@style/AppTheme">
		...
		<service
			android:name=".MyService"
			android:enabled="true"
			android:exported="true">
		</service>
	</application>
</manifest>
  • android:enabled:是否启用这个服务
  • android:exported:是否允许除了当前程序之外的其他程序访问这个服务

启动和停止Service

在Context类中定义了两个方法用于启动和停止Service:

  • 启动:startService(Intent intent);
  • 停止:stopService(Intent intent);

除了调用stopService() 方法来停止Service,还可以用stopSelf() 方法让Service停止

Activity和Service通信

Service可以在启动之后通过某些方式与Activity进行通信。需要借助onBind() 方法,具体步骤如下:

  1. 在自定义Service类中创建一个自定义Binder继承Binder类,通过这个自定义Binder类来对通信内容进行管理
public class MyService extends Service 
	private MyBinder mBinder = new MyBinder();
	class MyBinder extends Binder 
		//定义逻辑相关方法
	
	@Override
	public IBinder onBind(Intent intent) 
		return mBinder;
	
	...

  1. 将要与自定义Service类通信的Activity进行绑定
private ServiceConnection connection = new ServiceConnection() 
	@Override
	public void onServiceDisconnected(ComponentName name) 
	
	@Override
	public void onServiceConnected(ComponentName name, IBinder service) 
	
;
// 绑定服务
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);
// 解绑服务
unbindService(connection);
  • 创建一个ServiceConnection的匿名类,在里面重写了onServiceConnected() 方法和onServiceDisconnected() 方法,这两个方法分别会在活动与服务成功绑定以及活动与服务的连接断开的时候调用
  • 调用bindService() 方法将MainActivity和MyService进行绑定。
public boolean bindService(Intent service, ServiceConnection conn, int flags);

bindService() 方法接收3个参数:

  1. Intent service:自定义构建的Intent对象
  2. ServiceConnection conn:自定义构建的ServiceConnection对象
  3. int flags:标志位,BIND_AUTO_CREATE 表示在活动和服务进行绑定后自动创建服务
    绑定之后会使MyService中的onCreate() 方法得到执行,但onStartCommand() 方法不会执行

用 Intent 来启动 Service,使用显式 Intent 来确保应用的安全性;使用隐式 Intent 启动服务存在安全隐患,因为无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。在Android5.0(API 级别 21)及以上系统,使用隐式Intent 调用 bindService(),系统会抛出异常。不要在<service>标签中设置 IntentFilter

Service的生命周期

在项目的任何位置调用了Context的startService() 方法,相应的Service就会启动起来,并回调onStartCommand() 方法。

如果这个Service之前还没有创建过,onCreate() 方法会先于onStartCommand() 方法执行。Service启动了之后会一直保持运行状态,直到stopService()stopSelf() 方法被调用。每调用一次startService() 方法,onStartCommand() 就会执行一次,但实际上每个Service都只会存在一个实例。不管调用了多少次startService() 方法,只需调用一次stopService()stopSelf() 方法,Service就会停止下来了。

调用Context的bindService() 来获取一个Service的持久连接,这时就会回调Service中的onBind() 方法。这个Service之前还没有创建过,onCreate() 方法会先于onBind() 方法执行。调用方可以获取到onBind() 方法里返回的IBinder 对象的实例,这样就能自由地和Service进行通信了

调用了startService() 方法后,又去调用stopService() 方法,这时Service中的onDestroy() 方法就会执行,表示Service已经销毁。当调用了bindService() 方法后,又去调用unbindService() 方法,onDestroy() 方法也会执行。对一个服务同时调用了startService() 方法和bindService() 方法的,要同时调用stopService()unbindService() 方法,onDestroy() 方法才会执行。

Service进阶

前台服务

Service的系统优先级比较低,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的Service,若希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。

前台服务和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

要使用前台服务需要使用如下代码实现:

public class MyService extends Service 
	...
	@Override
	public void onCreate() 
		super.onCreate();
		Intent intent = new Intent(this, MainActivity.class);
		PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
		Notification notification = new NotificationCompat.Builder(this)
				.setContentTitle("This is content title")
				.setContentText("This is content text")
				.setWhen(System.currentTimeMillis())
				.setSmallIcon(R.mipmap.ic_launcher)
				.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
				.setContentIntent(pi)
				.build();
		startForeground(1, notification);
	
	...

简单来说就是使用构建Notification的方式来使用前台服务,在构建出Notification对象后并没有使用NotificationManager来将通知显示出来,而是调用了Service类中的startForeground() 方法。调用startForeground() 方法后就会让Service变成一个前台服务,并在系统状态栏显示出来

public final void startForeground(int id, Notification notification);

这个方法两个参数:

  • int id:Notification的id,类似于notify() 方法的第一个参数
  • Notification notification:自定义构建出的Notification对象

IntentService

一个比较标准的Service可以写成如下形式:

public class MyService extends Service 
	...
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) 
		new Thread(new Runnable() 
			@Override
			public void run() 
				// 处理具体的逻辑
				stopSelf();
			
		).start();
		return super.onStartCommand(intent, flags, startId);
	

调用stopSelf() 方法就可以让服务在执行完毕之后自动停止。

在开发过程中难免会忘记开启线程,或者忘记调用stopSelf() 方法,针对这两种情况,Android官方提供了IntentService类。
新建一个类继承自IntentService

public class MyIntentService extends IntentService 
	public MyIntentService() 
		// 调用父类的有参构造函数
		super("MyIntentService");
	
	@Override
	protected void onHandleIntent(Intent intent) 
		//相关逻辑
	
	@Override
	public void onDestroy() 
		super.onDestroy();
	

首先要提供一个无参的构造函数,并且必须在其内部调用父类的有参构造函数。然后要在子类中去实现onHandleIntent() 这个抽象方法,在这个方法中可以去处理一些具体的逻辑,这个方法已经是在子线程中运行的,根据IntentService的特性,这个Service在运行结束后应该是会自动停止

以上是关于Android学习羁绊之Service的主要内容,如果未能解决你的问题,请参考以下文章

Android学习羁绊之Material Design

Android学习羁绊之多媒体开发

Android学习笔记之Service

Android 四大组件之Service学习

Android:安卓学习笔记之Service 的简单理解和使用

Android:安卓学习笔记之Service 的简单理解和使用