当前位置: 代码迷 >> Android >> Android学习系列(3)四大组件之Service详解
  详细解决方案

Android学习系列(3)四大组件之Service详解

热度:122   发布时间:2016-04-24 11:17:45.0
Android学习系列(三)四大组件之Service详解

Service概述

Service作为Android四大组件中的一员,有着非常重要的地位。它没有交互界面,一般用于长期运行在后台来处理一些比较耗时的操作和为其他应用提供服务接口,如消息推送、音乐播放、定位服务、后台数据同步、长连接(如:蓝牙、IM)的数据收发及连接状态变化等。Service分为本地服务和远程服务,本地服务运行在主进程的主线程中,而远程服务则运行在独立进程的主线程中。当然如果在服务里面写了非常耗时的代码时可能会出现ANR的情况,这时你可以在Service在创建一个子线程来处理耗时逻辑。


Service的几个属性

android:enabled 表示该服务是否能后被系统实例化(激活),默认为true;
android:name 表示该服务的名字(完整类名或.xxx.XXService),不可缺少;
android:icon 表示该服务的图标,默认与<application>元素的icon属性值一致;
android:label 表示该服务的名字(可以在手机里面的应用管理里面看到);
android:permission 表示启动或绑定该服务所需的权限,如:android:permission="com.permission.xxx.service",则其他应用要想访问该服务,必须声明权限<uses-permission android:name="com.permission.xxx.service" />;
android:exported 表示能够被外包应用访问,它的默认值依赖与该服务所包含的过滤器,若至少包含了一个过滤器,则意味着该服务可以给外部的其他应用提供服务,因此默认值是true; 否则默认值为false,此时只能在应用程序的内部使用该服务或者具有相同用户ID 的应用程序的组件才能使用该服务;
android:process=":remote",代表在应用程序里需要该service时会自动创建新的进程。而如果是android:process="remote",没有“:”分号的则创建全局进程,不同的应用程序共享该进程。


Service的启动两种启动方式


第一种:startService(intent) 

第一次启动服务时,系统会执行onCreate、onStartCommand(onStart已过时)方法,再次调用startService则只会执行onStartCommand方法。
服务的onCreate方法用于进行一些初始化操作; onStartCommand(Intent intent, int flags, int startId) 方法能够被多次执行,其中intent就是startService(intent) 里面的intent,而startId为服务请求者的唯一编号(若服务没有被进程杀死过,则可以看做是服务的启动次数,也可作为IntentService消息队列里面的消息参数)。
这里重点讲下方法的onStartCommand的常用几个返回值
START_STICKY:服务被杀死系统会尝试重新创建,intent丢失。
E/FirstService.java(10403): [onCreate:23]服务被激活
E/FirstService.java(10403): [onStartCommand:30]intent = Intent { cmp=com.leo.myservice/.FirstService (has extras) } ,flags = 0 ,startId = 1
E/FirstService.java(10492): [onCreate:23]服务被激活
E/FirstService.java(10492): [onStartCommand:30]intent = null ,flags = 0 ,startId = 2
START_NOT_STICKY:服务被杀死系统不会自动重启该服务。
START_REDELIVER_INTENT:服务被杀死系统会尝试重新创建,intent不会丢失。
E/FirstService.java(12336): [onCreate:23]服务被激活
E/FirstService.java(12336): [onStartCommand:30]intent = Intent { cmp=com.leo.myservice/.FirstService (has extras) } ,flags = 0 ,startId = 1
E/FirstService.java(12405): [onCreate:23]服务被激活
E/FirstService.java(12405): [onStartCommand:30]intent = Intent { cmp=com.leo.myservice/.FirstService (has extras) } ,flags = 1 ,startId = 1
销毁服务:stopService(intent)只要指定相应的服务即可:
E/FirstService.java(14390): [onDestroy:39]服务被销毁

第二种bindService(Intent service, ServiceConnection conn, int flags) 

 其中:conn作为与服务连接的一个回调接口ServiceConnection(可通过该接口的onServiceConnected(ComponentName name, IBinder service)方法被传递的service来拿到绑定的服务对象或者Binder子类进而调用服务里的方法),flags一般为Context.BIND_AUTO_CREATE
E/FirstService.java(17091): [onCreate:23]服务被激活
E/FirstService.java(17091): [onBind:17]绑定服务被执行
E/MainActivity.java(17091): [onServiceConnected:68]连接成功
解绑服务:unbindService(ServiceConnection conn)
E/FirstService.java(19794): [onUnbind:45]解绑服务被执行
E/FirstService.java(19794): [onDestroy:39]服务被销毁
【注意】
1. 如果你既调用了bindService又调用了startService,那么调用unbindService方法解绑的时候系统只会执行onUnbind,不会执行onDestroy; 所以如果服务不需要了还要记得stopService关闭服务。
2. 服务还可以调用stopSelf()、stopSelf(int startId)进行内部关闭服务,区别就是:前者直接关闭服务,后者若有多个服务请求可指定startId(可从onStartCommand参数拿到)进行关闭。


Activity与Service交互

通过上文可以知道,咱们只要通过绑定服务的方式,实现ServiceConnection接口的onServiceConnected方法拿到被传递过来的IBinder对象,就可以调用Service(或Binder)里面的方法了。
实例说明:
public class FirstService extends Service {	private FirstBinder mBinder = new FirstBinder();	@Override	public IBinder onBind(Intent intent) {		DebugLog.e("绑定服务被执行");		return mBinder;	}	@Override	public void onCreate() {		super.onCreate();		DebugLog.e("服务被激活");	}	@Override	public int onStartCommand(Intent intent, int flags, int startId) {		DebugLog.e("intent = " + intent + " ,flags = " + flags + " ,startId = "				+ startId);		return super.onStartCommand(intent, flags, startId);	}	@Override	public void onDestroy() {		super.onDestroy();		DebugLog.e("服务被销毁");	}	@Override	public boolean onUnbind(Intent intent) {		DebugLog.e("解绑服务被执行");		return super.onUnbind(intent);	}	@Override	public void onRebind(Intent intent) {		super.onRebind(intent);		DebugLog.e("重新绑定被执行");	}	public void info() {		DebugLog.e("FirstService中的info方法被调用");	}	class FirstBinder extends Binder {		@Override		public String getInterfaceDescriptor() {			return "接口描述";		}		FirstService getService() {			return FirstService.this;		}		public void info() {			DebugLog.e("FirstBinder中的info方法被调用");		}	}}


Activity绑定FirstService
public class MainActivity extends Activity {	private FirstService mService;	@Override	protected void onCreate(Bundle savedInstanceState) {		super.onCreate(savedInstanceState);		setContentView(R.layout.activity_main);		// 绑定FirstService		Intent intent = new Intent(MainActivity.this, FirstService.class);		bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);	}	public void test01(View view) {		mService.info();	}	private ServiceConnection mServiceConnection = new ServiceConnection() {		@Override		public void onServiceDisconnected(ComponentName name) {			DebugLog.e("断开连接");			mService = null;		}		@Override		public void onServiceConnected(ComponentName name, IBinder service) {			DebugLog.e("连接成功");			mService = ((FirstService.FirstBinder) service).getService();		}	};	@Override	protected void onDestroy() {		super.onDestroy();		DebugLog.e("MainActivity被销毁");		unbindService(mServiceConnection);	}
打印日志:
E/FirstService.java(5032): [onCreate:22]服务被激活
E/FirstService.java(5032): [onBind:15]绑定服务被执行
E/MainActivity.java(5032): [onServiceConnected:40]连接成功
可以看到连接服务成功了,拿到服务后咱们就可以调用FirstService里面的方法了。
打印日志:E/FirstService.java(5032): [info:51]FirstService中的info方法被调用
当然,这里也可以直接调用FirstBinder里面的方法,拿到binder对象即可(mBinder = (FirstService.FirstBinder) service;)
打印日志:E/FirstService.java(6342): [info:65]FirstBinder中的info方法被调用
当不需要该服务的时候,记得要接触绑定。
打印日志:
E/MainActivity.java(6342): [onDestroy:49]MainActivity被销毁
E/FirstService.java(6342): [onUnbind:40]解绑服务被执行
E/FirstService.java(6342): [onDestroy:35]服务被销毁


A应用访问B应用的Service

第一种:通过指定Action启动服务

比如B应用有些数据是需要一段时间后才能检测的到某个(稳定)值或者平均值,A应用需要启动该服务后过段时间才能拿到,若没有其他操作可将其关闭或者服务自行关闭(如:stopSelf()、stopSelf(int startId)等),当然还可以处理其他逻辑简单、数据单一或者定时任务能够形成闭环的情形。
举例说明:咱们可以在B应用manifest文件中的<Service>元素中添加一个过滤器:
<service	android:name="com.leo.myservice.FirstService"	android:permission="com.permission.xxx.service" >	<intent-filter>		<action android:name="com.intent.action.XxxService" />	</intent-filter></service>
然后咱们就可以在A应用中声明权限后访问B应用的服务了:context.startService(new Intent("com.intent.action.XxxService"));

第二种:通过AIDL绑定服务

AIDL:Android接口定义语言(Android Interface definition language),它是一种面向接口的描述语言,作用就是为其他应用提供一些服务接口。一般客户端(A应用)通过绑定该远程服务(B应用)的方式拿到Binder对象来进行进程间通信IPC(interprocess communication)的。为了建立与远程服务的连接(至于连接建立的内部机制,不在本篇的讨论范围内),与之前一样,咱们需要实现ServiceConnection接口的onServiceConnected(ComponentName name, IBinder service)方法,只不过这里的参数service不是直接强制转换,而是通过mService = IAIDLService.Stub.asInterface(service);来拿到远程服务(其中IAIDLService.Stub实际就是Binder的子类,咱们只要实现这个IAIDLService.Stub抽象类中声明的所有方法,然后在onBind方法里返回它的对象即可; asInterface的作用就是将Binder对象转化成IAIDLService接口实例)。
举例说明:
B应用(服务端):
创建一个IAIDLService.aidl文件
package com.leo.aidl;interface IAIDLService{	// 获取步数	int getSteps();}
新建一个服务AidlService,并实现.aidl文件中声明的所有抽象方法,然后onBind方法返回一个Binder实例。
public class AidlService extends Service {	@Override	public void onCreate() {		super.onCreate();		DebugLog.e("远程服务被激活");	}	@Override	public void onDestroy() {		super.onDestroy();		DebugLog.e("远程服务被销毁");	}	@Override	public boolean onUnbind(Intent intent) {		DebugLog.e("解绑远程服务");		return super.onUnbind(intent);	}	@Override	public IBinder onBind(Intent intent) {		DebugLog.e("绑定远程服务");		return mBinder;	}	private final IAIDLService.Stub mBinder = new IAIDLService.Stub() {		@Override		public int getSteps() throws RemoteException {			return 10000;		}	};}
接着在manifest文件中注册该远程服务:
<service	android:name="com.leo.myservice.AidlService"	android:exported="true" >	<intent-filter>		<action android:name="com.intent.action.remoteService" />	</intent-filter></service>
A应用(客户端)  导入B应用中的.aidl文件,且aidl接口文件所在的包与服务端的必须相同
public class MainActivity extends Activity {	private IAIDLService mService;	@Override	protected void onCreate(Bundle savedInstanceState) {		super.onCreate(savedInstanceState);		setContentView(R.layout.activity_main);		// 绑定远程服务XxxService		Intent intent = new Intent("com.intent.action.remoteService");		bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);	}		// Button监听	public void test01(View view) {		try {			int steps = mService.getSteps();			DebugLog.e("steps = " + steps);		} catch (RemoteException e) {			e.printStackTrace();		}	}	private ServiceConnection mServiceConnection = new ServiceConnection() {		@Override		public void onServiceDisconnected(ComponentName name) {			DebugLog.e("断开连接");			mService = null;		}		@Override		public void onServiceConnected(ComponentName name, IBinder service) {			DebugLog.e("连接成功");			mService = IAIDLService.Stub.asInterface(service);		}	};	@Override	protected void onDestroy() {		super.onDestroy();		unbindService(mServiceConnection);	}}
服务端打印日志:
E/AidlService.java(27821): [onCreate:16]远程服务被激活
E/AidlService.java(27821): [onBind:33]绑定远程服务
客户端打印日志:
E/MainActivity.java(26711): [onServiceConnected:47]连接成功
接着点击Button,可以看到步数也被打印了:
E/MainActivity.java(26711): [test01:31]steps = 10000
说明客户端调用服务端的方法成功了。
当咱们不需要该服务时,记得调用unbindService(mServiceConnection)解绑
E/AidlService.java(27821): [onUnbind:27]解绑远程服务
E/AidlService.java(27821): [onDestroy:22]远程服务被销毁
【使用情景】当有多个应用程序要与服务端进行通讯时,即服务端需要接收来自不同应用的多线程请求时需要使用AIDL。

IntentService

 在讲IntentService之前,先来了解一下Handler和HandlerThread。
Handler的作用是把消息加入特定的(Looper)消息队列中,并分发和处理该消息队列中的消息。
 一般会使用Handler handler = new Handler()创建Handler, 这样创建的handler是在主线程即UI线程下的Handler,默认与UI线程绑定,在UI线程处理,所以它不能做太耗时的操作,否则会出现ANR。
HandlerThread本质就是个Thread。与普通Thread的差别就在于,它有自己的looper(包含消息队列),可以让Handler在自己的线程中分发和处理消息,从而避免在出现anr的情况。它的创建步骤如下:
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");// 创建HandlerThread(指定线程名:IntentService[xxService])
thread.start();// 启动HandlerThread 
mServiceLooper = thread.getLooper();//获取HandlerThread中的mLooper成员
mServiceHandler = new ServiceHandler(mServiceLooper);//绑定HandlerThread

IntentService就是通过绑定HandlerThread在自己的线程中分发和处理消息的。
具体流程:启动服务startService(intent),onStart(intent, startId)被调用,intent被传入、startId由系统生成(这个里可以看做是请求服务的次数),而后mServiceHandler将消息(携带这两个参数)发出,由咱们自己的服务(继承了IntentService)的onHandleIntent方法来处理,非常方便,等消息队列中的挨个(阻塞式地)处理完所有消息后会执行stopSelf(startId)方法。
值得注意的是:消息处理完后,在服务销毁时先执行mServiceLooper.quit(),这个方法会清空所有消息(无论是延时消息还是非延时消息)。
总的来说:IntentService这与Service的最大区别就是IntentService它可以在自己的线程里处理非常耗时的操作,而且还可以挨个的处理(即一个之后再处理第二个)多个请求,处理完后将自毁服务。

下面举例说明:
public class FirstIntentService extends IntentService {	@Override	public void onCreate() {		super.onCreate();		DebugLog.e("服务被激活");	}	public FirstIntentService() {		super("MyIntentService");	}	int i = 0;	@Override	protected void onHandleIntent(Intent intent) {		DebugLog.e("pid = " + Thread.currentThread().getId());		DebugLog.e("任务执行开始...");		i = 0;		try {			while (i < 3) {				Thread.sleep(1000);				i++;				DebugLog.e("i = " + i + "s");			}		} catch (InterruptedException e) {			e.printStackTrace();		}		DebugLog.e("任务执行完成...");	}	@Override	public void onDestroy() {		super.onDestroy();		DebugLog.e("服务被销毁");	}}
MainActivity添加两个Button进行测试:一个启动服务、一个测试服务是否存在
public class MainActivity extends Activity {	@Override	protected void onCreate(Bundle savedInstanceState) {		super.onCreate(savedInstanceState);		setContentView(R.layout.activity_main);		DebugLog.e("pid = " + Thread.currentThread().getId());	}	public void button1(View view) {		startService(new Intent(this, FirstIntentService.class));	}	public void button2(View view) {		boolean isServiceRunning = ServiceUtils.isServiceRunning(this,				FirstIntentService.class.getName());		DebugLog.e("isServiceRunning = " + isServiceRunning);	}}
点两次按钮,打印日志如下:
E/MainActivity.java(19249): [onCreate:18]pid = 1E/FirstIntentService.java(19249): [onCreate:13]服务被激活E/FirstIntentService.java(19249): [onHandleIntent:24]pid = 2236E/FirstIntentService.java(19249): [onHandleIntent:25]任务执行开始...E/FirstIntentService.java(19249): [onHandleIntent:31]i = 1sE/FirstIntentService.java(19249): [onHandleIntent:31]i = 2sE/FirstIntentService.java(19249): [onHandleIntent:31]i = 3sE/FirstIntentService.java(19249): [onHandleIntent:36]任务执行完成...E/FirstIntentService.java(19249): [onHandleIntent:24]pid = 2236E/FirstIntentService.java(19249): [onHandleIntent:25]任务执行开始...E/FirstIntentService.java(19249): [onHandleIntent:31]i = 1sE/MainActivity.java(19249): [button2:28]isServiceRunning = trueE/FirstIntentService.java(19249): [onHandleIntent:31]i = 2sE/FirstIntentService.java(19249): [onHandleIntent:31]i = 3sE/FirstIntentService.java(19249): [onHandleIntent:36]任务执行完成...E/FirstIntentService.java(19249): [onDestroy:42]服务被销毁E/MainActivity.java(19249): [button2:28]isServiceRunning = false
从日志中可以看到:onHandleIntent中的线程ID与主线程中的是不一样的,这样验证了IntentService消息处理是在非UI线程中进行的,而且是在同一线程中执行; 同时消息一旦处理完服务也随之被销毁了。

总结:

以上是笔者从项目中总结出来的一些心得,花了两天时间整理了一下思路,若有不对的地方还请指正。







  相关解决方案