🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
**Android四大组件之Service** Android支持服务的概念,服务是在后台运行的组件,没有用户界面,Android服务可用有与活动独立的生命周期。Android支持两种类型的服务: **本地服务:** 本地服务只能由承载该服务的应用程序访问,无法供在设备上运行的其他应用程序访问。客户端调用Context.startService()启动该服务。 **远程服务:** 远程服务除了可从承载服务的应用程序访问,还可以从其他应用程序访问。远程服务使用AIDL向客户端定义。服务支持onBind()方法,客户端通过Context.bindService()进行调用。 **1)本地服务** **1.1、startService**  本地服务可由Context.startService()启动,启动后这些服务将持续运行,直到客户端调用Context.stopService()或服务自己调用stopSelf()。 注意:如果调用Context.startService()时还未创建服务,系统将实例化服务并调用服务的onStartCommand()方法。如果在调用Context.startService()时服务已经启动,那么不会再创建一个实例,而是重新调用正在运行的服务的onStartCommand()方法。 Demo:我们在MainActivity中新建两个两个Button,一个用于启动服务,另外一个用于停止服务。建立一个MyService类继承于Service,当收到服务的时候在通知栏弹出通知,一直到我们的服务退出才清除通知,同时收到启动服务的消息时我们建立一个线程sleep 10秒。当我们退出MainActivity的时候停止服务,同时清除通知栏的通知。 MainActivity.xml:就两个Button用于启动和停止服务。 ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <Button android:id="@+id/btnStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="startService" > </Button> <Button android:id="@+id/btnStop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="stopService" android:layout_below="@id/btnStart"> </Button> </RelativeLayout> ~~~ 然后是MainActivity,用于响应Button的单击事件: ~~~ public class MainActivity extends Activity implements OnClickListener{ private static final String TAG = "MainActivity"; private int counter = 1; private Button btnStart, btnStop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnStart = (Button)this.findViewById(R.id.btnStart); btnStop = (Button)this.findViewById(R.id.btnStop); btnStart.setOnClickListener(this); btnStop.setOnClickListener(this); } @Override public void onClick(View v) { // TODO Auto-generated method stub Log.v(TAG, "id:"+v.getId() + "btn:"+R.id.btnStart); switch (v.getId()) { case R.id.btnStart: Log.v(TAG, "Starting Service...counter=" + counter); Intent intent = new Intent(MainActivity.this, MyService.class); intent.putExtra("counter", counter); startService(intent); break; case R.id.btnStop: Log.v(TAG, "Stopping Service..."); if( stopService(new Intent(MainActivity.this, MyService.class)) ) { Log.v(TAG, "stopService successful"); } else { Log.v(TAG, "stopService failed"); } break; default: break; } } @Override protected void onDestroy() { // TODO Auto-generated method stub stopService(new Intent(MainActivity.this, MyService.class)); super.onDestroy(); } } ~~~ 最后是我们的MyService,当收到服务的时候,在通知栏弹出通知,并启动一个sleep 10秒的线程。 ~~~ public class MyService extends Service { private static final String TAG = "MyService"; private NotificationManager notificationMgr; private ThreadGroup threadGroup = new ThreadGroup("ServiceWorkder"); @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); Log.v(TAG, "in onCreate"); notificationMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); Notification notification = new Notification(R.drawable.ic_launcher, "Service is running", System.currentTimeMillis()); notification.flags = Notification.FLAG_NO_CLEAR; PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0); notification.setLatestEventInfo(this, TAG, "Service is running", intent); notificationMgr.notify(0, notification); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub super.onStartCommand(intent, flags, startId); int counter = intent.getExtras().getInt("counter"); Log.v(TAG, "in onStartCommand, counter = "+counter+",startId = "+startId); new Thread(threadGroup, new ServiceWorker(counter)).start(); return START_STICKY; } class ServiceWorker implements Runnable { private int counter = -1; public ServiceWorker(int counter) { this.counter = counter; } public void run() { final String TAG = "ServiceWorker" + Thread.currentThread().getId(); try { Log.v(TAG, "Sleeping for 10 seconds.counter="+counter); Thread.sleep(10000); Log.v(TAG, "...waking up"); } catch (Exception e) { // TODO: handle exception Log.v(TAG, "...sleep interrupt"); } } } @Override public void onDestroy() { // TODO Auto-generated method stub Log.v(TAG, "in onDestroy. Interrupt threads and canceling notifications"); threadGroup.interrupt(); notificationMgr.cancelAll(); super.onDestroy(); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } } ~~~ 最后不要忘了在AndroidManifest.xml中声明我们的Service: ~~~ <service android:name=".MyService"> </service> ~~~ 最后通知栏运行效果如下: ![](https://box.kancloud.cn/2016-05-24_5743f83f25822.jpg) **1.2、bindService** 本地服务也可由Context.bindService()启动,这样调用者和服务绑在一起,调用者一旦退出,服务也就终止了。客户端建立一个与Service连接,使用此连接与Service通信,通过Context.bindService()绑定服务,使用Context.unbindService()关闭服务。多个客户端可以绑定同一个服务,如果Service未启动,bindService()可以启动服务。 注意:上面的startService()和bindService()是完全独立的两种模式,你可以绑定一个已经通过startService()启动的服务。例如:一个后台播放音乐的服务可以通过startService()启动播放,然后activity可以通过调用bindService()方法建立于Service的联系,执行切换歌曲等操作。这种情况下:stopService()不会停止服务,直到最后一个unbindService()调用。 当创建一个能够提供绑定功能的服务时,我们必须提供一个IBinder对象,客户端能够使用这个对象与服务通信,Android中有三种方式: **(1)扩展Binder类** 一般用于服务和Activity属于同一个进程的情况。类似上面的startService()我们在MainActivity中建立两个Button,一个用于bindService,另外一个unbindService()。在获得Service的IBinder接口之后就可以调用Service的内部方法了。 ~~~ public class MainActivity extends Activity implements OnClickListener{ private static final String TAG = "MainActivity"; private boolean isBindFlag = false; private Button btnBind, btnUnbind; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnBind = (Button)this.findViewById(R.id.btnBind); btnUnbind = (Button)this.findViewById(R.id.btnUnbind); btnBind.setOnClickListener(this); btnUnbind.setOnClickListener(this); } @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.btnBind: Intent intent2 = new Intent(MainActivity.this, MyBindService.class); bindService(intent2, serviceConnection, Context.BIND_AUTO_CREATE); break; case R.id.btnUnbind: unbindService(serviceConnection); break; default: break; } } private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub isBindFlag = false; } @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub MyBindService.MyBinder binder = (MyBinder)service; MyBindService bndService = binder.getService(); bndService.myMethod(); isBindFlag = true; } }; @Override protected void onDestroy() { // TODO Auto-generated method stub if(isBindFlag == true) { unbindService(serviceConnection); } super.onDestroy(); } } ~~~ 这里当绑定到MyBindService之后,就可以通过bndService实例调用其方法myMethod()了。下面MyBindService比较简单就是继承于Service,并实现其onBind()接口,返回一个MyBinder实例,客户端拿到这个MyBinder之后可以通过它获取到MyBindService实例,然后调用其提供的myMethod()方法了。 ~~~ public class MyBindService extends Service { private static final String TAG = "MyBindService"; public void myMethod() { Log.i(TAG, "myBindService->myMethod()"); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return myBinder; } public class MyBinder extends Binder { public MyBindService getService() { return MyBindService.this; } } private MyBinder myBinder = new MyBinder(); } ~~~ 同理最后我们也需要在AndroidManifest.xml中声明我们的服务。 **(2)使用Messenger** **(3)Remote Service**也就是我们下面要说的AIDL服务了。 2)AIDL服务    2.1)构建远程服务 上面介绍的各种服务只能由承载它的应用程序使用,如果想构建可由其他进程通过RPC使用的服务,需要使用IDL来定义向客户端公开的接口,在Android中这个IDL就称为AIDL。构建远程服务的一般步骤为: 1、编写一个AIDL文件用来向客户端定义接口。AIDL文件使用Java语法扩展名为.aidl,其内部使用的包名和Android项目使用的包名相同。 首先在项目的src目录下新建一个IStudentInfo.aidl文件,在AIDL文件中定义服务接口。提供了double getScore(String name)接口,根据给定的String类型的学生姓名,返回一个double类型的分数。      ~~~ package com.myAndroid.aidlService; interface IStudentInfoService { double getScore(String name); } ~~~      2、将AIDL文件添加到Eclipse项目的src目录下,Android Eclipse插件将调用AIDL编译器从AIDL文件生成Java接口。 生成的java接口文件位于gen/com.myAndroid.aidlService下,名为IStudengInfoService.java: ~~~ /* * This file is auto-generated. DO NOT MODIFY. * Original file: C:\\Documents and Settings\\Administrator\\workspace\\Android使用AIDL创建Service\\src\\com\\myAndroid\\aidlService\\IStudentInfoService.aidl */ package com.myAndroid.aidlService; public interface IStudentInfoService extends android.os.IInterface { /**Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.myAndroid.aidlService.IStudentInfoService { private static final java.lang.String DESCRIPTOR = "com.myAndroid.aidlService.IStudentInfoService"; /**Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.myAndroid.aidlService.IStudentInfoService interface, * generating a proxy if needed. */ public static com.myAndroid.aidlService.IStudentInfoService asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.myAndroid.aidlService.IStudentInfoService))) { return ((com.myAndroid.aidlService.IStudentInfoService)iin); } return new com.myAndroid.aidlService.IStudentInfoService.Stub.Proxy(obj); } @Override 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_getScore: { data.enforceInterface(DESCRIPTOR); java.lang.String _arg0; _arg0 = data.readString(); double _result = this.getScore(_arg0); reply.writeNoException(); reply.writeDouble(_result); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.myAndroid.aidlService.IStudentInfoService { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public double getScore(java.lang.String name) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); double _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(name); mRemote.transact(Stub.TRANSACTION_getScore, _data, _reply, 0); _reply.readException(); _result = _reply.readDouble(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_getScore = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public double getScore(java.lang.String name) throws android.os.RemoteException; } ~~~ 对于所生成的类,注意几点:在IStudentInfoService中有一个名为IStudentInfoService的接口,实现了IInterface接口。 内部有一个名为Stub的static final 抽象类扩展了android.os.Binder并实现了IStudentInfoService接口。 内部还有一个名为Proxy的static类,实现了IStudentInfoService接口,它是Stub类的代理。       3、实现一个服务并从onBind()方法返回所生成的接口。 要实现服务的接口,需要编写一个类来扩展android.app.Service并实现IStudentInfoService接口,这个类需要提供onBind()方法将服务向客户端公开。 ~~~ package com.myAndroid.aidlService; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class StudentInfoService extends Service { private static final String TAG = "StudentInfoService"; public class StudentInfoServiceImpl extends IStudentInfoService.Stub { @Override public double getScore(String name) throws RemoteException { // TODO Auto-generated method stub Log.v(TAG, "getScore() called for "+ name); return 85.0; } } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub Log.v(TAG, "onBind called"); return new StudentInfoServiceImpl(); } } ~~~ 从AIDL文件生成的Stub类是抽象类,且实现了IStudentInfoService接口。在我们的服务实现中内部类StudentInfoServiceIml扩展了Stub类,实现了getScore()方法,充当着远程服务具体实现,当客户端bind到服务时,返回一个此类的实例。 4、最后将服务配置添加到AndroidManifest.xml文件中 这次我们需要用一个Intent过滤器来公开服务。 ~~~ <service android:name="StudentInfoService"> <intent-filter > <action android:name="com.myAndroid.aidlService.IStudentInfoService"/> </intent-filter> </service> ~~~ 2.2)调用远程服务 当客户端与服务通信时,它们之间需要一个协议或契约,在Android中这个协议就是AIDL文件。所以客户端调用服务的第一步就是获取服务的AIDL文件并将其复制到客户端项目中,同理AIDL编译器会创建一个接口定义公开文件,这个文件与服务器中的文件一样。 我们创建一个新的Android项目名为 StudentInfoClient,包名为com.myAndroid.studentInfoClient。然后在这个项目下新建一个Java包名为 com.myAndroid.aidlService,并将IStudentInfoService.aidl文件拷贝到当前包下面。 最后我们在MainActivity中通过bindService()获取服务的引用,然后调用其getScore()方法,即可跟服务端通信。下面给出客户端源码: ~~~ package com.myAndroid.studentInfoClient; import com.myAndroid.aidlService.IStudengInfoService; import android.os.Bundle; import android.os.IBinder; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.support.v4.widget.SimpleCursorAdapter.ViewBinder; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast; import android.widget.ToggleButton; public class MainActivity extends Activity implements View.OnClickListener { private ToggleButton toggleButton; private Button callButton; private IStudengInfoService myService = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toggleButton = (ToggleButton)this.findViewById(R.id.bindBtn); callButton = (Button)this.findViewById(R.id.callBtn); toggleButton.setOnClickListener(this); callButton.setOnClickListener(this); } @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.bindBtn: if(((ToggleButton)v).isChecked()) { bindService(new Intent(IStudengInfoService.class.getName()), conn, Context.BIND_AUTO_CREATE); } else { unbindService(conn); callButton.setEnabled(false); } break; case R.id.callBtn: callService(); break; default: break; } } private void callService() { try { double val = myService.getScore("Lucy"); Toast.makeText(MainActivity.this, "Value from service is "+ val, Toast.LENGTH_LONG).show(); } catch (Exception e) { // TODO: handle exception } } private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub myService = null; toggleButton.setChecked(false); callButton.setEnabled(false); } @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub myService = IStudengInfoService.Stub.asInterface(service); toggleButton.setChecked(true); callButton.setEnabled(true); } }; protected void onDestroy() { if(callButton.isEnabled()) { unbindService(conn); } super.onDestroy(); } } ~~~ 代码中对于AIDL服务我们需要提供ServiceConnection接口的实现,此接口定义两个方法:一个供系统建立服务连接时调用,另一个在销毁服务连接时调用。当建立服务时回调onServiceConnected()方法,我们根据参数service调用IStudengInfoService.Stub.asInterface()获得服务端的代理,然后调用其相应方法getScore()。 注意:bindService()是异步调用,因为进程或服务可能没有运行,但是我们不能在主线程上等待服务启动。当从服务解除绑定时我们不会调用onServiceDisConnected(),只有在服务崩溃时才会调用它。如果调用了它,我们可能需要重写调用bindService()。 2.3)向服务传递复杂类型 注意:AIDL对非原语的支持: 1、AIDL支持String和CharSequence。 2、AIDL支持传递其他AIDL接口,但你引用的每个AIDL接口都需要一个import语句。 3、AIDL支持传递实现android.os.Parcelable接口的复杂类型。需要在AIDL文件中包含针对这些类型的Import语句。 4、AIDL支持java.util.List和java.util.Map,但是具有一些限制,集合中的项允许数据类型包括Java原语、String、CharSequence和android.os.Parcelable。无需为List和Map提供import语句,但是需要为Parcelable提供。 5、除字符串外。非原语类型需要一个方向指示符。方向指示符包括in、out和inout。in表示由客户端设置,out表示值由服务设置,inout表示客户端和服务都设置了该值。 Parcelable接口告诉Android运行时在封送marshalling和解unmarshalling过程中如何序列化和反序列化对象。 ~~~ public class Person implements Parcelable { private int age; private String name; public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() { public Person createFromParcel(Parcel in) { return new Person(in); } public Person[] newArray(int size) { return new Person[size]; } }; public Person() { } private Person(Parcel in) { readFromParcel(in); } @Override public int describeContents() { // TODO Auto-generated method stub return 0; } @Override public void writeToParcel(Parcel dest, int flags) { // TODO Auto-generated method stub dest.writeInt(age); dest.writeString(name); } public void readFromParcel(Parcel in) { age = in.readInt(); name = in.readString(); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ~~~ Parcelable接口定义在封送/解封送过程中混合和分解对象的契约,Parcelable接口的底层是Parcel容器对象,Parcel类是一种最快的序列号和反序列化机制,专为Android中的进程间通信而设计。 要实现Parcelable接口,需要实现writeToParecl()和readFromParcel()方法。写入对象到包裹和从包裹中读取对象,注意:写入属性的顺序和读取属性的顺序必须相同。 向Person类添加一个名为CREATOR的static final属性,该属性需要实现android.os.Parcelable.Creator<T>接口。 为Parcelable提供一个构造函数,知道如何从Parcel创建对象。 在.aidl文件中我们需要导入该类:import com.myAndroid.aidlService.Person。 ~~~ interface IStudentInfoServie{                 String getScore(in String name, in Person requester);    // 后面非原语类型需要一个方向指示符。      } ~~~