Android IPC 之Service 还可以这么理解

前言

IPC 系列文章:
建议按顺序阅读。

Android IPC 之Service 还可以这么理解
Android IPC 之Binder基础
Android IPC 之Binder应用
Android IPC 之AIDL应用(上)
Android IPC 之AIDL应用(下)
Android IPC 之Messenger 原理及应用
Android IPC 之服务端回调
Android IPC 之获取服务(IBinder)
Android Binder 原理换个姿势就顿悟了(图文版)

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调用方法如下:


image.png

需要注意的点是:

当再次开启一个已经存在的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调用方法流程:

image.png

解绑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可以实现此功能。


image.png

进程A的Activity展示UI需要数据,这些数据从进程B获取。进程B开启Service生产数据,进程A通过绑定进程B的Service,从而获得IBinder引用,最终调用Service的方法获取数据。关键点就是中间的连接桥梁--Binder。
可以看出,Service的重点应该是在进程间通信。

接下来的文章,将重点分析IPC 过程涉及的Binder、AIDL等知识。

本文基于Android 10.0

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营学习Android

1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 彻底厘清
7、Android Window 如何确定大小/onMeasure()多次执行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标彻底明了
11、Android Activity/Window/View 的background
12、Android Activity创建到View的显示过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑惑
16、Java 线程池系列

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容