应用程序组件----Service(一)



Service作为Android四大组件之一,在每一个应用程序中都扮演着非常重要的角色。我们大多主要用它在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。
一、Service生命周期与启动方式
ontext.startService() 启动流程:
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()启动流程:
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在一个生命周期中只能被调用一次。

 
特别注意,如果出现启动既start Service又绑定bind Service ,那么在销毁Service的时候,仅仅stopService是不行的,StopService只会让Service停止,UnbindService按钮只会让Service和Activity解除关联,一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁。
二、Service组件的通信方式
(1)通过startService来启动的Service;
startService(Intent service),通过intent值来指定启动哪个Service,可以直接指定目标Service的名,也可以通过Intent的action属性来启动设置了相应action属性的Service.
例如:
在AndroidManifest.xml文件中对service类的定义
<service android:enabled="true" android:name=".MusicService">
<intent-filter>
<action android:name="com.github.callanna.musicService" />
</intent-filter>
</service>
既可以StartService(newIntent(context,MusicService.class)).显 启动。
也可以StartService( new Intent("org.allin.android.musicService"));隐式 启动
但注意,好像在android 5.0以后,如此的隐式调用不起作用, 如果采用隐式的方式就会抛出安全异常。
解决方法:把Intent intent = new Intent("XXX.XXX.XXX");替换为Intent mIntent = new Intent();mIntent.setAction("XXX.XXX.XXX");mIntent.setPackage(“XXX.XXX”);
如果要实现对服务端的访问,即对Service的调用。可以在启动的Intent里添加标识即可,如下:
public   class  MusicService extends  Service {
 
     private   static   final  String TAG = "MyService" ;
     private  MediaPlayer mediaPlayer;
 
     /*
      * (non-Javadoc)
      *
      * @see android.app.Service#onBind(android.content.Intent)
      */
     @Override
     public  IBinder onBind(Intent arg0) {
         return   null ;
    }
 
     @Override
     public   void  onCreate() {
        Log.v(TAG, "onCreate" );
         if  (mediaPlayer == null ) {
            mediaPlayer = MediaPlayer.create( this , R.raw.tmp);
            mediaPlayer.setLooping( false );
        }
    }
 
     @Override
     public   void  onDestroy() {
        Log.v(TAG, "onDestroy" );
         if  (mediaPlayer != null ) {
            mediaPlayer.stop();
            mediaPlayer.release();
        }
    }
 
     @Override
     public   void  onStart(Intent intent, int  startId) {
        Log.v(TAG, "onStart" );
         if  (intent != null ) {
            Bundle bundle = intent.getExtras();
             if  (bundle != null ) {
 
                 int  op = bundle.getInt( "op" );
                 switch  (op) {
                 case   1 :
                    play();
                     break ;
                 case   2 :
                    stop();
                     break ;
                 case   3 :
                    pause();
                     break ;
                }
 
            }
        }
 
    }
 
     public   void  play() {
         if  (!mediaPlayer.isPlaying()) {
            mediaPlayer.start();
        }
    }
 
     public   void  pause() {
         if  (mediaPlayer != null  && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }
    }
 
     public   void  stop() {
         if  (mediaPlayer != null ) {
            mediaPlayer.stop();
             try  {
                 // 在调用stop后如果需要再次通过start进行播放,需要之前调用prepare函数
                mediaPlayer.prepare();
            } catch  (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
 
}




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

(2)通过bindService来启动的Service;
通过bindService(intent, sc, Context.BIND_AUTO_CREATE)来启动服务的,
我们需要定义ServiceConnectionnn,并实现里面的方法,当服务绑定成功后会调用ServiceConnectionnn中的回调函数:
public void onServiceConnected(ComponentName name, IBinder service),如下:
private  ServiceConnection sc = new  ServiceConnection() {
         @Override
         public   void  onServiceDisconnected(ComponentName name) {
            musicService = null ;
            Log.d(TAG, "in onServiceDisconnected" );
        }
         
         @Override
         public   void  onServiceConnected(ComponentName name, IBinder service) {
            musicService = ((BindMusicService.MyBinder)(service)).getService();
             if (musicService != null ){
                musicService.play();
            }
             
            Log.d(TAG, "in onServiceConnected" );
        }
    };
回调函数里面使用musicService = ((BindMusicService.MyBinder)(service)).getService();来获取BindMusicService服务对象,有了BindMusicService实例对象,就可以调用服务提供的各种控制音乐播放的哦功能。
下面看看BindMusicService.java的实现:
/**
* @author allin.dev
* http://allin.cnblogs.com/
*/
public class BindMusicService extends Service {

private static final String TAG = "MyService";
private MediaPlayer mediaPlayer;

private final IBinder binder = new MyBinder();

public class MyBinder extends Binder {
BindMusicService getService() {
return BindMusicService.this;
}
}

/*
* (non-Javadoc)
* @see android.app.Service#onBind(android.content.Intent)
*/
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
play();
return binder;
}

@Override
public void onCreate() {
super.onCreate();

Log.d(TAG, "onCreate");
Toast.makeText(this, "show media player", Toast.LENGTH_SHORT).show();


}

@Override
public void onDestroy() {
super.onDestroy();

Log.d(TAG, "onDestroy");
Toast.makeText(this, "stop media player", Toast.LENGTH_SHORT);
if(mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release();
}
}


public void play() {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(this, R.raw.tmp);
mediaPlayer.setLooping(false);
}
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
}

public void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}

public void stop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
try {
// 在调用stop后如果需要再次通过start进行播放,需要之前调用prepare函数
mediaPlayer.prepare();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

}
我们看到Service中有个返回IBinder对象的onBind方法,这个方法会在Service被绑定到其他程序上时被调用,而这个IBinder对象和之前看到的onServiceConnected方法中传入的那个IBinder是同一个东西。应用和Service间就依靠这个IBinder对象进行通信。
(3)使用AIDL方式的Service(进行跨进程的通信);
要明白AIDL,首先要明白IBinder.其次好要明白什么是跨进程。
简单来说,大多数人都知道我们的app都在自己独立的进程工作,
Log.d( "TAG" "process id is "  + Process.myPid());  可以查看工作进程。
应用与应用之间属于不同的进程,应用与应用之间要通信,这叫做跨进程通信。但是也可以在自己的应用下开启一个私有进程。如下:
<service   
         android:name = ".MyService"   
         android:process = ":remote"   >   
     </service>  
在注册的时候将服务设置为远程服务,那么这个服务也就运行于另一个进程。
如果此时要与该服务通信,这就是跨进程通信。
AIDL(Android Interface Definition Language)  IPC机制是面向对象的,轻量级的。通过AIDL定义的接口可以实现服务器端与客户端的IPC通信。在Android上,一个进程不能简单的像访问本进程内存一样访问其他进程的内存。所以,进程间想要对话,需要将对象拆解为操作系统可以理解的基本数据单元,并且有序的通过进程边界。通过代码来实现这个数据传输过程是冗长乏味的,所幸的是android提供了AIDL工具来帮我们完成了此项工作。

AIDL接口的调用采用的是直接的函数调用方式,但你无法预知哪个进程(或线程)将调用该接口。同进程的线程调用和其他进程调用该接口之间是有所区别的:
  • 在同进程中调用AIDL接口,AIDL接口代码的执行将在调用该AIDL接口的线程中完成,如果在主UI线程中调用AIDL接口,那么AIDL接口代码的执行将会在这个主UI线程中完成。如果是其他线程,AIDL接口代码的执行将在service中完成。因此,如果仅仅是本进程中的线程访问该服务,你完全可以控制哪些线程将访问这个服务(但是如果是这样,那就完全没必要使用AIDL了,而采取Binder接口的方式更为合适)。
  • 远程进程(其他线程)调用AIDL接口时,将会在AIDL所属的进程的线程池中分派一个线程来执行该AIDL代码,所以编写AIDL时,你必须准备好可能有未知线程访问、同一时间可能有多个调用发生(多个线程的访问),所以ADIL接口的实现必须是线程安全的。
  • 可以用关键字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和pacakge语句之上的注释除外)。
  • aidl中只支持成员方法,不支持成员变量。 
我们通过一个例子来说明:
(服务端)YAIDLService工程里的包yy.service.aidl有以下三个文件
IAIDLService.aidl
package yy.service.aidl;  
interface IAIDLService {  
    String getName();  
}
将该.aidl文件保存在工程目录中的 src/目录下,当编译生成apk时,sdk 工具将会在 gen/ 目录下生成一个对应的IBiner接口的.java文件。
如果使用eclipse编写app,那么这个IBinder接口文件将会瞬间生成。
生成的接口包含一个名为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对象
实现ADIL接口时需要注意一下几点:
  • 不能保证所有对aidl接口的调用都在主线程中执行,所以必须考虑多线程调用的情况,也就是必须考虑线程安全。
  • 默认IPC调用是同步的。如果已知IPC服务端会花费很多毫秒才能完成,那就不要在Activity或View线程中调用,否则会引起应用程序挂起(Android可能会显示“应用程序未响应”对话框),可以试着在独立的线程中调用。
  • 不会将异常返回给调用方 
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类型如上程序。

服务端的AndroidMainfest.xml里注册Service
<service android:name = ".YAIDLService" android:process = ":remote">  
          <intent-filter>  
              <action android:name = "yy.service.aidl.IAIDLService" />  
          </intent-filter>  
       </service> 
总结:这里给出调用远端AIDL接口的步骤:
    1. 在 src/ 目录下包含.adil文件。
    2. 声明一个 IBinder 接口(通过.aidl文件生成的)的实例。
    3. 实现 ServiceConnection .
    4. 调用 Context.bindService() 绑定你的 ServiceConnection 实现类的对象(也就是远程服务端)。
    5. 在 onServiceConnected() 方法中会接收到IBinder对象(也就是服务端),调用 YourInterfaceName.Stub.asInterface((IBinder)service) 将返回值转换为YourInterface类型。
    6. 进行跨进程交互。

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


 

Android Service完全解析,关于服务你所需知道的一切(下 )

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值