码农A:看见标题我就震惊了。
码农B:我也是。
码农C:我琢磨着,UC震惊部还缺人吗?这小编是个人才啊。
某小编:标题是震惊的,但内容是朴实无华的。
前言
Android四大组件:Activity、Service、BroadcastReceiver、ContentProvider。它们的作用分别是:
Activity--->配合View展示界面
Service--->长时间在后台运行不与用户直接交互
BroadcastReceiver--->接收广播
ContentProvider--->提供数据给其他模块使用
本篇文章着重分析Service,通过它,你将了解到:
1、Service 开启与停止
2、Service 执行耗时操作
3、Service 与Thread、Manager关系
4、Service 进程间通信初相识
1、Service 开启与停止
先定义一个Service类,名为MyService,继承自Service。
public class MyService extends Service {
public MyService() {
super();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//必须重写该方法,该方法为抽象方法
//绑定开启Service会调用该方法
return null;
}
@Override
public void onCreate() {
//Service初次创建会调用该方法,我们可以做一些初始化操作, 与onDestroy()相对
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//每次显示启动Service都会调用该方法
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
//Service销毁时调用该方法,在该方法里我们可以做释放资源的操作,与onCreate()相对
super.onDestroy();
}
}
这是一个最简单的Service Demo。
接着想要使用该Service,还需要在AndroidManifest.xml里注册:
<service android:name=".service.MyService">
</service>
Service定义好了,怎么使用呢?开启Service有两种方式:
1、显示开启------> startService(Intent intent)
2、绑定开启------> bindService(Intent intent)
这俩都是Context里的方法
显示开启Service
构造Intent,传入startService(Intent intent)里。
private void startService() {
Intent intent = new Intent(this, MyService.class);
startService(intent);
}
通过此种方式,Service调用方法如下:
需要注意的点是:
当再次开启一个已经存在的Service的时候,onStartCommand(xx)依然会被调用。
显示关闭Service
显示开启Service后,Service就已经启动了。
若要关闭Service,通过如下方法:
private void stopService() {
Intent intent = new Intent(this, MyService.class);
stopService(intent);
}
或者在Service做完了事自己结束:
stopSelf();
绑定开启Service
通过上面的例子,可以看出显示开启Service后,调用者就和Service没有关联了。比如调用者是个Activity,Service的作用是不断地计数。在显示开启Service的场景下,会存在两个问题:
1、Activity无法直接(间接通过广播等方法)拿到Service计数结果,也就是说没法拿到Service引用。
2、当Activity退出的时候,若不是主动停止Service,那么Service将不会被关闭。不太恰当的比喻是:"管生不管养"
而绑定开启Service正好可以解决上面的问题。
为了实现绑定开启Service,在上面Demo的基础上稍微做修改。
定义MyBinder继承自Binder:
public class MyBinder extends Binder {
//持有Service引用
private Service service;
public MyBinder(Service service) {
this.service = service;
}
//返回Service引用
public Service getService() {
return service;
}
}
在显示开启Service过程中,我们重写了onBind(xx),直接返回的是null,该场景下该方法并没有调用。而当绑定开启Service时,需要返回IBiner的引用给绑定者使用。
#MyService.java
public IBinder onBind(Intent intent) {
return new MyBinder(this);
}
返回的是MyBinder对象的引用,该对象持有了MyService引用。
绑定者在哪里接收IBinder的引用呢?
在Activity里定义ServiceConnection匿名内部类:
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//service即是从onBind(xx)方法返回的(绑定者和Service同一进程)
MyBinder myBinder = (MyBinder)service;
//获取Service的引用
MyService myService = (MyService)myBinder.getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
//Service被销毁时调用(内存不足等,正常解绑不会走这)
}
};
有了ServiceConnection 引用,接着就需要和Service建立联系,建立联系的过程即是绑定开启Service的过程。
private void bindService() {
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
从上面可以看出,绑定开启的流程:
1、将Service和ServiceConnection建立联系(绑定开启)
2、在Service的onBind(xx)方法里返回IBinder引用,该引用持有Service引用
3、绑定成功后会调用ServiceConnection的onServiceConnected(xx)返回IBinder引用
4、通过IBinder引用就能拿到Service引用,进而操作Service
以上回答了上面的第一个问题:绑定者(Activity)无法拿到Service引用。
来看看绑定开启Service调用方法流程:
解绑Service
既然绑定时传入了ServiceConnection引用,可以猜测解绑时也需要传入ServiceConnection引用,不然无法确定解绑哪个Service。
private void unBindService() {
unbindService(serviceConnection);
}
手动调用该方法即可解绑Service。
当Activity绑定开启Service后,若是Activity销毁了,那么相应的Service也会被销毁掉。这就解答了第二个问题。
值得注意的是:
若是显示开启了Service,则无法用解绑方法关闭Service。
若是绑定开启了Service,则无法用显示关闭Service方法。
2、Service 执行耗时操作
在Service的onCreate(xx)方法里循环计数:
#MyService.java
@Override
public void onCreate() {
super.onCreate();
while(true) {
count++;
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
服务开启后,过一会就会提示ANR错误,说明onCreate(xx)是在主线程执行的。
来看看onCreate(xx)调用栈。
#ActivityThread.java
private class ApplicationThread extends IApplicationThread.Stub {
...
//IPC通信调用该方法,表明要创建服务
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
//发送Message
sendMessage(H.CREATE_SERVICE, s);
}
...
}
//中间调用省略
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
...
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
//mH 为在ActivityThread 里构造的Handler,也就是说在主线程里构造的Handler。
mH.sendMessage(msg);
}
再看看接收Message的地方:
#ActivityThread.java
public void handleMessage(Message msg) {
switch (msg.what) {
...
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
//创建Service
handleCreateService((CreateServiceData) msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
//绑定Service
handleBindService((BindServiceData) msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UNBIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
handleUnbindService((BindServiceData) msg.obj);
//解绑Service
schedulePurgeIdler();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...
}
}
以上分支最终会调用到MyService重写的onCreate(xx)、onBind(xx)、onUnbind(xx)里,这些方法都是在主线程里被调用的。
既然Service各个方法是在主线程里执行,那么想要实现计数功能得子开启线程来完成此事。
#MyService.java
@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
Log.d("time:", count + "");
count++;
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
}).start();
}
public int getCount() {
return count;
}
Service一直在计数,计数结果怎么通知给调用者呢,此处假设调用者是Activity。
依然是两种方式:
1、如果是显示开启的Service,则Service可选择广播将数据发送给Activity
2、如果是绑定开启的Service,Activity拿到IBinder引用,进而拿到Service引用,最终可以调用getCount()获得计数值,并更新UI。
当然如果觉得每次开启子线程很麻烦,可以选择Android 提供的IntentService,该类里封装了HandlerThread,并提供回调方法用以执行耗时任务。
3、Service 与Thread、Manager关系
Service 与 Thread联系
既然Service无法直接执行耗时操作,那么需要Service干嘛呢,还不如直接开启子线程执行任务呢?
前面说了:Service是长时间在后台运行。
实际上说的是Service的生命周期,也就是说Service对象一直存在,当我们需要使用Service的时候,通过Intent或者IBinder找到它,进而使用它提供的功能。
同样实现计数功能:
- 如果直接在Activity里开启Thread计数,当Activity退出的时候,要把Thread关闭了。再次开启Activity的时候,已经找不到Thread引用了,无法继续上次的累计计数。再者,就算不考虑内存泄漏,Activity退出时候不关闭Thread,再次开启Activity的时候,依然找不到Thread引用。
- 另外如果想将计数功能抽出来,供多个Activity使用,直接使用Thread也无法实现多Activity共用计数功能。
上面问题的本质就是需要维护一个对Thread对象的引用。而Thread仅仅是个工具而已,没必要维护全局的引用(那是线程池要做的工作)。
Service 与 Manager
既然维护Thread全局引用方法不太推荐,那么实现一个单例的Manager(管理类)来持有Thread,进而使用Thread执行耗时任务,而外界通过调用这个Manager来获取数据,如下:
public class CountManager {
private static volatile CountManager instance;
private CountManager(){
startCount();
}
private int count = 0;
public static CountManager getInstance() {
if (instance == null) {
synchronized (CountManager.class) {
if (instance == null) {
instance = new CountManager();
}
}
}
return instance;
}
private void startCount() {
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
Log.d("time:", count + "");
count++;
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
}).start();
}
public int getCount() {
return count;
}
}
事实上不少的项目都是采用Activity + Manager方式来实现页面展示 + 数据获取。
Activity展示UI,后台通过Manager获取数据,如从数据库获取或者从从网络获取等,最后将数据反馈给Activity用以刷新UI。
到此你可能疑惑了,都有了Manager了,Service还有使用的必要吗?
答案是肯定的。
Service作为Android 四大组件之一,是广泛使用于Android 系统里的。
1、Service可以调整优先级,尽可能避免在资源紧张的时候被销毁
2、借助Service + Binder,实现Android 进程间通信
4、Service 进程间通信初相识
之前的例子分析的都是调用者和被调用处于同一进程,试想一下,如果它们不在同一进程还能互相调用吗?如进程A里的Activity需要使用进程B里的CountManager,显然无法直接调用。
而对于Service来说,借助于Binder可以实现此功能。
进程A的Activity展示UI需要数据,这些数据从进程B获取。进程B开启Service生产数据,进程A通过绑定进程B的Service,从而获得IBinder引用,最终调用Service的方法获取数据。关键点就是中间的连接桥梁--Binder。
可以看出,Service的重点应该是在进程间通信。
接下来的文章,将重点分析IPC 过程涉及的Binder、AIDL等知识。
本文基于Android 10.0
原文地址:https://www.jianshu.com/p/f5e08b06bf7a
5、最后
Android学习是一条漫长的道路,我们要学习的东西不仅仅只有表面的 技术,还要深入底层,弄明白下面的 原理,只有这样,我们才能够提高自己的竞争力,在当今这个竞争激烈的世界里立足。
千里之行始于足下,愿你我共勉。
我把自己这段时间整理的Android最重要最热门的学习方向资料放在了我的GitHub:https://github.com/xieyuliang/Android-P7-share/blob/master/Android%E5%,里面还有面试题集合/面经、及系列技术文章等。
资源持续更新中,欢迎大家一起学习和探讨。
网友评论