android开发之Service深入分析全解

来源:互联网 发布:求数组最大值和最小值 编辑:程序博客网 时间:2024/05/01 15:47

1、先来看看What is Service?

A Service is not a separate process And A Service is not a thread.一个服务不是一个独立的进程,也不是一个线程。
那么,Service是什么呢?
①. 后台运行,处理耗时较长的工作
②. Service是一个应用程序组件
③. Service通常用来处理一些耗时比较长的操作
④. 可以使用Service更新ContentProvider,发送Intent以及启动系统的通知等等。

2、接着来看看Service的整个生命周期:

Service的活动生命周期是在onStart()之后,这个方法会处理通过startService()方法传递来的Intent对象。音乐Service可以通过开打intent对象来找到要播放的音乐,然后开始后台播放。

Service停止时没有相应的回调方法,即没有onStop()方法。onCreate()方法和onDestory()方法是针对所有的Service,无论它们是否启动。通过Context.startService()和Context.bindService()方法。然而,只有通过startService()方法启动的Service才会被调用onStart()方法。如果一个Service允许别人绑定,那么需要实现以下额外的方法:

IBinder onBind(Intent intent);
boolean onUnbind(Intent intent);
void onRebind(Intent intent);

onBind()回调方法会继续传递通过bindService()传递来的intent对象。onUnbind()会处理传递给unbindService()的intent对象。如果Service允许绑定,onBind()会返回客户端与服务互相联系的通信频道。如果建立了一个新的客户端与服务的连接,onUnbind()方法可以请求调用onRebind()方法。

下面的图标介绍了Service的回调方法,然而,它把通过startService()方法建立的服务从通过bindService()方法建立的服务分离开来。记住任何服务,无论它怎么建立,都默认客户端可以连接,所以任何的Service能够接收onBind()和onUnbind()方法。
这里写图片描述

Service组件的通信方式一共有三种:

①. 通过startService来启动的Service
②. 通过bindService来启动的Service
③. 使用AIDL方式来启动的Service
下面我们来看看它们的具体使用方法和它们之间有什么区别:

(1)、首先来说说通过startService来启动的Service(后台处理工作)

startService(Intent service),通过intent值来指定启动哪个Service,可以直接指定目标Service的名,也可以通过Intent的action属性来启动设置了相应action属性的Service,使用这种方式启动的Service,当启动它的Activity被销毁时,是不会影响到它的运行的,这时它仍然继续在后台运行它的工作。直至调用stopService(Intent service)方法时候或者当系统资源非常紧缺时,这个服务才会调用onDestory()方法停止运行。所以这种Service一般可以用做处理一些耗时的工作。可能有人问,如果Service不是独立的一个进程的话,为什么当Activity退出时,Service仍然可以进行运行呢?其实是这样的,sdk上说了Activity和Service默认是运行在应用进程的主线程中,四大组件默认都是和Activity运行在同一个主进程的,那就是说Activity通过startService()方法启动一个服务后,被启动的服务和Activity都是在同一个线程中的。所以当我主动销毁了这个Activity,但是它所在的线程还是存在的,只不过是这个Activity它所占用的资源被释放掉了,这个Activity所在的主线程只有当Activity内存不足才会被kill,否则一般的情况下这个Activity所在的应用程序的线程始终存在,也就是这个Activity所启动的服务也会一直运行下去。

还有一点需要注意的是,如果Service要处理一些比较耗时的工作时,因为Service和Activity默认情况都在同一个主线程中的缘故,所以要操作这些耗时的工作一般是在Service里另起一个新线程来处理。这样可以避免主线程的阻塞,影响用户体验性。

(2)、然后来说说通过bindService来启动的Service(在本地同进程内与Activity交互)

举例:
首先,创建一个接口,IService.java:

package yy.android.service  public interface IService {      String getName();}   

接着,创建一个服务LocalService.java:

public class LocalService extends Service{    private static final String TAG="LocalService";    private MyBind myBind = new MyBind();     public IBinder onBind(Intent intent) {        Log.d(TAG, "localService onBind");        return myBind;    }     @Override    public void onCreate() {        super.onCreate();        Log.d(TAG, "localService onCreate");    }    @Override    public void onDestroy() {        super.onDestroy();        Log.d(TAG, "localService onDestroy");    }    @Override    public void onStart(Intent intent, int startId) {        // TODO Auto-generated method stub        super.onStart(intent, startId);         Log.d(TAG, "localService onStart");    }     @Override    public boolean onUnbind(Intent intent) {        // TODO Auto-generated method stub        Log.d(TAG, "localService onUnBind");        return super.onUnbind(intent);     }    public class MyBind extends Binder implements IService{        public String getName() {            // TODO Auto-generated method stub            return "YUZHIBOYI";         }     }  }

最后,就是现实ServiceActivity了:

public class ServiceActivity extends Activity {     private static final String TAG="ServiceActivity";    private IService iService=null;    private EditText edit;    private Button mBind;     ServiceConnection connection=new ServiceConnection() {        public void onServiceDisconnected(ComponentName name) {            Log.d(TAG,"DisConnection");            System.out.println("DisConnection!!!");        }        public void onServiceConnected(ComponentName name, IBinder service) {            // TODO Auto-generated method stub             Log.d(TAG,"Connection");            System.out.println("Connection!!!");            iService=(IService)service;            edit.setText(iService.getName());            // text.setText(iService.getName());        }    };     @Override     public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);        setContentView(R.layout.main);        //  Log.d(TAG,"Start");        edit =(EditText)findViewById(R.id.edit);         mBind = (Button)findViewById(R.id.Connection);        mBind.setOnClickListener(new OnClickListener() {             public void onClick(View v) {                 Intent intent=new Intent(ServiceActivity.this,LocalService.class);                 bindService(intent,connection, BIND_AUTO_CREATE);                }        });    }}

需要在AndroidManifest.xml中注册LocalService:

<service android:name = ".LocalService" />  

在布局文件中:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent"    android:orientation="vertical" >     <EditText        android:id="@+id/edit"        android:layout_width="fill_parent"        android:layout_height="wrap_content"/>           <Button         android:id="@+id/Connection"        android:layout_height="wrap_content"        android:layout_width="wrap_content"        android:text="@string/Bind"/></LinearLayout>

当运行程序时,日志信息:

05-10 13:46:28.353: D/LocalService(1328): localService onCreate
05-10 13:46:28.353: D/LocalService(1328): localService onBind
05-10 13:46:28.385: D/ServiceActivity(1328): Connection

当点击模拟器的back按钮时,日志信息:

05-10 13:46:57.953: D/LocalService(1328): localService onUnBind
05-10 13:46:57.953: D/LocalService(1328): localService onDestroy

从日志信息可以分析出,整个程序的运行过程是这样的:当用户点击绑定服务按钮时Activity调用bindService()方法,然后系统就去调用onCreate创建服务,然后系统继续调用onBind()方法向Activity传递一个IBinder类型的对象,是传递给Activity里的ServiceConnection里的onServiceConnected(ComponentName name,IBinder service)的第二个参数,然后通过这个参数可以获得IService的方法。进行本地的Activity和Service交互。(在同一个进程里进行的),当用户点击back键时,系统就调用onUnbind()再接着调用onDestory()方法销毁服务。总结可以理解成:bindService() -> onCreate() -> onBind() -> onServiceConnected(); 这里需要注意的一点是,启动的LocalService是和ServiceActivity在同一个进程里的,因为在注册服务时,没有配置它的android:process=”xxx”属性。具体android:process的用法可以自己去看其他资料。

(3)、最后要说的是使用AIDL方式的Service(进行跨进程的通信)

可以参考:http://www.cnblogs.com/hibraincol/archive/2011/09/06/2169325.html
AIDL(Android Interface Definition Language,安卓接口定义语言) IPC(Inter-Process Communication,进程间通信)机制是面向对象的,轻量级的。通过AIDL定义的接口可以实现服务器端与客户端的IPC通信。在Android上,一个进程不能简单的像访问本进程内存一样访问其他进程的内存。所以进程间想要对话,需要将对象拆解为操作系统可以理解的基本数据单元,并且有序的通过进程边界。通过代码来实现这个数据传输过程是冗长乏味的,所幸的是android提供了AIDL工具来帮助我们完成了此项工作。

注意:不仅仅在你需要A应用程序的客户端访问B应用程序的服务器端来实现IPC通信,并且在服务器端需要处理多线程(客户端)访问的情况下使用AIDL。如果不需要使用到进程间的IPC通信,那么通过Binder接口实现将更为合适,如果需要实现进程间的IPC通信,但不需要处理多线程(多客户端),通过Messager接口来实现将更为合适。不管怎样,在使用AIDL之前,应先确保已理解Bound Service。

AIDL接口的调用采用的是直接的函数调用方式,但你无法预知哪个进程(或线程)将调用该接口。同进程的线程调用和其他进程调用该接口之间是有所区别的:
· 在同进程中调用AIDL接口,AIDL接口代码的执行将在调用该AIDL接口的线程中完成,如果在主UI线程中调用AIDL接口,那么AIDL接口代码的执行将会在这个主UI线程中完成。如果是其他线程,AIDL接口代码的执行将在service中完成。因此,如果仅仅是本进程中的线程访问,你完全可以控制哪些线程将访问这个服务(但是如果是这样,那就完全没必要使用AIDL,而采取Binder接口的方式更为合适)。

· 远程进程(其他进程)调用AIDL接口时,将会在AIDL所属的进程的线程池中分派一个线程来执行该AIDL代码,所以编写AIDL时,你必须准备好可能有未知线程访问、同一时间可能有多个调用发生(多个线程的访问),所以AIDL接口的实现必须是线程安全的。

· 可以用关键字oneway来标明远程调用的行为属性,如果使用了该关键字,那么远程调用将仅仅是调用所需的数据传输过来并立即返回,而不会等待结果的返回,也就不是说不会阻塞远程线程的运行。AIDL接口将最终获得一个从Binder线程池中产生的调用(和普通的远程调用类似)。如果关键字oneway在本地调用中被使用,将不会对函数调用有任何影响。

定义AIDL接口:

AIDL接口使用后缀名位.aidl的文件来定义,.aidl文件使用java语言编写,并且将该.aidl文件保存在src/目录下(无论是服务端还是客户端都得保存同样的一份拷贝,也就是说只要是需要使用到该AIDL接口的应用程序都得在其src目录下拥有一份.aidl文件的拷贝)
编译时,Android sdk工具将会为src/目录下的.aidl文件在gen/目录下产生一个IBinder接口。服务端必须相应的实现该IBinder接口。客户端可以绑定该服务,调用其中的方法实现IPC通信。

创建一个用AIDL实现的服务端,需要以下几个步骤:
1. 创建.aidl文件:
该文件(YourInterface.aidl)定义了客户端可用的方法和数据的接口。
2. 实现这个接口:
Android SDK将会根据你的.aidl文件产生AIDL接口。生成的接口包含一个名为Stub的抽象内部类,该类声明了所有.aidl中描述的方法,你必须在代码里继承Stub类并且实现.aidl中定义的方法。
3. 向客户端公开服务端的接口:
实现了一个Service,并且在onBinder
方法中返回第2步中实现的那个Stub类的子类(实现类)。

注意:
服务端AIDL的任何修改都必须的同步到所有的客户端,否则客户端调用服务端的接口可能会导致程序异常(因为客户端此时可能会调用到服务端已不再支持的接口)

1.创建.aidl文件

AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。重要的是必须导入所有非内置类型,哪怕是这些类型是在接口相同的包中。
默认的AIDL支持以下的数据类型(这些类型不需要通过import导入):

· java语言的原始数据类型(包括int、long、char、boolen等等)
· String
· CharSequence:该类是被TextView和其他控件对象使用的字符序列
· List:列表中的所有元素必须是在此列出的类型,包括其他AIDL生成的接口和可打包类型。List可以想一般的类(比如List<String>) 那样使用,另一边接收的具体类一般是一个ArrayList,这些方法会使用List接口
· Map:Map中的所有元素必须是在此列出的类型,包括其他AIDL生成的接口和可打包类型,一般的maps(比如Map<String,Integer>)不被支持,另一边接收的具体类一般是一个HashMap,这些方法会使用Map接口。

对于其他的类型,在aidl中必须使用import导入,即使该类型和aidl处于同一包内。

定义一个服务端接口时,注意以下几点:
· 方法可以有0个或多个参数,可以是空返回值也可以返回所需的数据。
· 所有的非原始数据类型的参数必须指定参数方向(是传入参数,还是传出参数),传入参数使用in关键字标记,传出参数使用out,传入传出参数使用inout。如果没有显示的指定,那么将缺省使用in。
· 在aidl文件中所有的注释都将会包含在生成的IBinder接口中(在import和package语句之上的注释除外)。
· aidl中只支持成员方法,不支持成员变量。

我们通过一个例子来说明:
服务端

YAIDLService工程里的包yy.service.aidl有以下三个文件:
IAIDLService.aidl:

package yy.service.aidl;  interface IAIDLService {      String getName();  }

将该.aidl文件保存在工程目录中的src/目录下,当编译生成apk时,sdk工具将会在gen/目录下生成一个对应的IBinder接口的.java文件。
如果使用eclipse编写app,那么这个IBinder接口文件将会瞬间生成。如果不是使用eclipse,Ant 工具将会在下次编译你的app时,生成这个IBinder接口文件——所以当你编写.aidl文件之后应该立即使用 ant debug 编译你的app,这样你后续的代码就可以使用这个已生成的IBinder接口类了。

2.实现接口

生成的接口包含一个名为Stub的抽象的内部类,该类声明了所有.aidl中的描述的方法。

注意:
Stub还定义了少量的辅助方法,尤其是asInterface(),通过它或以获得IBinder(当applicationContext.bindService()成功调用时传递到客户端的onServiceConnected()),并且返回用于调用IPC方法的接口实例。

YAIDLService.java

package yy.service.aidl;import yy.service.aidl.IAIDLService.Stub;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;public class YAIDLService extends Service{    @Override    public IBinder onBind(Intent intent) {        // TODO Auto-generated method stub        return mBinder;    }    private IAIDLService.Stub mBinder = new Stub() {          public String getName() throws RemoteException {              return "YUZHIBOYI";          }      };}

在服务端里的服务程序里实现接口,这样,mBinder就是一个Stub类的对象,该对象为Service提供了IPC接口,并将会向客户端公开,这样客户端就可以通过该对象与该Service进行交互了,现在,如果客户端(比如一个Activity)调用bindService()来连接该服务端(YAIDLService),客户端的onServiceConnected()回调函数将会获得从服务端(YAIDLService)的onBind()返回的mBinder对象。
实现AIDL接口时需要注意一个几点:
· 不能保证所有对aidl接口的调用都在主线程中执行,所以必须考虑多线程调用的情况,也就是必须考虑线程安全。
· 默认IPC调用是同步的。如果已知IPC服务端会花费很多毫秒才能完成,那就不要在Activity或View线程中调用,否则会引起应用程序挂起(Android可能会显示“应用程序未响应”对话框),可以试着在独立的线程中调用。
· 不会将异常返回给调用方。

3.向客户端暴露接口

YAIDLServiceActivity.java(这个文件是创建Activity自动生成的可以不用)

客户端

YAIDLClient工程里的yy.client.aidl包里有文件YAIDLClientActivity.java

package yy.client.aidl;import yy.service.aidl.IAIDLService;import android.app.Activity;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.RemoteException;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;public class YAIDLClientActivity extends Activity {    private IAIDLService mAIDLService;      private TextView mName;      private Button mMessage;      private Button mPerson;          private ServiceConnection mServiceConnection = new ServiceConnection() {            public void onServiceConnected(ComponentName name, IBinder service) {              mAIDLService = IAIDLService.Stub.asInterface(service);             System.out.println("OnService!!!");        }        public void onServiceDisconnected(ComponentName name) {              mAIDLService = null;              System.out.println("DisService!!!");        }    };      @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        findViewById(R.id.connection).setOnClickListener(new OnClickListener() {              public void onClick(View view) {                            /**                  * 第一步,单击"连接"按钮后用mServiceConnection去bind服务器端创建的Service。                  */                  Intent service = new Intent("yy.service.aidl.IAIDLService");                  bindService(service, mServiceConnection, BIND_AUTO_CREATE);              }          });          mName = (TextView)findViewById(R.id.name);        mMessage = (Button) findViewById(R.id.message);          mMessage.setOnClickListener(new OnClickListener() {              public void onClick(View view) {                  /**                  * 第二步,从服务器端获取字符串。                  */                  try {                      mName.setText(mAIDLService.getName());                  } catch (RemoteException e) {                      e.printStackTrace();                  }              }          });      }}

在包yy.service.aidl包里有文件IAIDLService.aidl:

package yy.service.aidl;  interface IAIDLService {      String getName();  }

客户端同样得访问该接口类(这里指YAIDLService),所以,如果服务端和客户端不在同一进程(应用程序)中,那么客户端也必须在src/目录下拥有和服务端同样的一份.aidl文件的拷贝(同样是指:包名、类名、内容完全一模一样),客户端将会通过这个.aidl文件生成android.os.Binder接口——以此来实现客户端访问AIDL中的方法。当客户端在onServiceConnected()回调方法中获得IBinder对象后,必须通过调用YourServiceInterface.Stub.asInterface(service)将其转化成为YourServiceInterface类型如上程序。

服务端的AndroidManifest.xml里注册Service:

<service android:name = ".YAIDLService" android:process = ":remote">     <intent-filter>         <action android:name = "yy.service.aidl.IAIDLService" />     </intent-filter>  </service> 

总结:

这里给出调用远端AIDL接口的步骤:
1、在src/目录下包含.aidl文件。
2、声明一个IBinder接口(通过.aidl文件生成的)的实例。
3、实现ServiceConnection。
4、调用Context.bindService()绑定你的ServiceConnection实现类的对象(也就是远程服务端)。
5、在onServiceConnected()方法中会接收到IBinder对象(也就是服务端),调用YourInterfaceName.Stub.asInterface((IBinder)service)将返回值转换为YourInterface类型。
6、进行跨进程交互。


免责声明:转载http://blog.csdn.net/YUZHIBOYI/article/details/7555508
AIDL详解:http://blog.csdn.net/zanelove/article/details/44008165

Game Over

0 0