Service详解
参考文章:https://blog.csdn.net/zxw136511485/article/details/53537993
service(服务)是安卓中的四大组件之一,它通常用作在后台处理耗时的逻辑,与Activity一样,它存在自己的生命周期,也需要在清单文件中配置相关信息。
Service的使用和启动方式
继承Service,实现onBind方法
(在生命周期里面打上日志)
public class TestService extends Service {
private static final String TAG = "TestService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "--------->onCreate: ");
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Log.e(TAG, "--------->onStart: ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "--------->onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, "--------->onDestroy: ");
super.onDestroy();
}
}
AndroidManifest.xml
注册一下Service
<service android:name=".TestService">
<intent-filter>
<action android:name="com.baiheng.androidcomponent.service" />
</intent-filter>
</service>
其中name
节点将用于Server的启动匹配
Server的两种启动方式startService
与bindService
startService
方式启动和停止
ps:关于Service的Intent
//5.0之后不能z这样
//Intent intent = new Intent("com.baiheng.androidcomponent.service");
intent = new Intent(MainActivity.this,TestService.class);
所以需要修改成下面这种方式
Intent intent = new Intent(MainActivity.this,TestService.class);
btStartService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.baiheng.androidcomponent.service");
startService(intent);
}
});
btStartStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.baiheng.androidcomponent.service");
stopService(intent);
}
});
查看一下生命周期
启动生命周期:
E/TestService: --------->onCreate:
--------->onStartCommand:
--------->onStart:
如果不停止,直接再次点击启动:
E/TestService: --------->onStartCommand:
--------->onStart:
停止生命周期:
E/TestService: --------->onDestroy:
-
1.可以看出,如果不停止直接startService 少掉一个onCreate的生命周期。也就是说如果Service没有被销毁,onCreate只会一次
-
2.onStartCommand()和onStart()的有什么关系。
在API 2.0之前,只有onStart()方法,而2.0之后,推荐使用onStartCommand()方法,其实onStartCommand()内部也是调用了onStart()方法,所以我们在开发中只重写onStartCommand()就可以了,而onStartCommand()方法的作用是告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止 等等。
在Service搞出一个ANR异常:
ANR触发条件:
1.只有主线程才会产生ANR,主线程就是UI线程;![![screenshot.png](https://upload-
2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;
3.上述事件响应超时,不同的context规定的上限时间不同
- a.主线程对输入事件5秒内没有处理完毕
- b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕
- c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。
在onCreate
搞一个50秒的延时操作
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "--------->onCreate: ");
try {
Thread.sleep(50*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ANR直接界面无响应了
05-23 15:44:11.783 452-470/system_process E/ActivityManager: ANR in com.baiheng.androidcomponent
PID: 15898
Reason: Executing service com.baiheng.androidcomponent/.TestService
这说明如果Service在主线程中,如果有耗时任务必须使用子线程。
通过bindService()启动Service
使用bindService()启动Service,这种模式是客户端-服务端模式(client-server),即服务端是Service,客户端是调用者,例如是某个Activity,客户端-服务端是可以相互通信的。
方法如下:
bindService(Intent service, ServiceConnection conn,int flags)
- 第一个参数:Intent指示对应的Service对象;
- 第二个参数:实现了 ServiceConnection接口的对象,conn是一个代表与service连接状态的类,当我们连接service成功或失败时,会主动触发其内部的onServiceConnected或onServiceDisconnected方法。如果我们想要访问service中的数据,可以在onServiceConnected()方法中进行实现;
- 第三个参数:Flags,在进行服务绑定时,其标志位可以为BIND_AUTO_CREATE、BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND等。其中BIND_AUTO_CREATE表示当收到绑定请求时,如果服务尚未创建,则即刻创建,在系统内存不足,需要先销毁优先级组件来释放内存,且只有驻留该服务的进程成为被销毁对象时,服务才可被销毁;BIND_DEBUG_UNBIND通常用于调试场景中判断绑定的服务是否正确,但其会引起内存泄漏,因此非调试目的不建议使用;BIND_NOT_FOREGROUND表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行,该标志位在Froyo中引入;
如果需要通信,需要在Service中添加Binder
private MyBinder binder = new MyBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "--------->onBind: ");
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, "--------->onUnbind: ");
return super.onUnbind(intent);
}
/**
* 得到Service
*/
class MyBinder extends Binder {
public TestService getService() {
return TestService.this;
}
}`
添加两个生命周期的监听: onBind和unBind 分别对应绑定和解绑
MyBinder继承Binder,并返回Server对象函数
最后在onCread中 返回了 binder 对象用于通信
好的,来onBind一下:
btServiceOnbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.baiheng.androidcomponent.service");
bindService(intent, MainActivity.this, Context.BIND_AUTO_CREATE);
}
});
//这里时解绑
btServiceUnbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unbindService(MainActivity.this);
}
});
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "-------------->onServiceConnected");
testService = ((TestService.MyBinder) service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "-------------->onServiceDisconnected");
testService = null;
}
具体参数已解释,值得注意的是,在ServiceConnection
接口中两个方法:
onServiceConnected
:连接时调用,用于得到Service对象
onServiceDisconnected
:onServiceDisconnected()方法在正常情况下是不被调用的,它的调用时机是当Service服务被异外销毁时,例如内存的资源不足时这个方法才被自动调用。
点击一下:生命周期
E/TestService: --------->onCreate:
--------->onBind:
E/MainActivity: -------------->onServiceConnected
再点一次:
E/MainActivity: -------------->onServiceConnected
可以看到只会onServiceConnected
,而不会再次onCreate和onBind
点击解绑:
E/TestService: --------->onUnbind:
--------->onDestroy:
值得注意的是:如果在没有绑定Service的情况下调用unbind,则会崩溃
java.lang.IllegalArgumentException: Service not registered: com.baiheng.androidcomponent.MainActivity@422fca80
提示说Service没有注册。因此,我们在解除绑定(调用unbindService())时,需要多注意。
可以添加一个isbind参数 是否已经bind
btServiceOnbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.baiheng.androidcomponent.service");
bindService(intent, MainActivity.this, Context.BIND_AUTO_CREATE);
isbind=true;
}
});
btServiceUnbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(isbind) {
unbindService(MainActivity.this);
isbind=false;
}
}
});
如果直接bind后,不unbind销毁当前的Activity,则会出现错误:
E/ActivityThread: Activity com.baiheng.androidcomponent.MainActivity has leaked ServiceConnection com.baiheng.androidcomponent.MainActivity@422f95b8 that was originally bound here
android.app.ServiceConnectionLeaked: Activity com.baiheng.androidcomponent.MainActivity has leaked ServiceConnection com.baiheng.androidcomponent.MainActivity@422f95b8 that was originally bound here
所以在onDestroy记得解绑:
@Override
protected void onDestroy() {
super.onDestroy();
if(isbind) {
unbindService(MainActivity.this);
isbind=false;
}
}
通过startService()和bindService()同时启动Service
启动函数
btServiceOnbindStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.baiheng.androidcomponent.service");
startService(intent);
bindService(intent, MainActivity.this, Context.BIND_AUTO_CREATE);
isbind=true;
}
});
生命周期,基本上就是合起来了:
E/TestService: --------->onCreate:
--------->onStartCommand:
--------->onStart:
--------->onBind:
E/MainActivity: -------------->onServiceConnected
点击一下stopService:
发现没有任何日志信息,说明无法使用stopService函数停止Service了。已经和启动者绑定了,如果要停止该Service,不仅要调用 stopService(intent)方法,还需要调用 unbindService(serviceConnection)方法
使用unbindService,只有unbind 没有销毁,可以继续运行,无法交互
E/TestService: --------->onUnbind:
然后使用stopService,才真正的销毁
E/TestService: --------->onDestroy:
通过上面的实例,我们可以看出,如果我们在启动一个Service时,同时调用了startService()和bindService(),那么在停止该Service时,也要同时调用 stopService(intent)和 unbindService(serviceConnection);否则,Service可能会未正常停止。
Service的启动方式总结
startService()和bindService()启动Service的区别
1.生命周期不同 如图
imageonCreate()和onDestory()是一对的
startService(): onStartCommand
bindService(): onBind unBind 一对 绑定和解绑
startService() onCreate 只会一次除非stopService
bindService() onCreate onbind 都只会一次
2.两者和启动者的关系。
startService()启动的Service和启动者的生命周期无关,必须要显示调用stopService(intent)方法,才能停止该Service;
bindService()启动的Service和启动者的生命周期有关,当启动者销毁时,会自动销毁该Service。
3.两者的具体使用场景。
startService(),常用于启动一个后台服务,例如推送服务、心跳服务,保持一个长久的链接;
bindService(),常用于调用一个远程连接服务,例如AIDL,使用远程服务暴露的功能。
特殊情况,如果你想搞一个长连接又需要和当前的Content交互,就需要startService和bindService一起使用了。
实战阶段
实战两种启动方式:
长链接:startService 与当前界面没有数据交互
这里写一个子线程假装发送心跳包:
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "--------->onCreate: ");
new Thread(new Runnable() {
@Override
public void run() {
handler.sendEmptyMessage(0);
}
}).start();
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
//暂停两秒
Log.e("TestLongCommService",count+"");
count++;
handler.sendEmptyMessageDelayed(0,2000);
}
};
startService启动服务之后:
05-24 08:55:04.757 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: --------->onCreate:
--------->onStartCommand:
--------->onStart:
05-24 08:55:04.777 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: 0
05-24 08:55:06.777 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: 1
05-24 08:55:08.777 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: 2
05-24 08:55:10.787 4205-4205/com.baiheng.androidcomponent E/TestLongCommService: 3
05-24 08:55:12.787
测试即使退出Activity或者回到桌面都不会停止,只有强制清理后台的时候才会退出。
类似的下载服务也可以写成长连接的服务,因为他和界面无关。只需要一个通知栏的进度提示即可,与当前界面无关。不会因为退出当前界面而停止下载。一般这种下载叫做全局下载。
当然也有基于当前页面的下载,如果页面关闭则关闭下载。所以需要使用onBind方式
OnBind局部下载
使用onbind下载当前页面的东西,并使用binder通信
TestDownloadService
public static final int MAX_PROGRESS = 100;
private int progress = 0;
private DownProcess downProcess;
public interface DownProcess {
void update(int i);
}
class MyBinder extends Binder {
public TestDownloadService getService() {
return TestDownloadService.this;
}
}
/**
* 模拟下载任务,每秒钟更新一次
*/
public void startDownLoad() {
new Thread(new Runnable() {
@Override
public void run() {
while (progress < MAX_PROGRESS) {
progress += 1;
if(downProcess!=null) {
downProcess.update(progress);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public void setDownProcess(DownProcess downProcess) {
this.downProcess = downProcess;
}
代码很简单,通过binder传递service对象,通过接口传出去数值
button4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(TestServiceActivity.this, TestDownloadService.class);
bindService(intent, conn2, Context.BIND_AUTO_CREATE);
}
});
ServiceConnection conn2 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
testDownloadService = ((TestDownloadService.MyBinder) service).getService();
//开始下载
testDownloadService.startDownLoad();
testDownloadService.setDownProcess(new TestDownloadService.DownProcess() {
@Override
public void update(int i) {
progressBar.setProgress(i);
}
});
}
@Override
public void onServiceDisconnected(ComponentName name) {
testDownloadService = null;
}
};
screenshot.png
很多人不明白,为什么在当前页面下载也需要开启一个服务去下载,直接在当前页面开线cheng下载不行吗。当然可以,但是谷歌官方建议下载这种操作应该放到服务里面去,因为他不需要直接的用户交互而且是长时间操作,为了避免用户误操作。需要放到后台服务器执行。
最后的解绑操作:
void doUnbindService() {
unbindService(conn2);
}
网友评论