用于在后台完成用户指定的操作,为其他组件提供后台服务或监控其他组件的运行状态。
开发人员需要在应用程序配置文件中声明全部的service,使用<service></service>标签。
1. 使用context.startService()
由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
startService的生命周期为:onCreate --> onStart(可多次调用) --> onDestroy
- 正常启动Service
Intent intentRainbow = new Intent(mContext, NewRainbowService.class); getApplication().startService(intentRainbow); getApplication().stopService(intentRainbow); |
- Service中启动Activity
Intent dialogIntent = new Intent(getBaseContext(), YourActivity.class); dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getApplication().startActivity(dialogIntent); |
- Service和Thread的区别?
1) servie是系统的组件,它由系统进程托管(service manager);它们之间的通信类似于client和server,是一种轻量级的 ipc通信,这种通信的载体是binder,它是在linux层交换信息的一种ipc。
2) Thread是由本应用程序托管: Thread是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。
- Service中如何执行耗时操作?
Activity和Service都是在主线程,区别只是有没有前台界面。所以Service只适用于一些无需交互的后台操作。如果直接在service中进行耗时操作,因为在主线程,所以仍然会和activity在主线程操作一个样的超时问题存在,所以好的方式是在service中启动其他的线程去执行耗时的操作。
2. 使用context.bindService()
调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点
启动Service会经历:
context.bindService() -> onCreate() -> onBind() -> Service running onUnbind() -> onDestroy() -> Service stop |
onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。这个时候把调用者和Service绑定在一起;Context退出了,Srevice就会调用onUnbind相应退出。
bindService的生命周期为:onCreate --> onBind(只一次,不可多次绑定) --> onUnbind --> onDestory
- 绑定本地服务(LocalPushService)
1) 在service中创建Binder子类
public final class LocalBinder extends Binder { public LocalPushService getService() { return LocalPushService.this; } } |
2) 在activity中创建ServiceConnection对象
private ServiceConnection mConn= new ServiceConnection() { public void onServiceDisconnected(ComponentName arg0) { mService = null; } public void onServiceConnected(ComponentName name, IBinder service) { mService = ((LocalPushService.LocalBinder)service).getService(); } }; |
3) activity中绑定服务
Start()中: Intent mIntent = new Intent(this,LocalPushService.class); mIntent.putExtra("enter_ezlink", true); startService(mIntent); bindService(new Intent(this,MyService.class),mConn, Context.BIND_AUTO_CREATE); |
onStop()中: unbindService(mConn); |
- activity和services绑定流程:
1. new intent指定和哪个service绑定
2. 开始绑定,传递服务连接处理函数ServiceConnection(),绑定成功后会调用service类中的onBind()函数。
3. onBind()里面返回了一个Binder子类对象,可以用该对象的getServices()方法获得service对象的引用。
4. activity和service绑定成功后,会调用onServiceConnected()函数。此函数中IBinder就是Binder的子类对象。
5. 调用LocalBinder中的方法getService()即可获得service对象的引用。
6. 之后就可以使用该service对象引用调用公共函数了。
- 混合使用startService与bindService时的情况:
- bindService()和startService()无所谓先后
- 在bind的Activity退出的时候,Service会执行unBind方法而不执行onDestory方法。因为有startService方法调用过,所以Activity与Service解除绑定后会有一个与调用者没有关连的Service存在。
- 若调用stopService(),Service的onDestory方法不会立刻执行。因为有一个与Service绑定的Activity,但是在Activity退出的时候,会执行onDestory;如果要立刻执行stopService,就得先解除绑定。
注意:退出Activity时,会执行onUnbind(),但是,再次进入Activity时不会执行onBind()。只有当service销毁(onDestory)之后才会onBind(之前说过,onBind只执行一次)。
如何二次绑定?
需要重写onUnbind(),它的默认返回值是False,当组件与Service解绑后再要绑定时,会调用onBind(),但是这时onBind()又不能用。将onUnbind()的返回值改回True,再绑定时就会走onRebind()。
3. 如何尽量不让Service被杀死
1) 监听广播,通过接收广播,启动service。死而复生
<action android:name="android.intent.action.BOOT_COMPLETED"></action> <!-- 手机开机 --> <action android:name="android.intent.action.USER_PRESENT" /> <!-- 手机唤醒解锁 --> |
2) 使用onStartCommand()
只在startService()时有效,在onCreate()之后触发,告诉系统如何启动服务(如判断是否异常终止后重启)。
A、START_STICKY
在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent。
B、START_NOT_STICKY
在运行onStartCommand后service进程被kill后,并且没有新的intent传递给它。Service将移出开始状态,并且直到新的明显的方法(startService)调用才重新创建。因为如果没有传递任何未决定的intent那么service是不会启动,也就是期间onstartCommand不会接收到任何null的intent。
C、START_REDELIVER_INTENT
在运行onStartCommand后service进程被kill后,系统将会再次启动service,并传入最后一个intent给onstartCommand。直到调用stopSelf(int)才停止传递intent。如果在被kill后还有未处理好的intent,那被kill后服务还是会自动启动。因此onstartCommand不会接收到任何null的intent。
public int onStartCommand(Intent intent, int flags, int startId) { flags = START_STICKY; return super.onStartCommand(intent, flags, startId); } |
手动返回START_STICKY,当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错。
3) android:persistent="true"
在配置文件的<application>标签中添加该属性。将该service提升为系统服务。
4)提升service进程优先级
在AndroidManifest.xml文件中对于Service 的intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播
<service android:name="com.dbjtech.acbxt.waiqin.UploadService" android:enabled="true" > <intent-filter android:priority="1000" > <action android:name="com.dbjtech.myservice" /> </intent-filter> </service> |
5)提升service优先级
Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:
1. 前台进程( FOREGROUND_APP) 2. 可视进程(VISIBLE_APP ) 3. 次要服务进程(SECONDARY_SERVER )
4. 后台进程(HIDDEN_APP) 5. 内容供应节点(CONTENT_PROVIDER) 6. 空进程(EMPTY_APP)
当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以使用startForeground API将service放到前台状态。这样在低内存时被kill的几率更低,但是如果在极度极度低内存的压力下,该service还是会被kill掉。
在onStartCommand方法内
|
注意 在onDestroy里还需要stopForeground(true);
4. 绑定Service的三种实现方式
- 绑定Service的三种实现方式之继承Binder类
适用于不需要IPC的情况,可以接收Service的信息(获取Service中的方法),但不可以给Service发送信息
- 绑定Service的三种实现方式之使用Messeager
适用于需要IPC,但不需要并发(多线程)的情况,可以和service相互通信,但是无法调用Service中的方法。
- 绑定Service的三种实现方式之实现AIDL
适用于需要IPC,且需要并发的情况
- 他们都与IPC(远程)调用有关。
- 本质不同:Binder是一个对象,继承了IBinder对象,你可以借助它来自定义RPC(远程过程调用协议)协议。AIDL是Android提供的接口定义语言,借助它,你可以很轻松地实现IPC通信机制,根据需要灵活定义接口。
- 作用范围不同:如果是在一个应用里实现远程调用,使用Binder即可,没必要使用AIDL。如果涉及到在多个应用程序之间使用IPC通信,并且在服务又有多线程业务处理,这时可以使用AIDL。
1.4.1 Binder
如果服务是你的应用程序所私有的,并且与客户端运行于同一个进程中(通常都是如此),你应该通过扩展Binder类来创建你的接口,并从onBind()返回一个它的实例。客户端接收该Binder对象并用它来直接访问Binder甚至Service中可用的公共(public)方法。
如果你的服务只是为你自己的应用程序执行一些后台工作,那这就是首选的技术方案。不用这种方式来创建接口的理由只有一个,就是服务要被其它应用程序使用或者要跨多个进程使用。
1 绑定本地服务(详见前页)
2 绑定远程服务(即不在同一个进程内),结合Messager实现,可以实现双向通信。
1)在manifestp里配置service属性,允许运行在同一个应用程序的不同进程:android:process=":remote"
2)在activity(client端)中,通过onServiceConnected()获得service端传来的binder,用来构建一个Messenger向service发送消息。调用Messenger的send函数,就可以把Message发送至服务端的Handler。
同时,如果需要服务端回调客户端(往客户端的Handler发消息),则可以在send的Message中设置replyTo,服务端就可以往客户端发送消息了。
3)server端构建一个Messenger,包含一个handler,然后将messenger的binder传给客户端,客户端可以通过handler再构造一个messenger与service通信,消息在handler里面被处理。
1.4.2 Messager
如果你需要接口跨越多个进程进行工作,可以通过Messenger来为服务创建接口。在这种方式下,服务定义一个响应各类消息对象Message的Handler。此Handler是Messenger与客户端共享同一个IBinder的基础,它使得客户端可以用消息对象Message向服务发送指令。此外,客户端还可以定义自己的Message,以便服务能够往回发送消息。这是执行进程间通信(IPC)最为简便的方式,因为Messenger会把所有的请求放入一个独立进程中的队列,这样你就不一定非要把服务设计为线程安全的模式了,即为非线程安全的。实现过程如下:
1 服务端
public class MessengerTestService extends Service { private Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 1: final Messenger callback = msg.replyTo; //获取客户端message中的Messenger,用于回调 try { callback.send(Message.obtain(null, 0)); } catch (RemoteException e) { e.printStackTrace(); } break; } } }; @Override public IBinder onBind(Intent intent) { return new Messenger(mHandler).getBinder(); } } |
2 客户端
public class MainActivity extends Activity { Messenger messenger; Messenger reply; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); reply = new Messenger(handler); Intent intent = new Intent(); intent.setClassName("test.messenger", "test.messenger.MessengerTestService"); bindService(intent, new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) {} @Override public void onServiceConnected(ComponentName name, IBinder service) { messenger = new Messenger(service); } }, Context.BIND_AUTO_CREATE); } public void sendMessage(View v) { Message msg = Message.obtain(null, 1); msg.replyTo = reply; // 设置回调用的Messenger try { messenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { Log.d(TAG, "回调成功"); } }; } |
Messenger与AIDL的异同
1. Messenger本质也是AIDL,只是进行了封装,开发的时候不用再写.aidl文件。
因为不用去写.aidl文件,相比起来,Messenger使用起来十分简单。但前面也说了,Messenger本质上也是AIDL,故在底层进程间通信这一块,两者的效率应该是一样的。
2. 在service端,Messenger处理client端的请求是单线程的,而AIDL是多线程的。
使用AIDL的时候,service端每收到一个client端的请求时,就会启动一个线程(非主线程)去执行相应的操作。而Messenger,service收到的请求是放在Handler的MessageQueue里面,Handler大家都用过,它需要绑定一个Thread,然后不断poll message执行相关操作,这个过程是同步执行的。
3. client的方法,使用AIDL获取返回值是同步的,而Messenger是异步的。
Messenger只提供了一个方法进行进程间通信,就是send(Message msg)方法,发送的是一个Message,没有返回值,要拿到返回值,需要把client的Messenger作为msg.replyTo参数传递过去,service端处理完之后,在调用客户端的Messenger的send(Message msg)方法把返回值传递回client,这个过程是异步的,而AIDL你可以自己指定方法,指定返回值,它获取返回值是同步的。
其实,第二点是有办法解决的,在service端,Messenger的Handler可以只当作一个转发器,不处理请求,只转发请求到相应的处理线程(多是相应的HandlerThread),这样也可以达到异步的效果。
1.4.3 AIDL
1. AIDL简述
Android Interface definition language(AIDL),它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口。(Android 中的IPC通信机制,只适用于Activity和Service)当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL,其他情况下你都可以选择其他方法,如使用Messager,也能跨进程通讯。可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。
2. AIDL实例
1) 创建AIDL文件:
写法跟java代码类似,但是这里有一点值得注意的就是它可以引用其它aidl文件中定义的接口,但是不能够引用你的java类文件中定义的接口。
package com.tpv.xmic.dmc; import com.tpv.xmic.dmc.IPushServiceCallbacks; import com.tpv.xmic.dmc.dlna.DlnaDeviceInfo; interface IPushService{ int playMedia(String path, String title, int type); int stopMedia(); String getDuration(); DlnaDeviceInfo getCurrentDMRDevice(); void setSelectedDMR(in DlnaDeviceInfo info); void regesterCallbacks(IPushServiceCallbacks callback); void unRegesterCallbacks(IPushServiceCallbacks callback); } |
其中引入一个自定义对象和一个回调接口,所以还需要添加相应的AIDL文件。在DlnaDeviceInfo.java的同一个包下添加DlnaDeviceInfo.aidl文件:
package com.tpv.xmic.dmc.dlna; parcelable DlnaDeviceInfo; |
注意到了,要对DLNADeviceInfo对象实现序列化(Parcelable)。
-
代码实现:
- DlnaDeviceInfo
package com.tpv.xmic.dmc.dlna; import android.os.Parcel; import android.os.Parcelable; public class DlnaDeviceInfo implements Parcelable{ public String Name; public String getName() { return Name; } public void setName(String name) { Name = name; } public String toString(){ return "device name:"+Name; } public int describeContents() {return 0; } public void writeToParcel(Parcel dest, int flags) { dest.writeString(Name); } public static final Parcelable.Creator<DlnaDeviceInfo> CREATOR = new Creator<DlnaDeviceInfo>() { public DlnaDeviceInfo[] newArray(int size) { return new DlnaDeviceInfo[size]; } public DlnaDeviceInfo createFromParcel(Parcel source) { DlnaDeviceInfo dlna = new DlnaDeviceInfo(); dlna.Name = source.readString(); return dlna; } }; } |
- IPushServiceCallbacks:
package com.tpv.xmic.dmc; interface IPushServiceCallbacks{ void getRenderListUpdate(); void pushStateUpdate(boolean isEnablePush); } |
2. 如果AIDL文件的内容是正确的,ADT会自动在gen目录下生成一个Java接口文件(IPushService.java)。
3. 建立一个服务类 PushService,实现由AIDL文件生成的Java接口:
public class PushService extends Service{ private List<IPushServiceCallbacks> mCallBacks = new ArrayList<IPushServiceCallbacks>(); private final IPushService.Stub mBinder= new IPushService.Stub() { @Override public int playMedia(String path, String title, int type) throws RemoteException { return 0; } @Override public List<DlnaDeviceInfo> getDMRDevicesList() throws RemoteException { Log.i(TAG , "getDMRDevicesList() "); return null; } @Override public void regesterCallbacks(IPushServiceCallbacks callback)throws RemoteException { if(callback == null) return; if(!(callback instanceof IPushServiceCallbacks)) return; mCallBacks.add(callback); } @Override public void unRegesterCallbacks(IPushServiceCallbacks callback) throws RemoteException { if (callback != null && (callback instanceof IPushServiceCallbacks)) mCallBacks.remove(callback); } }; @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind()"); return mBinder; } …… } |
4. 在引用APP的AndroidManifest.xml文件中配置AIDL服务
<action>标签中android:name的属性值就是客户端要引用该服务的ID,也就是Intent类的参数值。
<service android:name=".PushService" android:exported="true" > <intent-filter> <action android:name="com.tpv.xmic.dmc.PushService" /> </intent-filter> </service> |
5. 客户端导入
1) 导入AIDL文件,放在对应的包名下。包括:IpushService.aidl, DlnaDeviceInfo.aidl ,IpushServiceCallBacks.aidl, 添加对象:
private IPushService mService; private ServiceConnection mMRconnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName arg0) { try { mService.unRegesterCallbacks(mCallBack); } catch (RemoteException e) { e.printStackTrace(); } mService = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IPushService.Stub.asInterface(service); try { mService.regesterCallbacks(mCallBack); // 注册回调函数 } catch (RemoteException e) { e.printStackTrace(); } } }; private IPushServiceCallbacks.Stub mCallBack = new IPushServiceCallbacks.Stub() { @Override public void getRenderListUpdate() throws RemoteException { Log.i(TAG+"--callback", "DMR设备更新"); } @Override public void unEnablePush() throws RemoteException { Log.i(TAG+"--callback", "当前推送功能不可用"); }; }; |
2) 绑定服务:
Intent intent = new Intent("com.tpv.xmic.dmc.PushService"); intent.setClassName("com.tpv.xmic.dmc", "com.tpv.xmic.dmc.PushService"); startService(intent); bindService(intent, mMRconnection, Context.BIND_AUTO_CREATE); |
3) 之后就可以通过mService调用了
为什么要序列化?
Android中实现序列化有两个选择:一是实现Serializable接口(是JavaSE本身就支持的),一是实现Parcelable接口(是Android特有功能,效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于进程间通信(IPC))。实现Serializable接口非常简单,声明一下就可以了,而实现Parcelable接口稍微复杂一些,但效率更高。
Android中Intent传递复杂类型对象有两种方法:
一是Bundle.putSerializable(Key,Object),另一种是Bundle.putParcelable(Key,Object)。
(前者是实现了Serializable接口,而后者是实现了Parcelable接口。)
Serializable的作用是为了保存对象的属性到本地文件、数据库、网络流、rmi以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
- 选择序列化方法的原则
Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据,而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化
- Serializable实现与Parcelabel实现的区别
1)Serializable的实现,只需要implements Serializable 即可,这只是一个标记,系统会自动将其序列化。
2)Parcelabel的实现,不仅需要implement Parcelabel,还需要在类中添加一个静态成员变量CREATOR,这个变量需要实现 Parcelable.Creator 接口。
Parcelable:接口定义
public interface Parcelable { public int describeContents(); // 内容描述接口,基本不用管 public void writeToParcel(Parcel dest, int flags); // 写入接口函数,打包 // 读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板参数传入 // 为了能够实现模板参数的传入,定义Creator嵌入接口,内含两个接口函数返回单个和多个继承类实例 public interface Creator<T> { public T createFromParcel(Parcel source); public T[] newArray(int size); } } |
实现Parcelable步骤
1)implements Parcelable
2)重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从 Parcel容器获取数据
3)重写describeContents方法,内容接口描述,默认返回0就可以
4)实例化静态内部对象CREATOR实现接口Parcelable.Creator
public static final Parcelable.Creator<T> CREATOR |
注:其中public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方法:createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。
简而言之:通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,这个过程需要你来实现,因此写的顺序和读的顺序必须一致。