概述
Service是Android四大组件之一,主要两个目的: 后台运行和跨进程访问。
通过启动一个Service,可以在不显示界面的前提下在后台运行指定的任务,这样可以不影响用户做其他事情。
通过跨进程服务(AIDL)可以实现不同进程之间的通信,这也是服务的重要的用途之一。
Service并没有实际界面,而是一直在Android系统的后台运行。 一般使用Service为应用程序提供一些服务,或者不需要界面的功能,例如从网络上下载文件,控制Video播放等。
Service生命周期
Service和Activity一样,也有一个从启动到销毁的过程,但是Service的这个过程比Activity的要简单的多。
通过startService方式启动的Activity的生命周期
Service从启动到销毁 只会经历如下三个阶段
- 创建服务
- 开始服务
- 销毁服务
一个服务需要继承自android.app.Service类,当服务经历以上三个阶段后,会分别和Service类中的三个方法交互:
创建服务
public void onCreate() ;
开始服务:
Service的onStart方法在API 5时被废弃,替代它的是onStartCommand方法。
public int onStartCommand(Intent intent, int flags, int startId)
销毁服务:
public void onDestory() ;
一个服务只会被创建一次,销毁一次,但是可以开始多次。
因此onCreate() 和onDestory()方法只会调用一次,而onStartCommand方法可以调用很多次。
执行startService时,Service会经历onCreate->onStartCommand。 当执行stopService时,直接调用onDestroy方法。 调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。 多次调用startService,该Service只能被创建一次,即该Service的onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用。Service的onStart方法在API 5时被废弃,替代它的是onStartCommand方法。
onStartCommand返回参数讲解
控制service运行的主要方式有两种,主要是根据onStartCommand方法返回的数值。方法:
1、START_STICKY
2、START_NOT_STICKY or START_REDELIVER_INTENT
这里主要解释这三个变量的意义:
1、 START_STICKY
在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建 service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent 。 如果返回START_STICKY,表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,但是onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息。如果你的Service可以在任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY,比如一个用来播放背景音乐功能的Service就适合返回该值。
2、 START_NOT_STICKY
在运行onStartCommand后service进程被kill后,并且没有新的intent传递给它。Service将移出开始状态,并且直到新的明显的方法(startService)调用才重新创建。因为如果没有传递任何未决定的intent那么service是不会启动,也就是期间onstartCommand不会接收到任何null的intent。
如果返回START_NOT_STICKY,表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service,当然如果在其被杀掉之后一段时间又调用了startService,那么该Service又将被实例化。那什么情境下返回该值比较恰当呢?如果我们某个Service执行的工作被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受,那么我们便可将 onStartCommand的返回值设置为START_NOT_STICKY。举个例子,某个Service需要定时从服务器获取最新数据:通过一个定时器每隔指定的N分钟让定时器启动Service去获取服务端的最新数据。当执行到Service的onStartCommand时,在该方法内再规划一个N分钟后的定时器用于再次启动该Service并开辟一个新的线程去执行网络操作。假设Service在从服务器获取最新数据的过程中被Android系统强制杀掉,Service不会再重新创建,这也没关系,因为再过N分钟定时器就会再次启动该Service并重新获取数据。
3、 START_REDELIVER_INTENT
在运行onStartCommand后service进程被kill后,系统将会再次启动service,并传入最后一个intent给onstartCommand。直到调用stopSelf(int)才停止传递intent。如果在被kill后还有未处理好的intent,那被kill后服务还是会自动启动。因此onstartCommand不会接收到任何null的intent。如果返回START_REDELIVER_INTENT,表示Service运行的进程被Android系统强制杀掉之后,与返回START_STICKY的情况类似,Android系统会将再次重新创建该Service,并执行onStartCommand回调方法,但是不同的是,Android系统会再次将Service在被杀掉之前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中,这样我们就能读取到intent参数。只要返回START_REDELIVER_INTENT,那么onStartCommand重的intent一定不是null。如果我们的Service需要依赖具体的Intent才能运行(需要从Intent中读取相关数据信息等),并且在强制销毁后有必要重新创建运行,那么这样的Service就适合返回START_REDELIVER_INTENT。
示例
编写一个服务类 观察服务的生命周期从开始到销毁的全部过程。
import android.app.Service; import android.content.Intent; import android.os.IBinder; import com.apkfuns.logutils.LogUtils; /** *MyService_StartService 是一个服务类,必须从android.app.Service类继承。 */ public class MyService_StartService extends Service { public MyService_StartService() { } @Override public IBinder onBind(Intent intent) { return null ; } /** * Service第一次被创建的时候调用改方法,只会调用一次。 */ @Override public void onCreate() { LogUtils.d("MyService_StartService onCreate"); super.onCreate(); } /** * Service 开始的时候调用该方法。 * @param intent * @param flags * @param startId * @return */ @Override public int onStartCommand(Intent intent, int flags, int startId) { LogUtils.d("MyService_StartService onStartCommand"); return super.onStartCommand(intent, flags, startId); } /** * Service被销毁时调用该方法,只会调用一次。 */ @Override public void onDestroy() { LogUtils.d("MyService_StartService onDestroy"); super.onDestroy(); } }
<service android:name=".activity.service.MyService_StartService" android:enabled="true" android:exported="true"/>
总结下创建和开始服务的步骤:
编写一个Service类,该类继承自android.app.Service.
Service类涉及3个生命周期方法,但这3个方法不一定在子类中都覆盖重写,可以根据不同的需求来决定重写哪些生命周期方法。 在Service类中有一个onBind()方法,该方法是一个抽象方法,在Service的子类中必须重写,这个方法在Activity和Service绑定的时候被调用。
在AndroidManifest.xml中使用<service>标签配置服务,一般将android:enable属性设置为true,表示Service处于激活状态,并使用android:name属性指定建立的服务类名。
系统并不会自动的启动Service,需要显示调用startService(),停止一个服务需要使用stopService().
通过bindService方式启动的Activity的生命周期
执行bindService时,Service会经历onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。 第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务。
import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import com.apkfuns.logutils.LogUtils; public class MyService_BindService extends Service { private MySBinder mySBinder = new MySBinder(); public MyService_BindService() { } @Override public void onCreate() { LogUtils.d("onCreate"); super.onCreate(); } /** * 返回Binder对象 * @param intent * @return */ @Override public IBinder onBind(Intent intent) { LogUtils.d("onBind"); return mySBinder ; } @Override public void onRebind(Intent intent) { LogUtils.d("onRebind"); super.onRebind(intent); } @Override public boolean onUnbind(Intent intent) { LogUtils.d("onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { LogUtils.d("onDestory"); super.onDestroy(); } /** * 定义MySBinder类,继承自Binder类,用于获取MyService_BindService类 */ class MySBinder extends Binder{ public MyService_BindService getMyService(){ return MyService_BindService.this ; } } }
第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务。
当关闭当前绑定Service的Activity时,onUnbind()和onDestory()会调用。
调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。
两种方式的比较
Started Service中使用StartService()方法来进行方法的调用,调用者和服务之间没有联系,即使调用者退出了,服务依然在进行。
BindService中使用bindService()方法来绑定服务,调用者和绑定者绑在一起,调用者一旦退出服务也就终止了【onCreate()->onBind()->onUnbind()->onDestroy()】。
绑定Activity和Service
如果我们希望在启动服务的Activity关闭后服务自动关闭,就需要将Activity和Service进行绑定了。
通过bindService方法可以将Activity和Service绑定,bindService方法的定义
public boolean bindService(Intent service, ServiceConnection conn , int flags);
参数说明:
第一个参数表示与服务相关联的Intent对象
第二个参数的类型是ServiceConnection,负责连接Intent对象指定的服务,通过ServiceConnection对象可以获得连接成功或者失败的状态,并且可以获得连接后的服务对象。
第三个参数是一个标志位,一般设置为Context.BIND_AUTO_CREATE。 这样就会在service不存在时创建一个.其它可选的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,不想指定时设为0即可.
应用组件(客户端)可以调用bindService()绑定到一个service.Android系统之后调用service的onBind()方法,它返回一个用来与service交互的IBinder
绑定是异步的.bindService()会立即返回,它不会返回IBinder给客户端.要接收IBinder,客户端必须创建一个ServiceConnection的实例并传
给bindService().ServiceConnection包含一个回调方法,系统调用这个方法来传递要返回的IBinder.
注:只有activities,services,和contentproviders可以绑定到一个service—你不能从一个broadcastreceiver绑定到service.
所以,从你的客户端绑定到一个service,你必须:
1实现ServiceConnection.
你的实现必须重写两个回调方法:
onServiceConnected()
系统调用这个来传送在service的onBind()中返回的IBinder.
OnServiceDisconnected()
Android系统在同service的连接意外丢失时调用这个.比如当service崩溃了或被强杀了.当客户端解除绑定时,这个方法不会被调用.
2调用bindService(),传给它ServiceConnection的实现.
3当系统调用你的onServiceConnected()方法时,你就可以使用接口定义的方法们开始调用service了.
4要与service断开连接,调用unbindService().
ServiceBindAct.java
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.view.View; import android.widget.Button; import android.widget.Toast; import com.apkfuns.logutils.LogUtils; import com.turing.base.R; public class ServiceBindAct extends Activity implements View.OnClickListener { private Button bindServiceBtn, unBindServiceBtn; private Intent serviceIntent ; private MyService_BindService myService ; private ServiceConnection serviceConnection = new ServiceConnection() { /** * 成功连接服务后,改方法被调用,在该方法中可以获得MyService_BindService对象 * @param name * @param service */ @Override public void onServiceConnected(ComponentName name, IBinder service) { // 获得MyService myService = ((MyService_BindService.MySBinder)service).getMyService(); Toast.makeText(ServiceBindAct.this,"Service Connected",Toast.LENGTH_SHORT).show(); } /** * 连接服务失败后,改方法被调用 * @param name */ @Override public void onServiceDisconnected(ComponentName name) { myService = null; Toast.makeText(ServiceBindAct.this,"Service Faield",Toast.LENGTH_SHORT).show(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_service_bind); setTitle("绑定Activity和Service"); LogUtils.d("ServiceLifeCycle Activity onCreate, Thread id:" + Thread.currentThread().getId()); // 初始化组件 bindServiceBtn = (Button) findViewById(R.id.BindService); unBindServiceBtn = (Button) findViewById(R.id.unBindService); // 注册监听事件 bindServiceBtn.setOnClickListener(this); unBindServiceBtn.setOnClickListener(this); serviceIntent = new Intent(ServiceBindAct.this,MyService_BindService.class); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.BindService: bindService(serviceIntent,serviceConnection, Context.BIND_AUTO_CREATE); break; case R.id.unBindService: unbindService(serviceConnection); break; default: break; } } }
MyService_BindService.java
import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import com.apkfuns.logutils.LogUtils; public class MyService_BindService extends Service { private MySBinder mySBinder = new MySBinder(); public MyService_BindService() { } @Override public void onCreate() { LogUtils.d("onCreate"); super.onCreate(); } /** * 返回Binder对象 * @param intent * @return */ @Override public IBinder onBind(Intent intent) { LogUtils.d("onBind"); return mySBinder ; } @Override public void onRebind(Intent intent) { LogUtils.d("onRebind"); super.onRebind(intent); } @Override public boolean onUnbind(Intent intent) { LogUtils.d("onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { LogUtils.d("onDestory"); super.onDestroy(); } /** * 定义MySBinder类,继承自Binder类,用于获取MyService_BindService类 */ class MySBinder extends Binder{ public MyService_BindService getMyService(){ return MyService_BindService.this ; } } }
AndroidManifest.xml中的配置
<service android:name=".activity.service.MyService_BindService" android:enabled="true" android:exported="true"></service>
在MyService_BindService 类中定义了一个MySBinder 类,该类用于获取MyService_BindService 对象。
ServiceConnection.onServiceConnected方法的第二个参数是一个IBinder类型的变量,将该参数转换成MyService_BindService .MySBinder对象,并使用MySBinder.getService方法获得MyService_BindService 对象,在获得MyService_BindService 后,就可以在Activity中任意操作MyService_BindService 了。
bindService和startService混合使用时
1.如果先bindService,再startService:
在bind的Activity退出的时候,Service会执行unBind方法而不执行onDestory方法,因为有startService方法调用过,所以Activity与Service解除绑定后会有一个与调用者没有关连的Service存在
2.如果先bindService,再startService,再调用Context.stopService
Service的onDestory方法不会立刻执行,因为有一个与Service绑定的Activity,但是在Activity退出的时候,会执行onDestory,如果要立刻执行stopService,就得先解除绑定
把上面的”如果先bindService,再startService”换成”如果先startService,再bindService”,结果是一样的
问题:
如果在一个Activity的onCreate方法中,
先
bindService(serviceIntent, conn, Context.BIND_AUTO_CREATE);
再startService(serviceIntent);
退出这个Activity时,会执行onUnBind
但是再次进入这个Activity的时候,为什么不执行onBind方法了?只有在这个Service销毁后(执行onDestory),再进这个Activity才会执行onBind,还有就是当有两个客户端时,在第一个客户端startServie启动服务再bindService绑定服务,这时跳到第二个客户端里(启动时会调用onBind()),再客户端startServie启动服务再bindService绑定服务,启动时不会调用用onBind()了(因为之前客户端已经启动后没有onDestory()销毁Service,所以再客户端第二次绑定服务时,只会返回IBinder对象给onServiceConnected()),而且要注意的是当,当第一个服务启动并绑定一个服务时,再跳去第二个服务端启动并绑定这个服务时,第二个服务端再解绑时,不会调用onUnbind(),只有回到第一个客户端时,解绑这是才会调用onUnbind(),顺序反过来结果是一样的。得出一个结论是:当一个服务没被onDestory()销毁之前,只有第一个启动它的客户端能调用它的onBind()和onUnbind()。
开机启动Service
判断Service是否已注册
Android SDK 并没有直接提供API来判断某个Service是否已经注册,但是我们可以通过PackageManager.queryIntentService方法根据IntentFilter来查询系统中某个或者某组服务。
首先修改AndroidManifest.xml中的Service标签,增加intent-filter标签以及一个action标签
<service android:name=".activity.service.MyService" android:enabled="true"> <intent-filter> <action android:name="com.turing.base.activity.service.MyService" /> </intent-filter> </service>
其中action标签中的 android:name就相当于要查找的ID
- 获取PackageManager ,通过隐式方式指定要查询的Service Action,获取服务列表,判断即可
/** * 判断某个服务是否注册 */ private void queryService() { PackageManager packageManager = getPackageManager(); // 指定要查询的Service Action Intent intent = new Intent("com.turing.base.activity.service.MyService"); // 查询服务 List<ResolveInfo> resolveInfos = packageManager.queryIntentServices( intent, PackageManager.GET_INTENT_FILTERS); if (resolveInfos.size() > 0) { ResolveInfo resolveInfo = resolveInfos.get(0); Toast.makeText(this, resolveInfo.toString(), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "服务未注册", Toast.LENGTH_SHORT).show(); } }
判断Service是否已运行
PackageManager.queryIntentService方法只能查到已注册的服务,不管这个服务是否正在运行都可以被查到,如果只想确定某个服务是否正在运行,就需要使用ActivityManager.getRunningServices方法了
public List<RunningServiceInfo> getRunningServices(int maxNum)
maxNum表示返回正在运行的服务的最大数,如果正在运行的服务小于maxNum,则按照实际数量返回,一般可以将该参数的值设置为一个较大的值,例如100.
/** * 判断服务是否已经开始 */ private void queryRunningService() { ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); // 返回所有正在运行的服务信息 List<ActivityManager.RunningServiceInfo> runningServiceInfos = activityManager.getRunningServices(1000); for (int i = 0; i < runningServiceInfos.size(); i++) { ActivityManager.RunningServiceInfo runningServiceInfo = runningServiceInfos.get(i); // 通过服务的类名来判断服务是否正在运行 if ("com.turing.base.activity.service.MyService".equals(runningServiceInfo.service.getClassName())) { Toast.makeText(ServiceDemoAct.this, "服务正在运行...", Toast.LENGTH_SHORT).show(); return; } } Toast.makeText(ServiceDemoAct.this, "服务没有开始", Toast.LENGTH_SHORT).show(); }
通过Service播放音乐
PlayMusicBackGroundAct.java
import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import com.turing.base.R; /** * 通过播放背景音乐的简单示例,演示startService的基本使用流程。 * 通过操纵Activity的按钮控制MusicService播放或停止播放音乐。 * 将一个名为sound.mp3的放到资源目录/res/raw文件夹下面,(As中没有的话,res右键new 选择android resource directory) * 这样我们在程序中就可以通过R.raw.sound引用该音乐文件,放入/res/raw文件夹中的资源文件会保持原来的面貌不会被编译成二进制。 */ public class PlayMusicBackGroundAct extends AppCompatActivity implements View.OnClickListener{ private Button btn_playMusic ,btn_stopMusic ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_play_music_back_ground); initView(); initEvents(); } private void initEvents() { btn_playMusic.setOnClickListener(this); btn_stopMusic.setOnClickListener(this); } private void initView() { btn_playMusic = (Button) findViewById(R.id.id_btn_playMusic); btn_stopMusic = (Button) findViewById(R.id.id_btn_stopMusic); } @Override public void onClick(View v) { Intent serviceIntent = new Intent(PlayMusicBackGroundAct.this,PlayMusicService.class); switch(v.getId()){ case R.id.id_btn_playMusic: startService(serviceIntent); this.finish(); break; case R.id.id_btn_stopMusic: stopService(serviceIntent); break; default: break; } } }
PlayMusicService.java
import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.os.IBinder; import android.widget.Toast; import com.apkfuns.logutils.LogUtils; import com.turing.base.R; import java.io.IOException; public class PlayMusicService extends Service { private MediaPlayer mediaPlayer ; // 是否正在播放,默认值false private boolean isReady = false ; public PlayMusicService() { } @Override public IBinder onBind(Intent intent) { // 该Service中没有bindService方法,所以此处直接返回null return null ; } /** * onCreate在Service的生命周期中只会调用一次 * 初始化的工作可以放到这里来做 */ @Override public void onCreate() { LogUtils.d("PlayMusic Service onCreate"); super.onCreate(); // 初始化媒体播放器 mediaPlayer = MediaPlayer.create(this, R.raw.sound); // 非空判断 if(mediaPlayer == null){ return ; } mediaPlayer.stop(); mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { mp.release(); stopSelf(); return false; } }); try { mediaPlayer.prepare(); isReady = true ; } catch (IOException e) { e.printStackTrace(); } if(isReady){ //将背景音乐设置为循环播放 mediaPlayer.setLooping(true); } } /** * 每次调用Context的startService都会触发onStartCommand回调方法 * 所以onStartCommand在Service的生命周期中可能会被调用多次 * @param intent * @param flags * @param startId * @return */ @Override public int onStartCommand(Intent intent, int flags, int startId) { LogUtils.d("PlayMusic Service onStartCommand"); // 如果 isReady 并且 多媒体没有在播放,则开始播放 if(isReady && !mediaPlayer.isPlaying()){ mediaPlayer.start(); Toast.makeText(this, "开始播放背景音乐", Toast.LENGTH_LONG).show(); } return START_STICKY ; } /** * 当调用Context的stopService或Service内部执行stopSelf方法时就会触发onDestroy回调方法 * 一般在此方法中释放资源 */ @Override public void onDestroy() { LogUtils.d("PlayMusic Service onDestroy"); super.onDestroy(); // 如果mediaPlayer不为空 if(mediaPlayer != null){ if(mediaPlayer.isPlaying()){ // 如果正在播放,停止播放音乐 mediaPlayer.stop(); } // 释放多媒体资源 mediaPlayer.release(); } } }
AndoridManifest.xml
<service android:name=".activity.service.PlayMusicService" android:enabled="true" />
Service元素常见配置项
在AndroidManifest.xml里Service元素常见选项
android:name ------------- 服务类名 android:label -------------- 服务的名字,如果此项不设置,那么默认显示的服务名则为类名 android:icon -------------- 服务的图标 android:permission ------- 申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务 android:process ---------- 表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字 android:enabled ---------- 表示是否能被系统实例化,为true表示可以,为false表示不可以,默认为true android:exported --------- 表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false