Android 四大组件之二(Service)
service可以在和多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化,再或者在后台记录你地理信息位置的改变等等,总之服务嘛,总是藏在后头的。
Service是在一段不定的时间运行在后台,不和用户交互应用组件。每个Service必须在manifest中 通过<service>来声明。可以通过contect.startservice和contect.bindserverice来启动。
Service和其他的应用组件一样,运行在进程的主线程中。这就是说如果service需要很多耗时或者阻塞的操作,需要在其子线程中实现。
service的两种模式(startService()/bindService()不是完全分离的):
本地服务 Local Service 用于应用程序内部。
它可以启动并运行,直至有人停止了它或它自己停止。在这种方式下,它以调用Context.startService()启动,而以调用Context.stopService()结束。它可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。不论调用了多少次startService()方法,你只需要调用一次stopService()来停止服务。
用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好。
远程服务 Remote Service 用于android系统内部的应用程序之间。
它可以通过自己定义并暴露出来的接口进行程序操作。客户端建立一个到服务对象的连接,并通过那个连接来调用服务。连接以调用Context.bindService()方法建立,以调用 Context.unbindService()关闭。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。
可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。
生命周期
使用context.startService() 启动Service是会会经历:
context.startService() ->onCreate()- >onStart()->Service running
context.stopService() | ->onDestroy() ->Service stop
如果Service还没有运行,则android先调用onCreate()然后调用onStart();如果Service已经运行,则只调用onStart(),所以一个Service的onStart方法可能会重复调用多次。
stopService的时候直接onDestroy,如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。该Service的调用者再启动起来后可以通过stopService关闭Service。
所以调用startService的生命周期为:onCreate --> onStart(可多次调用) --> onDestroy
使用使用context.bindService()启动Service会经历:
context.bindService()->onCreate()->onBind()->Service running
onUnbind() -> onDestroy() ->Service stop
onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。
所以调用bindService的生命周期为:onCreate --> onBind(只一次,不可多次绑定) --> onUnbind --> onDestory。
在Service每一次的开启关闭过程中,只有onStart可被多次调用(通过多次startService调用),其他onCreate,onBind,onUnbind,onDestory在一个生命周期中只能被调用一次。
而启动service,根据onStartCommand的返回值不同,有两个附加的模式:
1. START_STICKY 用于显示启动和停止service。
2. START_NOT_STICKY或START_REDELIVER_INTENT用于有命令需要处理时才运行的模式。
START_NOT_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。除非程序中Client明确再次调用startService(...)启动此Service。
START_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand(...)方法,但其中的Intent将是null,pendingintent除外。
START_REDELIVER_INTENT:与START_STICKY唯一不同的是,回调onStartCommand(...)方法时,其中的Intent将是非空,将是最后一次调用startService(...)中的intent。
1.Service AndroidManifest.xml 声明
一般而言,从Service的启动方式上,可以将Service分为Started Service和Bound Service。无论哪种具体的Service启动类型,都是通过继承Service基类自定义而来。在使用Service时,要想系统能够找到此自定义Service,无论哪种类型,都需要在AndroidManifest.xml中声明,语法格式如下:
<service android:enabled=["true" | "false"] android:exported=["true" | "false"] android:icon="drawable resource" android:isolatedProcess=["true" | "false"] android:label="string resource" android:name="string" android:permission="string" android:process="string" > . . . </service>
其中,android:exported属性:
意思如下: 该属性用来标示,其它应用的组件是否可以唤醒service或者和这个service进行交互:true可以,false不可以。如果为false,只有同一个应用的组件或者有着同样user ID的应用可以启动这个service或者绑定这个service。默认值根据当前service是否有intent filter来定。如果没有任何filter意味着当前service只有在被详细的描述class name后才会被唤醒。这意味这当前service只能在应用内部使用(因为其它应用不知道这个class name).所以在这种情况下它的默认值为 false.从另一方面讲,如果至少有一个filter的话那么就意味着这个service可以被外部应用使用,这种情况下默认值为true。其实,不只有这个属性可以指定service是否暴露给其它应用。你也可以使用permission来限制外部实体唤醒当前service(详情见permission属性)。
android:name对应Service类名,android:permission是权限声明,android:process设置具体的进程名称。需要注意的是Service能否单独使用一个进程与其启动方式有关,本后下面会给出具体说明。其他的属性此处与其他组件基本相同,不再过多描述。
注:如果自定义Service没有在AndroidManifest.xml中声明,当具体使用时,不会像Activity那样直接崩溃报错,对于显式Intent启动的Service,此时也会给出waring信息“IllegalArgumentException: Service not registered”,有时候不容易发现忘了声明而一时定位不到问题。
2.Started Service
Started Service相对比较简单,通过context.startService(Intent serviceIntent)启动Service,context.stopService(Intent serviceIntent)停止此Service。当然,在Service内部,也可以通过stopSelf(...)方式停止其本身。
1)Started Service自定义
下面代码片段显示的是一个最基本的Started Service的自定义方式:
public class MyService extends Service { public static final String TAG = "MyService"; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); Log.w(TAG, "in onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.w(TAG, "in onStartCommand"); Log.w(TAG, "MyService:" + this); String name = intent.getStringExtra("name"); Log.w(TAG, "name:" + name); return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); Log.w(TAG, "in onDestroy"); } }
其中,onBind(...)函数是Service基类中的唯一抽象方法,子类都必须重写实现,此函数的返回值是针对Bound Service类型的Service才有用的,在Started Service类型中,此函数直接返回 null 即可。onCreate(...)、onStartCommand(...)和onDestroy()都是Started Service相应生命周期阶段的回调函数。
2) Started Service使用
public class MainActivity extends Activity { public static final String TAG = "MainActivity"; private Button startServiceBtn; private Button stopServideBtn; private Button goBtn; private Intent serviceIntent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startServiceBtn = (Button) findViewById(R.id.start_service); stopServideBtn = (Button) findViewById(R.id.stop_service); goBtn = (Button) findViewById(R.id.go); startServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { serviceIntent = new Intent(MainActivity.this, MyService.class); startService(serviceIntent); } }); stopServideBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { stopService(serviceIntent); } }); goBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, BActivity.class); startActivity(intent); } }); } @Override protected void onDestroy() { super.onDestroy(); Log.w(TAG, "in onDestroy"); } }
如上代码片段,
当Client调用startService(Intent serviceIntent)后,如果MyService是第一次启动,首先会执行 onCreate()回调,然后再执行onStartCommand(Intent intent, int flags, int startId),当Client再次调用startService(Intent serviceIntent),将只执行onStartCommand(Intent intent, int flags, int startId),因为此时Service已经创建了,无需执行onCreate()回调。无论多少次的startService,只需要一次stopService()即可将此Service终止,执行onDestroy()函数(其实很好理解,因为onDestroy()与onCreate()回调是相对的)。
下面重点关注下onStartCommand(Intent intent, int flags, int startId)方法。
其中参数flags默认情况下是0,对应的常量名为START_STICKY_COMPATIBILITY。startId是一个唯一的整型,用于表示此次Client执行startService(...)的请求请求标识,在多次startService(...)的情况下,呈现0,1,2....递增。另外,此函数具有一个int型的返回值,具体的可选值及含义如下:
START_NOT_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。除非程序中Client明确再次调用startService(...)启动此Service。
START_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand(...)方法,但其中的Intent将是null,pendingintent除外。
START_REDELIVER_INTENT:与START_STICKY唯一不同的是,回调onStartCommand(...)方法时,其中的Intent将是非空,将是最后一次调用startService(...)中的intent。
START_STICKY_COMPATIBILITY:compatibility version of {@link #START_STICKY} that does not guarantee that {@link #onStartCommand} will be called again after being killed。此值一般不会使用,所以注意前面三种情形就好。
以上的描述中,”当Service因为内存不足而被系统kill后“一定要非常注意,因为此函数的返回值设定只是针对此种情况才有意义的,换言之,当认为的kill掉Service进程,此函数返回值无论怎么设定,接下来未来的某个时间内,即使系统内存足够可用,Service也不会重启。
小米手机针对此处做了变更:
另外,需要注意的是,小米手机针对此处做了一定的修改。在“自启动管理”中有一个自启动应用列表,默认情况下,只有少应用(如微信、QQ、YY、360等)默认是可以自启动的,其他应用默认都是禁止的。用户可以手动添加自启动应用,添加后的应用中如果Started Service onStartCommand(...)回调返回值是START_STICKY或START_REDELIVER_INTENT,当用户在小米手机上长按Home键结束App后,接下来未来的某个时间内,当系统内存足够可用时,Service依然可以按照上述规定重启。当然,如果用户在 设置 >> 应用 >> 强制kill掉App进程,此时Service是不会重启的。
注:以上实验结论基于小米2S亲测。
3) Started Service生命周期及进程相关
1.onCreate(Client首次startService(..)) >> onStartCommand >> onStartCommand - optional ... >> onDestroy(Client调用stopService(..))
注:onStartCommand(..)可以多次被调用,onDestroy()与onCreate()想匹配,当用户强制kill掉进程时,onDestroy()是不会执行的。
2.对于同一类型的Service,Service实例一次永远只存在一个,而不管Client是否是相同的组件,也不管Client是否处于相同的进程中。
3.Service通过startService(..)启动Service后,此时Service的生命周期与Client本身的什么周期是没有任何关系的,只有Client调用stopService(..)或Service本身调用stopSelf(..)才能停止此Service。当然,当用户强制kill掉Service进程或系统因内存不足也可能kill掉此Service。
4.Client A 通过startService(..)启动Service后,可以在其他Client(如Client B、Client C)通过调用stopService(..)结束此Service。
5.Client调用stopService(..)时,如果当前Service没有启动,也不会出现任何报错或问题,也就是说,stopService(..)无需做当前Service是否有效的判断。
6.startService(Intent serviceIntent),其中的intent既可以是显式Intent,也可以是隐式Intent,当Client与Service同处于一个App时,一般推荐使用显示Intent。当处于不同App时,只能使用隐式Intent。
当Service需要运行在单独的进程中,AndroidManifest.xml声明时需要通过android:process指明此进程名称,当此Service需要对其他App开放时,android:exported属性值需要设置为true(当然,在有intent-filter时默认值就是true)。
<service android:name=".MyService" android:exported="true" android:process=":MyCorn" > <intent-filter> <action android:name="com.example.androidtest.myservice" /> </intent-filter> </service>
4)Started Service Client与Service通信相关
当Client调用startService(Intent serviceIntent)启动Service时,Client可以将参数通过Intent直接传递给Service。Service执行过程中,如果需要将参数传递给Client,一般可以通过借助于发送广播的方式(此时,Client需要注册此广播)。
3.Bound Service
相对于Started Service,Bound Service具有更多的知识点。Bound Service的主要特性在于Service的生命周期是依附于Client的生命周期的,当Client不存在时,Bound Service将执行onDestroy,同时通过Service中的Binder对象可以较为方便进行Client-Service通信。Bound Service一般使用过程如下:
1.自定义Service继承基类Service,并重写onBind(Intent intent)方法,此方法中需要返回具体的Binder对象;
2.Client通过实现ServiceConnection接口来自定义ServiceConnection,并通过bindService (Intent service, ServiceConnection sc, int flags)方法将Service绑定到此Client上;
3.自定义的ServiceConnection中实现onServiceConnected(ComponentName name, IBinder binder)方法,获取Service端Binder实例;
4.通过获取的Binder实例进行Service端其他公共方法的调用,以完成Client-Service通信;
5.当Client在恰当的生命周期(如onDestroy等)时,此时需要解绑之前已经绑定的Service,通过调用函数unbindService(ServiceConnection sc)。
在Bound Service具体使用过程中,根据onBind(Intent intent)方法放回的Binder对象的定义方式不同,又可以将其分为以下三种方式,且每种方式具有不同的特点和适用场景:
1).Extending the Binder class
这是Bound Service中最常见的一种使用方式,也是Bound Service中最简单的一种。
局限:Clinet与Service必须同属于同一个进程,不能实现进程间通信(IPC)。否则则会出现类似于“android.os.BinderProxy cannot be cast to xxx”错误。
下面通过代码片段看下具体的使用:
package com.example.test; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.Log; public class MyBindService extends Service { public MyBindService() { } public static final String TAG = "MyBindService"; MyBinder mBinder = new MyBinder(); public class MyBinder extends Binder{ MyBindService getService(){ return MyBindService.this; } } @Override public IBinder onBind(Intent intent) { Log.w(TAG, "in onBind"); // TODO: Return the communication channel to the service. return mBinder; } @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); Log.w(TAG, "in onCreate"); } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.w(TAG, "in onDestroy"); } @Override public boolean onUnbind(Intent intent) { Log.w(TAG, "in onUnbind"); // TODO Auto-generated method stub return super.onUnbind(intent); } }
package com.example.test; import com.example.test.MyBindService.MyBinder; import android.R.id; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { public static final String TAG = "MainActivity"; private Button bindServiceBtn; private Button unbindServiceBtn; private Button startIntentService; private Intent serviceIntent; private MyBinder mBinder; private MyBindService mBindService; private boolean mBound; private ServiceConnection sc = new MyServiceConnection(); private class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub Log.w(TAG, "in MyServiceConnection onServiceConnected"); mBinder = (MyBinder) service; mBindService = mBinder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub Log.w(TAG, "in MyServiceConnection onServiceDisconnected"); mBound = false; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindServiceBtn = (Button) findViewById(R.id.bindServiceBtn); unbindServiceBtn = (Button) findViewById(R.id.unbindServiceBtn); startIntentService = (Button) findViewById(R.id.startIntentService); bindServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Intent itent = new Intent(MainActivity.this, MyBindService.class); bindService(itent, sc, Context.BIND_AUTO_CREATE); } }); unbindServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub excuteUnbindService(); } }); } private void excuteUnbindService(){ if (mBound){ unbindService(sc); mBound = false; } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.w(TAG, "in onDestroy"); excuteUnbindService(); } }
首次点击bindServiceBtn进行bindService(..)时,依次回调顺序如下:
MyBindService(13457): in onCreate MyBindService(13457): in onBind MainActivity(13457): in MyServiceConnection onServiceConnected
再次点击bindServiceBtn按钮时,发现没有任何输出,说明MyBindService没有进行任何回调。
点击unbindServiceBtn进行unbindService(..)时,回调顺序为:
MyBindService(13457): in onUnbind MyBindService(13457): in onDestroy
注:在四大基本组件中,需要注意的的是BroadcastReceiver不能作为Bound Service的Client,因为BroadcastReceiver的生命周期很短,当执行完onReceive(..)回调时,BroadcastReceiver生命周期完结。而Bound Service又与Client本身的生命周期相关,因此,Android中不允许BroadcastReceiver去bindService(..),当有此类需求时,可以考虑通过startService(..)替代。
2)Using a Messenger
Messenger,在此可以理解成”信使“,通过Messenger方式返回Binder对象可以不用考虑Clinet - Service是否属于同一个进程的问题,并且,可以实现Client - Service之间的双向通信。极大方便了此类业务需求的实现。
局限:不支持严格意义上的多线程并发处理,实际上是以队列去处理
下面直接看下具体的使用:
package com.example.test1; import android.R.integer; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.util.Log; public class MyMessengerService extends Service { public MyMessengerService () { } public static final String TAG = "MyMessengerService"; public static final int MSG_FROM_CLIENT_TO_SERVER = 1; public static final int MSG_FROM_SERVER_TO_CLIENT = 2; private Messenger mClientMessenger; private Messenger mServerMessenger = new Messenger(new ServerHandler()); class ServerHandler extends Handler{ @Override public void handleMessage(Message msg) { Log.w(TAG, "thread name:" + Thread.currentThread().getName()); switch (msg.what) { case MSG_FROM_CLIENT_TO_SERVER: Log.w(TAG, "receive msg from client"); mClientMessenger = msg.replyTo; // service发送消息给client Message toClientMsg = Message.obtain(null, MSG_FROM_SERVER_TO_CLIENT); try { Log.w(TAG, "server begin send msg to client"); mClientMessenger.send(toClientMsg); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } break; default: super.handleMessage(msg); break; } // TODO Auto-generated method stub super.handleMessage(msg); } } @Override public IBinder onBind(Intent intent) { Log.w(TAG, "in onBind"); return mServerMessenger.getBinder(); } @Override public void onDestroy() { Log.w(TAG, "in onDestroy"); // TODO Auto-generated method stub super.onDestroy(); } @Override public boolean onUnbind(Intent intent) { Log.w(TAG, "in onUnbind"); // TODO Auto-generated method stub return super.onUnbind(intent); } }
package com.example.test1; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { public static final String TAG = "MainActivity"; private Button bindServiceBtn; private Button unbindServiceBtn; private Button sendMsgToServerBtn; private ServiceConnection sc = new MyServiceConnection(); private boolean mBound; private Messenger mServerMessenger; private Handler mClientHandler = new MyClientHandler(); private Messenger mClientMessenger = new Messenger(mClientHandler); private class MyClientHandler extends Handler{ @Override public void handleMessage(Message msg) { if (msg.what == MyMessengerService.MSG_FROM_SERVER_TO_CLIENT) { Log.w(TAG, "reveive msg from server"); } } } private class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub Log.w(TAG, "in MyServiceConnection onServiceConnected"); mServerMessenger = new Messenger(service); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub Log.w(TAG, "in MyServiceConnection onServiceDisconnected"); mBound = false; } } public void sayHello(){ if (!mBound) return; // Create and send a message to the service, using a support 'what' value Message msg = Message.obtain(null, MyMessengerService.MSG_FROM_CLIENT_TO_SERVER,0,0); msg.replyTo = mClientMessenger; try { mServerMessenger.send(msg); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } private void excuteUnbindService(){ if (mBound) { unbindService(sc); mBound = false; } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.w(TAG, "in onDestroy"); excuteUnbindService(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindServiceBtn = (Button) findViewById(R.id.bind_service); unbindServiceBtn = (Button) findViewById(R.id.unbind_service); sendMsgToServerBtn = (Button) findViewById(R.id.send_msg_to_server); bindServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Intent itent = new Intent(MainActivity.this, MyMessengerService.class); bindService(itent, sc, Context.BIND_AUTO_CREATE); } }); unbindServiceBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub excuteUnbindService(); } }); sendMsgToServerBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub sayHello(); } }); } }
其中,需要注意的几点是:
1.MyMessengerService自定中,通过new Messenger(new ServerHandler())创建Messenger对象,在onBind(..)回调中,通过调用Messenger对象的getBinder()方法,将Binder返回;
2.Client在ServiceConnection的onServiceConnected(..)的回调中,通过new Messenger(binder)获取到Service传递过来的mServerMessenger;
3.接下来,就可以通过mServerMessenger.send(msg)方法向Service发送message,Service中的Messenger构造器中的Handler即可接收到此信息,在handleMessage(..)回调中处理;
4.至此只是完成了从Client发送消息到Service,同样的道理,想实现Service发送消息到Client,可以在客户端定义一个Handler,并得到相应的Messenger,在Clinet发送消息给Service时,通过msg.replyTo = mClientMessenger方式将Client信使传递给Service;
5.Service接收到Client信使后,获取此信使,并通过mClientMessenger.send(toClientMsg)方式将Service消息发送给Client。
至此,完成了Client - Service之间的双向通信流程。
3).AIDL(Android Interface Definition Language)
1
|
Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL. |
第一句最重要,“只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL”,其他情况下你都可以选择其他方法,如使用Messager,也能跨进程通讯。可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。还是官方文档说的明白,一句话就可以理解为什么要有AIDL。那么是不是这样的写个AIDL试试。
首先新建一个包 com.scott.aidl,然后创建后缀为 .aidl 的文件 IPerson.aidl 。
在 IPenson.aidl 中我们定义了一个 "问候" 的方法 , 代码如下 :
package com.scott.aidl; interface IPerson { String greet(String someone); }
在Eclipse插件的帮助下,编译器会自动在gen目录中生成对应的IPerson.java文件,格式化后的代码如下:
package com.scott.aidl; public interface IPerson extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.scott.aidl.IPerson { private static final java.lang.String DESCRIPTOR = "com.scott.aidl.IPerson"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.scott.aidl.IPerson interface, * generating a proxy if needed. */ public static com.scott.aidl.IPerson asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.scott.aidl.IPerson))) { return ((com.scott.aidl.IPerson) iin); } return new com.scott.aidl.IPerson.Stub.Proxy(obj); } public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_greet: { data.enforceInterface(DESCRIPTOR); java.lang.String _arg0; _arg0 = data.readString(); java.lang.String _result = this.greet(_arg0); reply.writeNoException(); reply.writeString(_result); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.scott.aidl.IPerson { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } public java.lang.String greet(java.lang.String someone) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(someone); mRemote.transact(Stub.TRANSACTION_greet, _data, _reply, 0); _reply.readException(); _result = _reply.readString(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_greet = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public java.lang.String greet(java.lang.String someone) throws android.os.RemoteException; }
该文件的大纲视图如下:
IPerson接口中的抽象内部类Stub继承android.os.Binder类并实现IPerson接口,比较重要的方法是asInterface(IBinder)方法,该方法会将IBinder类型的对象转换成IPerson类型,必要的时候生成一个代理对象返回结果。
接下来就是我们的Service了:
package com.example.aidl; import com.scott.aidl.IPerson; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class AIDLService extends Service { public AIDLService() { } private static final String TAG = "AIDLService"; IPerson.Stub sub = new IPerson.Stub() { @Override public String greet(String someone) throws RemoteException { Log.w(TAG, "greet() called"); return "hello, " + someone; } }; @Override public IBinder onBind(Intent intent) { Log.w(TAG, "onBind() called"); return sub; } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.w(TAG, "onDestroy() called"); } @Override public boolean onUnbind(Intent intent) { Log.w(TAG, "onUnbind() called"); // TODO Auto-generated method stub return super.onUnbind(intent); } }
我们实现了IPerson.Stub这个抽象类的greet方法,然后再onBind(Intent)方法中返回我们的stub实例,这样一来调用方获取的IPerson.Stub就是我们的这个实例,greet方法也会按照我们的期望那样执行。
当然,要想让Service生效,我们还需要在AndroidManifest.xml中做一些配置工作:
<service android:name=".AIDLService"> <intent-filter> <action android:name="android.intent.action.AIDLService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
服务端已经完成了,接下来我们就该完成客户端的工作了。我已经建好了一个客户端工程,如图:
我们只需要把IPerson.aidl文件拷到相应的目录中即可,编译器同样会生成相对应的IPerson.java文件,这一部分和服务端没什么区别。这样一来,服务端和客户端就在通信协议上达到了统一。我们主要工作在MainActivity中完成。
MainActivity代码如下:
package com.example.aidl; import com.scott.aidl.IPerson; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private Button bindBtn; private Button greetBtn; private Button unbinButton; private IPerson person; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub Log.w(TAG, "onServiceDisconnected() called"); } @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub Log.w(TAG, "onServiceConnected() called"); person = IPerson.Stub.asInterface(service); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindBtn = (Button) findViewById(R.id.bindBtn); bindBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Intent intent = new Intent("android.intent.action.AIDLService"); bindService(intent, conn, Context.BIND_AUTO_CREATE); } }); greetBtn = (Button) findViewById(R.id.greetBtn); greetBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub try { String retVal = person.greet("scott"); Log.w(TAG, "receive value=" + retVal); } catch (Exception e) { // TODO: handle exception } } }); unbinButton = (Button) findViewById(R.id.unbindBtn); unbinButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub unbindService(conn); } }); } }
从代码中可以看到,我们要重写ServiceConnection中的onServiceConnected方法将IBinder类型的对像转换成我们的IPerson类型。到现在我们就剩下最后一个步骤了,这个环节也是最为关键的,就是绑定我们需要的服务。我们通过服务端Service定义的“android.intent.action.AIDLService”这个标识符来绑定其服务,这样客户端和服务端就实现了通信的连接,我们就可以调用IPerson中的“问候”方法了。
按照顺序分别是:初始界面;点击bindService后界面;点击greet后界面;点击unbindService后界面。
操作过程中的日志如下:
注:无论哪种方式的Bound Service,在进行unbind(..)操作时,都需要注意当前Service是否处于已经绑定状态,否则可能会因为当前Service已经解绑后继续执行unbind(..)会导致崩溃。这点与Started Service区别很大(如前文所述:stopService(..)无需做当前Service是否有效的判断)。
4.Local Service VS Remote Service
Local Service:不少人又称之为”本地服务“,是指Client - Service同处于一个进程;
Remote Service:又称之为”远程服务“,一般是指Service处于单独的一个进程中。
其他使用上上文中基本上都有所述。
5.Service特性
1.Service本身都是运行在其所在进程的主线程(如果Service与Clinet同属于一个进程,则是运行于UI线程),但Service一般都是需要进行”长期“操作,所以经常写法是在自定义Service中处理”长期“操作时需要新建线程,以免阻塞UI线程或导致ANR;
2.Service一旦创建,需要停止时都需要显示调用相应的方法(Started Service需要调用stopService(..)或Service本身调用stopSelf(..), Bound Service需要调用unbindService(..)),否则对于Started Service将处于一直运行状态,对于Bound Service,当Client生命周期结束时也将因此问题。也就是说,Service执行完毕后,必须人为的去停止它。
6.IntentService
IntentService是系统提供给我们的一个已经继承自Service类的特殊类,IntentService特殊性是相对于Service本身的特性而言的:
1.默认直接实现了onBind(..)方法,直接返回null,并定义了抽象方法onHandlerIntent(..),用户自定义子类时,需要实现此方法;
2.onHandlerIntent(..)主要就是用来处于相应的”长期“任务的,并且已经自动在新的线程中,用户无语自定义新线程;
3.当”长期“任务执行完毕后(也就是onHandlerIntent(..)执行完毕后),此IntentService将自动结束,无需人为调用方法使其结束;
4.IntentService处于任务时,也是按照队列的方式一个个去处理,而非真正意义上的多线程并发方式。
下面是一个基本的继承自IntentService的自定义Service:
public class MyIntentService extends IntentService { public static final String TAG = "MyIntentService"; public MyIntentService() { super(TAG); } public MyIntentService(String name) { super(name); } @Override protected void onHandleIntent(Intent intent) { Log.w(TAG, "in onHandleIntent"); Log.w(TAG, "thread name:" + Thread.currentThread().getName()); } }
7.前台Service
Android中Service接口中还提供了一个称之为”前台Service“的概念。通过Service.startForeground (int id, Notification notification)方法可以将此Service设置为前台Service。在UI显示上,notification将是一个处于onGoing状态的通知,使得前台Service拥有更高的进程优先级,并且Service可以直接notification通信。
下面是一个简单的前台Service使用实例:
public class MyService extends Service { public static final String TAG = "MyService"; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); Log.w(TAG, "in onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.w(TAG, "in onStartCommand"); Log.w(TAG, "MyService:" + this); String name = intent.getStringExtra("name"); Log.w(TAG, "name:" + name); Notification notification = new Notification(R.drawable.ic_launcher, "test", System.currentTimeMillis()); Intent notificationIntent = new Intent(this, DActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntesnt, 0); notification.setLatestEventInfo(this, "title", "content", pendingIntent); startForeground(1, notification); return START_REDELIVER_INTENT; } @Override public void onDestroy() { super.onDestroy(); Log.w(TAG, "in onDestroy"); } }