Service远程服务

案例解析

需求场景:client和server建立远程连接后,client主动调用server方法增加消息事件,server定时推送给client消息事件。

服务端server

创建AIDL文件,并在其中定义与客户端通信的内容(即方法),并编译(Make Project)。

TestMathAidl.aidl类

package com.jinzifu.myserver;

import com.jinzifu.myserver.MediumEvent;
import com.jinzifu.myserver.INewEventArrivedListener;

interface TestMathAidl {

    List<MediumEvent> getMediumList();

    boolean addEvent(in MediumEvent mediumEvent);

    void registerListener(INewEventArrivedListener listener);

    void unRegisterListener(INewEventArrivedListener listener);
}

INewEventArrivedListener.aidl类

package com.jinzifu.myserver;
import com.jinzifu.myserver.MediumEvent;

interface INewEventArrivedListener {
    void onNewEventArrieved(in MediumEvent mediumEvent);
}

MediumEvent.aidl类

package com.jinzifu.myserver;

import com.jinzifu.myserver.MediumEvent;

parcelable MediumEvent;

注意:MediumEvent.java是属于Java类,应放在Java包下,其他aidl类应放在aidl包下。

AndroidManifest.xml类

//自定义服务权限
<permission android:name="com.jinzifu.myserver.permission.ACCESS_EVENT_SERVICE"
        android:protectionLevel="normal"/>

<service
      android:name=".TestServerService">
      <intent-filter>
          <action android:name="com.jinzifu.myserver.TestMathAidl" />
      </intent-filter>
</service>

注意:宜将AIDL相关的文件都放到同一个包中,便于从server端复制到client端。
因为客户端需要反序列化服务端中和AIDL接口相关的所有文件,如果类的完整路径不一致,就无法反序列化成功。

TestServerService:通过IBinder的实现类实现远程业务方法。

public class TestServerService extends Service {
    //① CopyOnWriteArrayList 队列支持并发读写,且最终以ArrayList的类型数据通信,同理ConcurrentHashMap。
    CopyOnWriteArrayList<MediumEvent> mediumEvents =
            new CopyOnWriteArrayList<>();
    //② RemoteCallbackList 是系统专门提供的用于管理跨进程接口回调实例的,且在客户端进程终止后,能自动移除客户端注册的Listener,内部还实现了线程同步。
    RemoteCallbackList<INewEventArrivedListener> INewEventArrivedListeners
            = new RemoteCallbackList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        //③ 模拟远程服务定时推送消息给客户端,会遍历RemoteCallbackList的listener接口,参考观察者模式。
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        Thread.sleep(5000);
                        MediumEvent mediumEvent = new MediumEvent("" + System.currentTimeMillis(), 10);
                        //beginBroadcast与finishBroadcast要配对使用
                        final int size = INewEventArrivedListeners.beginBroadcast();
                        for (int i = 0; i < size; i++) {
                            INewEventArrivedListener iNewEventArrivedListener = INewEventArrivedListeners.getBroadcastItem(i);
                            iNewEventArrivedListener.onNewEventArrieved(mediumEvent);
                        }
                        INewEventArrivedListeners.finishBroadcast();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new TestMathAidl.Stub() {
            @Override
            public List<MediumEvent> getMediumList() throws RemoteException {
                return mediumEvents;
            }

            //④ 重写onTransact方法,对多进程/多应用的客户端连接申请权限验证
            @Override
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                    throws RemoteException {
                //⑤ server端自定义权限,client端注册权限后才能绑定成功
                int result = checkCallingPermission(
                        "com.jinzifu.myserver.permission.ACCESS_EVENT_SERVICE");
                if (result == PackageManager.PERMISSION_DENIED) {
                    Log.e("MainActivity", "远程服务权限校验=" + result);
                    return false;
                }
                return super.onTransact(code, data, reply, flags);
            }

            @Override
            public boolean addEvent(MediumEvent mediumEvent) throws RemoteException {
                return mediumEvents.add(mediumEvent);
            }

            @Override
            public void registerListener(INewEventArrivedListener listener)
                    throws RemoteException {
                INewEventArrivedListeners.register(listener);
            }

            @Override
            public void unRegisterListener(INewEventArrivedListener listener)
                    throws RemoteException {
                INewEventArrivedListeners.unregister(listener);
            }
        };
    }
}

核心知识点:如上源码中注释位置

  • CopyOnWriteArrayList 队列支持并发读写,且最终以ArrayList的类型数据通信,同理ConcurrentHashMap。
  • RemoteCallbackList 是系统专门提供的用于管理跨进程接口回调实例的,且在客户端进程终止后,能自动移除客户端注册的Listener,内部还实现了线程同步。
  • 模拟远程服务定时推送消息给客户端,会遍历RemoteCallbackList的listener接口,参考观察者模式。
  • 重写onTransact方法,对多进程/多应用的客户端连接申请权限验证。
  • server端自定义权限,client端注册权限后才能绑定成功。

客户端client

MainActivity类

...
ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //① onServiceConnected和onServiceDisconnected回调是在client的UI线程中的。
            testMathAidl = TestMathAidl.Stub.asInterface(iBinder);
            if (testMathAidl == null || !iBinder.isBinderAlive()) return;
            Log.e("MainActivity", "远程服务连接成功");

            //② 调用远程服务service的方法会阻塞client的UI线程,此须在子线程中调用。
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    try {
                        testMathAidl.registerListener(mINewEventArrivedListener);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            //③ 正常解绑服务不会回调此方法,远程服务service失联后会回调此方法,可在此重连服务
            Log.e("MainActivity", "远程服务失联");
        }
    };

    INewEventArrivedListener mINewEventArrivedListener = new INewEventArrivedListener.Stub() {

        @Override
        public void onNewEventArrieved(MediumEvent mediumEvent) throws RemoteException {
            //④ 接收远程服务端的回调,此处是在binder线程池中,更新UI需异步消息机制
            Log.e("MainActivity", "新事件名字=" + mediumEvent.name);
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService();
    }

    private void unbindService() {
        if (testMathAidl != null) {
            try {
                //⑤ 断开服务时,需要解注册listener并解绑service。
                testMathAidl.unRegisterListener(mINewEventArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            unbindService(serviceConnection);
            testMathAidl = null;
        }
    }
...    

核心知识点:如上源码中注释位置

  • onServiceConnected和onServiceDisconnected回调是在client的UI线程中的。
  • 调用远程服务service的方法会阻塞client的UI线程,此须在子线程中调用。
  • 正常解绑服务不会回调此方法,远程服务service失联后会回调此方法,可在此重连服务。
  • 接收远程服务端的回调,此处是在binder线程池中,更新UI需异步消息机制。
  • 断开服务时,需要解注册listener并解绑service。

Android Manifest.xml类

 <uses-permission android:name="com.jinzifu.myserver.permission.ACCESS_EVENT_SERVICE"/>

LOG日志:

com.jinzifu.myclient E/MainActivity: 远程服务连接成功
com.jinzifu.myclient E/MainActivity: 事件个数=1
com.jinzifu.myclient E/MainActivity: 新事件名字=1567330942898
com.jinzifu.myclient E/MainActivity: 新事件名字=1567330947903
com.jinzifu.myclient E/MainActivity: 新事件名字=1567330952911
com.jinzifu.myclient E/MainActivity: 新事件名字=1567330957915
com.jinzifu.myclient E/MainActivity: 新事件名字=1567330962919

基于AIDL的IPC问题汇总

  • 重复实例问题

Android为每个应用分配一个独立的虚拟机,或者为每个进程分配独立的虚拟机,不同的虚拟机在内存分配上有不同的地址,导致不同进程中,访问同一个类会有多个对象实例。

  • 静态成员变量,单例模式失效;
  • 依附于进程的线程同步机制失效;
  • SharedPreferences持久化失效(同理读写文件并发);
  • Application实例重复创建;

在进程之间通过共享内存的方式通信,都会失败的。

  • 并发问题

AIDL方法是在服务端的Binder线程池中执行的,当多个客户端同时连接服务端时,会有并发读写的问题。CopyOnWriteArrayList 队列支持并发读写,且最终以ArrayList的类型数据通信,同理ConcurrentHashMap。

  • 接口回调问题

客户端注册Listener对象实参传到服务端时,服务端收到的是一个新的Listener对象,导致在解注册时无法成功移除客户端Listener。
RemoteCallbackList是系统专门提供的用于管理跨进程接口回调实例的,且在客户端进程终止后,能自动移除客户端注册的Listener,内部还实现了线程同步。

对象实例是不能通过跨进程直接传输的,对象的跨进程本质是反序列的过程,传递的是值不是引用。

  • 权限验证问题
  1. onTransact:onTransact方法返回值来做权限验证,避免任意进程调用远程服务。【推荐】
  2. 自定义Permission:在服务端的onBind()中校验自定义的permission,如果通过了校验,正常返回Binder对象,否则返回null。

常用的权限验证都要在server的清单文件中注册自定义权限。

  • 线程挂起引起的ANR问题

客户端需在工作线程中调用服务端方法。客户端listener的回调方法是在客户端的binder线程池中,若更新UI需hander异步通信机制。

  • onServiceConnnected和onServiceDisconnected方法运行在客户端UI线程中。
  • 客户端调用远程服务方法时,客户端线程会被挂起,进入阻塞状态。
  • 通过binder 调用对方的方法,对方的方法都是运行在自身的binder线程池中,耗时操作无需另开线程。
//判断是否是UI线程
Log.d("MainActivity", "" + (Looper.myLooper() == getMainLooper()));//true
  • Binder死亡问题

Binder运行在服务端进程中,如果服务端异常终止,客户端与服务端的连接就会中断,即Binder死亡。

两种监听binder死亡的方法:

  • Binder的两个方法linkToDeath和unlinkToDeath,通过linkToDeath为Binder设置死亡代理,当连接中断时客户端就会收到通知。
  • 远程service异常关闭时会调用onServiceDisconnected()方法,在此可执行服务的重连机制。【推荐】

linkToDeath的binderDied方法是在Binder线程池中被调用的,onServiceDisconnected()在客户端的UI线程中回调的。

linkToDeath会在service异常关闭时,移除连接信息,并回调onServiceDisconnected方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容