美文网首页Android开发Android开发经验谈Android开发
Android IPC 之Service 还可以这么理解

Android IPC 之Service 还可以这么理解

作者: 小鱼人爱编程 | 来源:发表于2021-11-16 12:13 被阅读0次

    前言

    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四大组件: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 线程池系列

    相关文章

      网友评论

        本文标题:Android IPC 之Service 还可以这么理解

        本文链接:https://www.haomeiwen.com/subject/eilhtrtx.html