四大组件|service| 以服务的角度理解service

7 阅读4分钟

引言

service作为Android四大组件之一,在日常开发中会使用到,其中作为一个服务提供方向不同访问者提供访问功能是service很重要的一个能楼,今天在这里,重点讲解通过bind Service方式访问service 以一个服务提供者的视角,理解service

代码记录

package com.example.accounttest.service;

import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.example.accounttest.CallBack;
import com.example.accounttest.ServiceAIDL;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;



/**
 * 总结:从服务端的角度,理解bindService
 * 1、作为一个服务端,要明确自己的服务场景,主要的服务场景有两种(订阅类型、普通信息获取)
 * --订阅类型场景:例如当登录状变化时,通知所有注册的客户端。此类场景需要通过RemoteCallbackList对当前订阅的客户端进行保存,在状态变化时,集中通知所有客户端,相当于一对多的数据传输场景
 * --普通信息获取场景:例如客户端调用接口查询数据。此类场景不需要对callback进行额外的存储,直接使用callback进行回调就行,相当于一对一的数据传输
 * 2、从服务端的角度,在订阅通知场景,如何维护当前已经连接的客户端有哪些(客户端主动解绑、客户端异常崩溃解绑)
 * --客户端主动解绑:通过从callback中获取IBinder作为key的方式,保证在同一个客户端可以主动发起CallBack解绑,通知服务端客户端下线(因为跨进程通信导致每次获取到的都不是同一个CallBack对象)
 * --客户端异常崩溃解绑:通过监听死亡回调linkToDeath的形式,保证客户端在异常崩溃场景,服务端可以识别到客户端的解绑
 * 3、服务端的其他注意点
 * --mBinder对象的多线程问题:因跨进程通信,返回的数据都在binder线程,不一定是主线程,需要做线程安全处理
 * --RemoteCallbackList很好的封装了多线程、死亡监听、IBinder作为key的处理,是订阅类型服务的首选
 * --RemoteCallbackList 提供了cookie的能力,有额外字段要保存的话可以使用,可以参看room 组件中MultiInstanceInvalidationService的实现
 * 4、疑惑点
 * --客户端bindService 和 unBindService 服务端如何感知?bindService 多次,也仅会触发一次 onBind,unBindService服务端更是无感知
 */
public class ServerService extends Service {
    private static final String TAG = "whqq";

    //不建议直接存储CallBack的原因,是因为Binder接口调用,即便客户端使用的是同一个CallBack,也会因为跨进程通信,每次获取到的都不是同一个CallBack,使用set存储,会导致无法进行CallBack的解绑,而发生内存泄露
    private static final Set<CallBack> set = new HashSet<>();
    //使用HashMap<IBinder, CallBack>的形式,是对上面set的一个优化,因为跨进程通信,每次获取到的都不是同一个CallBack,但是他们的IBinder是相同的,因此可以用IBinder做key,来保证CallBack的解绑
    private static final HashMap<IBinder, CallBack> map = new HashMap<>();
    //RemoteCallbackList 相当于是系统提供的进阶版本HashMap<IBinder, CallBack>,其内部实现了以IBinder做key的存储,且同时通过DeathRecipient监听了非用户主动解绑(客户端崩溃异常解绑)与线程安全的处理,是推荐的使用方式
    private static final RemoteCallbackList<CallBack> reMoteList = new RemoteCallbackList<CallBack>() {
        @Override
        public void onCallbackDied(CallBack callback) {
            super.onCallbackDied(callback);
            Log.e(TAG, "有客户端解绑了" + callback.asBinder().toString());
        }
    };
    //RemoteCallbackList注意事项
    //1、RemoteCallbackList内部实现了DeathRecipient,并通过onCallbackDied提供给上层业务做额外的解绑操作,不需要自己实现linkToDeath去监听解绑
    //2、经测试,若自己实现了linkToDeath也无问题,两个回调都会触发

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind");
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG, "onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy");
    }

    private IBinder mBinder = new ServiceAIDL.Stub() {
        @Override
        public void getInfo(Bundle bundle, CallBack callBack) throws RemoteException {
            Log.e(TAG, "getInfo触发" + "客户端" + "传输值" + bundle.toString());
            Log.e(TAG, "binder对象" + callBack.asBinder().toString());
            Log.e(TAG, "callBack对象" + callBack.toString());
            String string = bundle.getString("type");
            if (string == null) {
                return;
            }

            switch (string) {
            case "同步查询个人信息":
                syncQuery(bundle, callBack);
                break;
            case "异步查询个人信息":
                aSyncQuery(bundle, callBack);
                break;
            case "订阅":
                set.add(callBack);
                Log.e(TAG, "当前对象数量" + set.size());
                reMoteList.register(callBack);
                callBack.asBinder().linkToDeath(new DeathRecipient() {
                    @Override
                    public void binderDied() {
                        Log.e(TAG, "有客户asdasdasda端解绑了");
                    }
                },0);
                Log.e(TAG, "当前remote对象数量" + reMoteList.beginBroadcast());
                reMoteList.finishBroadcast();
                map.put(callBack.asBinder(), callBack);
                Log.e(TAG, "当前map对象数量" + map.size());
                break;
            case "通知订阅":
                notifyAllClient();
                break;
            case "客户端请求断开连接":
                unBind(callBack);
                break;
            default:
            }


        }


        private void syncQuery(Bundle bundle, CallBack callBack) throws RemoteException {
            Bundle bundle1 = new Bundle();
            bundle1.putString("result", "结果回调");
            callBack.onResult(bundle1);
        }

        private void aSyncQuery(Bundle bundle, CallBack callBack) throws RemoteException {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(4000);

                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    Bundle bundle1 = new Bundle();
                    bundle1.putString("result", "结果回调");

                    try {
                        callBack.onResult(bundle1);
                    } catch (RemoteException e) {
                        throw new RuntimeException(e);
                    }
                }
            }).start();
        }


        private void unBind(CallBack callBack) {
            set.remove(callBack);
            Log.e(TAG, "当前对象数量" + set.size());
            reMoteList.unregister(callBack);
            Log.e(TAG, "当前remote对象数量" + reMoteList.beginBroadcast());
            reMoteList.finishBroadcast();
            map.remove(callBack.asBinder());
            Log.e(TAG, "当前map对象数量" + map.size());

        }


        private void notifyAllClient() {
            Bundle bundle1 = new Bundle();
            bundle1.putString("result", "订阅结果回调");
            try {
                for (CallBack callBack : set) {
                    callBack.onResult(bundle1);
                }
                int n = reMoteList.beginBroadcast();
                for (int i = 0; i < n; i++) {
                    reMoteList.getBroadcastItem(i).onResult(bundle1);
                }
                reMoteList.finishBroadcast();
                for (Map.Entry<IBinder, CallBack> entry : map.entrySet()) {

                    entry.getValue().onResult(bundle1);
                    // 使用key和value进行操作
                }
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }

        }
    };
}