目的:
1.Service的start和bind状态有什么区别
2.同一个Service,先startService,然后再bindService,如何把它停止掉
3.有注意到Service的onStartCommand方法的返回值么?不同返回值有什么区别
4.Service的生命周期方法onCreate,onStart,onBind等运行在哪个线程中?
介绍:
大多数朋友肯定都知道Service,它属于计算型组件,就是处理复杂的计算,或者下载,就是在后台长期运行,还有一个特点:生命周期很长,没有界面。作为Android的四大组件的一员,可看出其重要性。我得理解是:service和activity是兄弟,一个有界面,一个没有界面而儿。它在日常生活中,我们也肯定接触得到,Service能在后台中进行一些长期运行的服务,例如听歌 和下载等等。通常在应用管理查看手机运行中的app,都会看到一个app都有两到四个服务。
用法:
第一种方式:通过startService启动Service
1.首先新建一个TestService继承Service,必须重写onBind方法,这里我们重写onCreate,onStartCommand,onDestroy方法,如下图:
public class TestService extends Service {
private String TAG = "TestService";
@Override
public void onCreate(){
super.onCreate();
Log.d(TAG,"concreate()方法执行");
}
@Override
public int onStartCommand(Intent intent,int flags,int startId){
Log.d(TAG,"onstartcommand()方法执行");
return super.onStartCommand(intent,flags,startId);
}
@Override
public void onDestroy(){
Log.d(TAG,"ondestroy执行");
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind()方法执行");
return null;
}
}
在每个方法分别打印一句话,就是为了验证开启服务和关闭服务都走哪个方法。
2.程序的主布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.nova.servicedemo.activity.MainActivity">
<TextView
android:id="@+id/tv_startservice"
android:layout_width="200dp"
android:layout_height="40dp"
android:text="开始服务"
android:layout_centerInParent="true"
android:background="@drawable/bg_startservice"
android:gravity="center"
android:textColor="@color/white"/>
<TextView
android:id="@+id/tv_stopservice"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_centerHorizontal="true"
android:layout_below="@id/tv_startservice"
android:layout_marginTop="20dp"
android:background="@drawable/bg_stopservice"
android:text="停止服务"
android:gravity="center"
android:textColor="#FFFFFF"/> <TextView
android:id="@+id/tv_finish
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_centerHorizontal="true"
android:layout_below="@id/tv_stopservice"
android:text="finish"
android:gravity="center"/>
</RelativeLayout>可看到只有两个TextView,一个是开启服务,另一个是停止服务。
3.在主界面里面加入启动Service和停止Service的逻辑,如下所示:
public class MainActivity extends Activity {
@Bind(R.id.tv_startservice)
TextView tv_startservice;
@Bind(R.id.tv_stopservice)
TextView tv_stopservice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@OnClick({R.id.tv_stopservice,R.id.tv_startservice,R.id.tv_finish})
void Onclick(View v){
switch(v.getId()){
case R.id.tv_startservice:
Intent startIntent = new Intent(MainActivity.this, TestService.class);
startService(startIntent);
break;
case R.id.tv_stopservice:
Intent stopIntent = new Intent(MainActivity.this,TestService.class);
stopService(stopIntent);
break;
case R.id.tv_finish:
finish();
break; } }}
逻辑很简单,就是新建一个intent启动service和停止service.4.最后也要在AndroidManifest.xml中进行注册:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".service.TestService"/>
</application>
现在运行程序,点击开启服务,看到KogCat打印:
可看到当启动一个service的时候,会调用该Service的onCreate()和onStartCommand()方法。这时候如果再点击一次开始服务,看看LogCat打印什么
可见onCreate()方法没有执行,只执行onStartCommand()方法。也就是说,当用start方式来启动服务时,如果该Service已经启动了,如果再次调用startService()方法,onCreate()方法都不会执行。点击停止服务的时候,会执行onDestroy()方法,就不贴图了。
当服务启动了,点击finish,service没有执行任何方法,也就是说service没有销毁,后台一直启动。
第二种方式:通过bindService启动Service
1.bindService启动的服务和调用者是client-server模式,调用者是client,service是server,service只有一个,但是绑定到service上面的client可以有多个。
2.client可以通过IBinder接口获取Service实例,实现在client端直接调用Service中的方法,startService方法启动中无法实现。
3.bindService启动服务的生命周期和其绑定的client息息相关,当client销毁时,client会自动和Service解除绑定,client也可以调用context的unbindService()方法与Service解除绑定,如果Service没有和任何的client绑定时,会自行销毁。
总而言之,通过bind启动,可以和组件建立关联。
修改Service中的方法,上面有个onBind()方法没有用到,这个方法时用于和组件建立关联:
public class TestService extends Service {
private String TAG = "TestService";
//通过binder实现调用者client与Service之间通信
private Mbinder mBinder = new Mbinder();
@Override
public void onCreate(){
super.onCreate();
Log.d(TAG,"concreate()方法执行");
}
@Override
public int onStartCommand(Intent intent,int flags,int startId){
Log.d(TAG,"onstartcommand()方法执行");
return super.onStartCommand(intent,flags,startId);
}
@Override
public void onDestroy(){
Log.d(TAG,"ondestroy执行");
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind()方法执行");
return mBinder;
}
class Mbinder extends Binder {
public void hello(){
Log.d(TAG,"hello方法执行");
}
}
}
修改xml中的代码,添加用于bind启动服务和取消绑定的方式:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.nova.servicedemo.activity.MainActivity">
<TextView
android:id="@+id/tv_startservice"
android:layout_width="200dp"
android:layout_height="40dp"
android:text="开始服务"
android:layout_centerHorizontal="true"
android:background="@drawable/bg_startservice"
android:gravity="center"
android:textColor="@color/white"
android:clickable="true"
android:layout_marginTop="20dp"/>
<TextView
android:id="@+id/tv_stopservice"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_centerHorizontal="true"
android:layout_below="@id/tv_startservice"
android:layout_marginTop="20dp"
android:background="@drawable/bg_stopservice"
android:text="停止服务"
android:gravity="center"
android:textColor="#FFFFFF"/>
<TextView
android:id="@+id/tv_bindservice"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_centerHorizontal="true"
android:layout_below="@id/tv_stopservice"
android:gravity="center"
android:text="使用bind开启服务"
android:layout_marginTop="20dp"
android:background="@drawable/bg_startservice"
android:textColor="@color/white"/>
<TextView
android:id="@+id/tv_unbindservice"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_centerHorizontal="true"
android:layout_below="@id/tv_bindservice"
android:background="@drawable/bg_stopservice"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="使用unbind service停止"
android:textColor="@color/white"/>
<TextView
android:id="@+id/tv_finish"
android:layout_width="200dp"
android:layout_height="40dp"
android:text="finish界面"
android:layout_marginTop="20dp"
android:layout_centerHorizontal="true"
android:background="@drawable/bg_startservice"
android:layout_below="@id/tv_unbindservice"
android:textColor="@color/white"
android:gravity="center"/>
</RelativeLayout>
下面继续修改主界面代码,让MainActivity和TestService之间建立关联,思想就是:创建ServiceConnection类型实例,重写onServiceConnected()方法和onServiceDisconnected()方法,当执行到onServiceConnected回调时,这样就可以实现client和Service的连接。当onServiceDisconnected回调被执行时,表示client和Server断开连接,这个方法可以处理断开连接后的事情。通过IBinder实例得到Service实例对象,有了这个实例,Activity和Service之间就变得紧密,就可以在activity中调用Mbinder的方法。
public class MainActivity extends Activity {
@Bind(R.id.tv_startservice)
TextView tv_startservice;//使用start方式启动服务
@Bind(R.id.tv_stopservice)
TextView tv_stopservice;//停止服务
@Bind(R.id.tv_bindservice)
TextView tv_bindservice;//使用bind方式开启服务
@Bind(R.id.tv_unbindservice)
TextView tv_unbindservice;//取消绑定
@Bind(R.id.tv_finish)
TextView tv_finish;
private TestService.Mbinder mBinder;
private String TAG = "TestService";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick({R.id.tv_stopservice,R.id.tv_startservice,R.id.tv_bindservice,R.id.tv_unbindservice,R.id.tv_finish})
public void Onclick(View v){
switch(v.getId()){
case R.id.tv_startservice:
Intent startIntent = new Intent(MainActivity.this, TestService.class);
startService(startIntent);
break;
case R.id.tv_stopservice:
Intent stopIntent = new Intent(MainActivity.this,TestService.class);
stopService(stopIntent);
break;
case R.id.tv_bindservice:
Intent bindIntent = new Intent(MainActivity.this,TestService.class);
bindService(bindIntent,conn,BIND_AUTO_CREATE);
break;
case R.id.tv_unbindservice:
unbindService(conn);
break;
case R.id.tv_finish:
finish();
break;
}
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = (TestService.Mbinder)service;
mBinder.hello();
Log.d(TAG,"connected()方法执行");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG,"disconnected()方法执行");
}
};
}
通过调用bindService()方法将activity和service进行绑定,bindService()方法有三个参数,第一个参数是构建出的intent对象,第二个是ServiceConnention实例,第三个是标志位,BIND_AUTO_CREATE表示在activity和Service建立关联后自动创建Service.
下面点击bindService,看下log的输出:
发现当执行bindService之后:
1.如果Service不存在,则执行Service执行onCreate(),onBind()方法,client实例ServiceConnection执行onServiceConnected()方法。
2.如果Service存在,不会执行任何方法。
当点击unbindservice之后:
会执行service的ondestroy()方法
测试步骤2:
step1:点击bindService
step2:点击finish
发现:
发现ondestroy()方法执行了,也就是说如果使用bind开启服务,如果组件销毁了,service也会自动销毁。
如何销毁Service
step1:点击startService
step2:点击BindService
如何销毁Service?
上图是同时点击start和bind开启服务所执行的方法。
现在点击stopService:
发现没有执行onDestroy()方法,也就是没有销毁Service.
现在再点击unbundservice:
发现service销毁了。
现在重复上面操作,开启start和bind同时开启服务,但是这次先点击unbindservice,看看service有没有销毁:
发现先按unbindservice service没有销毁,再按stopservice,发现service销毁了:
由此可得出结论:
当如果同一个Service,先startService,然后再bindService,销毁service的方法是:stopservice和unbindservice要同时执行。
论onStartCommand方法的返回值
留意到onStartCommand这个方法最后是返回int值,点击源码发现:会返回的值有四种:
1.START_STICKY
表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该service依然设置为started(运行状态),保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。也就是说如果service可以任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY.
2.START_NOT_STICKY
“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务
3.START_REDELIVER_INTENT
重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,Android系统会再次将service在被杀掉之前最后一次传入onStartCommand方法中的intent再次保留下来并再次传入到重新创建后的service的onStartCommand方法中,这样就可以读到intent参数。
4.START_STICKY_COMPATIBILITY
START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
系统的默认策略:
Service的生命周期方法onCreate,onStart,onBind等运行在哪个线程中?
现在我们在主界面和service的生命周期方法打印当前线程id的语句
在Mainactivity中打印:
Log.d("TestService","MianActivity 线程是:" +Thread.currentThread().getId());
在service的各个方法中打印:
Log.d("TestService","oncreate 线程是:" +Thread.currentThread().getId());
可看到:线程id是完全一样的,证实了service确实运行在主线程中。