【公开课实践】之即时网络监听架构

本系列是在某平台公开课学习后的实践总结。

需求场景

  1. 根据用户当前网络状态来决定下一步逻辑,如流量条件下打开MV需要弹出即将消耗流量的提示,WiFi则可以直接播放。
  2. 需要即时监听网络变化,如播放MV时,WiFi不稳定断开切换到流量,需要提示用户,避免消耗大量流量。
  3. 每次执行逻辑前都用if语句判断网络,不够优雅。

实现思路

在公开课方案的基础上,兼容Android 5.0前后机型。

  • 暴露NetworkManager调度中心给用户进行app初始化、页面注册。内部会处理版本判断,5.0及以上使用ConnectivityManager.NetworkCallback方案,以下使用BroadcastReceiver方案。(为何两个方案并行?使用NetworkCallback会比广播稳定,但他不支持5.0以下机型)
  • 页面注册后,写一个含有指定注解和符合约定的方法来监听网络变化,用法跟EventBus类似。
    @Network(netType = NetType.AUTO)
    public void netword(NetType netType) {
        switch (netType) {
            case AUTO:
                Log.d(Constants.LOG_TAG, "页面回调 -- AUTO ");
                break;
            case WIFI:
                Log.d(Constants.LOG_TAG, "页面回调 -- WIFI ");
                break;
            case CMNET:
                Log.d(Constants.LOG_TAG, "页面回调 -- CMNET ");
                break;
            case CMWAP:
                Log.d(Constants.LOG_TAG, "页面回调 -- CMWAP ");
                break;
            case NONE:
                Log.d(Constants.LOG_TAG, "页面回调 -- NONE ");
                break;
        }
    }
  • 用数据中心Map> mNetworkList存储每个页面的网络监听方法,其中Object表示页面如activity或fragment,List表示该页面的网络监听方法集合,MethodManager是进一步包装的方法bean。核心思路跟EventBus类似。
  • 网络变换后,在两种方案的监听处对上述map进行遍历推送。如BroadcastReceiveronReceiveConnectivityManager.NetworkCallbackonCapabilitiesChanged。所谓推送,就是从map中取出方法进行invoke。至此,页面中的监听方法得以执行。

调用层使用姿势

在APP里初始化。

public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //网络监听初始化
        NetworkManager.getDefault().init(this);
    }
}

在页面中注册监听。

public class MainActivity extends AppCompatActivity {
    private TextView tv;
    private StringBuilder sb = new StringBuilder();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.tv);
        //在需要进行网络监听的页面进行注册
        NetworkManager.getDefault().registerObserver(this);
    }

    //监听所有网络情况,根据业务需求来定
    @Network(netType = NetType.AUTO)
    public void netword(NetType netType) {
        switch (netType) {
            case AUTO:
                Log.d(Constants.LOG_TAG, "页面回调 -- AUTO ");
                sb.append("页面回调 -- AUTO ");
                break;
            case WIFI:
                Log.d(Constants.LOG_TAG, "页面回调 -- WIFI ");
                sb.append("页面回调 -- WIFI ");
                break;
            case CMNET:
                Log.d(Constants.LOG_TAG, "页面回调 -- CMNET ");
                sb.append("页面回调 -- CMNET ");
                break;
            case CMWAP:
                Log.d(Constants.LOG_TAG, "页面回调 -- CMWAP ");
                sb.append("页面回调 -- CMWAP ");
                break;
            case NONE:
                Log.d(Constants.LOG_TAG, "页面回调 -- NONE ");
                sb.append("页面回调 -- NONE ");
                break;
        }
        sb.append("\n");
        tv.setText(sb.toString());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //退出时注销网络监听
        NetworkManager.getDefault().unRegisterObserver(this);
        //如果是首页退出,还可以再次注销所有
        NetworkManager.getDefault().unRegisterAllObserver();
    }
}

底层实现

共9个类,重点在最后3个。

  • Network 注解
  • MethodManager 将方法和其他参数封装成对象
  • NetType 网络类型枚举
  • BaseUtils 基层代码抽取
  • Constants 常量类
  • NetworkUtils 网络工具类
  • NetStateReceiver 广播接收器
  • NetworkCallbackImpl 继承系统提供的网络变化回调NetworkCallback
  • NetworkManager 调度中心,提供给用户使用

按依赖顺序列出各类的详细实现。

  1. NetType
/**
 * 网络类型枚举
 */
public enum NetType {
    //有网络,包括WiFi、GPRS
    AUTO,
    //WiFi
    WIFI,
    //主要是为PC、笔记本电脑、PDA等利用GPRS上网服务
    CMNET,
    //主要是手机
    CMWAP,
    //无网络
    NONE
}
  1. Network
/**
 * 监听的网络类型注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Network {
    NetType netType() default NetType.AUTO;
}
  1. MethodManager
/**
 * 将方法和其他参数封装成对象
 */
public class MethodManager {
    //参数类型
    private Class mType;
    //网络类型
    private NetType mNetType;
    //方法
    private Method mMethod;

    public MethodManager(Class type, NetType netType, Method method) {
        mType = type;
        mNetType = netType;
        mMethod = method;
    }

    public Class getType() {
        return mType;
    }

    public void setType(Class type) {
        mType = type;
    }

    public NetType getNetType() {
        return mNetType;
    }

    public void setNetType(NetType netType) {
        mNetType = netType;
    }

    public Method getMethod() {
        return mMethod;
    }

    public void setMethod(Method method) {
        mMethod = method;
    }
}
  1. Constants
/**
 * 常量类
 */
public class Constants {
    public static final String LOG_TAG = "network_monitor_tag";
    public static final String ANDROID_NET_CHANGE_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
    //跳转设置回调请求码
    public static final int SETTING_REQUEST_CODE = 123;
}
  1. NetworkUtils
/**
 * 网络工具类
 */
public class NetworkUtils {
    /**
     * 网络是否可用
     *
     * @return
     */
    @SuppressLint("MissingPermission")
    public static boolean isNetworkAvailable() {
        ConnectivityManager manager = (ConnectivityManager) NetworkManager.getDefault().getApplication()
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (null == manager) {
            return false;
        }
        NetworkInfo[] infos = manager.getAllNetworkInfo();
        if (null != infos) {
            for (NetworkInfo info : infos) {
                if (info.getState() == NetworkInfo.State.CONNECTED) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获取网络类型
     *
     * @return
     */
    @SuppressLint("MissingPermission")
    public static NetType getNetType() {
        ConnectivityManager manager = (ConnectivityManager) NetworkManager.getDefault().getApplication()
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (null == manager) {
            return NetType.NONE;
        }
        NetworkInfo info = manager.getActiveNetworkInfo();
        if (null == info) {
            return NetType.NONE;
        }
        int nType = info.getType();
        if (nType == ConnectivityManager.TYPE_MOBILE) {
            if (info.getExtraInfo().toLowerCase().equals("cmnet")) {
                return NetType.CMNET;
            } else {
                return NetType.CMWAP;
            }
        } else if (nType == ConnectivityManager.TYPE_WIFI) {
            return NetType.WIFI;
        }
        return NetType.NONE;
    }

    /**
     * 打开网络设置界面
     */
    public static void openSettings(Context context, int requestCode) {
        Intent intent = new Intent("/");
        ComponentName cn = new ComponentName("com.android.settings",
                "com.android.settings.WirelessSettings");
        intent.setComponent(cn);
        intent.setAction("android.intent.action.VIEW");
        ((Activity) context).startActivityForResult(intent, requestCode);
    }
}
  1. BaseUtils
/**
 * 基层代码,无需细看
 */
public class BaseUtils {
    /**
     * 推送(遍历并执行方法集)
     */
    public static void post(NetType netType, Map> networkList) {
        Set keySet = networkList.keySet();
        for (Object object : keySet) {
            List methodList = networkList.get(object);
            if (null != methodList) {
                for (MethodManager methodManager : methodList) {
                    if (methodManager.getType().isAssignableFrom(netType.getClass())) {
                        switch (methodManager.getNetType()) {
                            case AUTO:
                                invoke(methodManager, object, netType);
                                break;
                            case WIFI:
                                if (netType == NetType.WIFI || netType == NetType.NONE) {
                                    invoke(methodManager, object, netType);
                                }
                                break;
                            case CMNET:
                                if (netType == NetType.CMNET || netType == NetType.NONE) {
                                    invoke(methodManager, object, netType);
                                }
                                break;
                            case CMWAP:
                                if (netType == NetType.CMWAP || netType == NetType.NONE) {
                                    invoke(methodManager, object, netType);
                                }
                                break;
                            case NONE:
                                if (netType == NetType.NONE) {
                                    invoke(methodManager, object, netType);
                                }
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
        }
    }

    /**
     * 方法执行,抛异常忽略即可
     */
    private static void invoke(MethodManager methodManager, Object object, NetType netType) {
        Method method = methodManager.getMethod();
        try {
            method.invoke(object, netType);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 找出页面中符合要求的方法并存储
     */
    public static List findAnnotationMethod(Object object) {
        List methodList = new ArrayList<>();
        Class clazz = object.getClass();
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            Network network = method.getAnnotation(Network.class);
            if (null == network) {
                continue;
            }
            Type returnType = method.getGenericReturnType();
            if (!"void".equals(returnType.toString())) {
                throw new RuntimeException("method return type must be void");
            }
            Class[] parameterTypes = method.getParameterTypes();
            if (1 != parameterTypes.length) {
                throw new RuntimeException("method parameter count must be one");
            }
            MethodManager methodManager = new MethodManager(parameterTypes[0], network.netType(), method);
            methodList.add(methodManager);
        }
        return methodList;
    }
}
 
 
  1. NetStateReceiver
/**
 * 广播接收器
 */
public class NetStateReceiver extends BroadcastReceiver {
    private NetType mNetType;
    private Map> mNetworkList;

    public NetStateReceiver() {
        //初始化为无网络
        mNetType = NetType.NONE;
        mNetworkList = new HashMap<>();
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (null != intent && null != intent.getAction()) {
            if (intent.getAction().equalsIgnoreCase(Constants.ANDROID_NET_CHANGE_ACTION)) {
                Log.d(Constants.LOG_TAG, "network change");
                mNetType = NetworkUtils.getNetType();
                if (NetworkUtils.isNetworkAvailable()) {
                    Log.d(Constants.LOG_TAG, "network connect");
                } else {
                    Log.d(Constants.LOG_TAG, "network disconnect");
                }
                BaseUtils.post(mNetType, mNetworkList);
            }
        }
    }

    /**
     * 页面注册
     *
     * @param object 可以是activity、fragment
     */
    public void registerObserver(Object object) {
        List methodList = mNetworkList.get(object);
        if (methodList == null) {
            methodList = BaseUtils.findAnnotationMethod(object);
            mNetworkList.put(object, methodList);
        }
    }

    /**
     * 页面注销网络监听
     *
     * @param object
     */
    public void unRegisterObserver(Object object) {
        if (!mNetworkList.isEmpty()) {
            mNetworkList.remove(object);
        }
    }

    /**
     * 注销所有页面的网络监听,一般用于首页
     */
    public void unRegisterAllObserver() {
        if (!mNetworkList.isEmpty()) {
            mNetworkList.clear();
        }
        NetworkManager.getDefault().getApplication().unregisterReceiver(this);
    }
}
  1. NetworkCallbackImpl
/**
 * 系统的网络监听回调
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class NetworkCallbackImpl extends ConnectivityManager.NetworkCallback {
    private NetType mNetType;
    private Map> mNetworkList;

    public NetworkCallbackImpl() {
        //初始化为无网络
        mNetType = NetType.NONE;
        mNetworkList = new HashMap<>();
    }

    @Override
    public void onAvailable(Network network) {
        super.onAvailable(network);
        Log.d(Constants.LOG_TAG, "NetworkCallbackImpl - onAvailable");
    }

    @Override
    public void onLost(Network network) {
        super.onLost(network);
        Log.d(Constants.LOG_TAG, "NetworkCallbackImpl - onLost");
        //onLost时不会触发onCapabilitiesChanged,所以这里单独推送
        BaseUtils.post(NetType.NONE, mNetworkList);
    }

    /**
     * onCapabilitiesChanged方法触发次数不定,但是有if语句保护,不会导致重复推送
     *
     * @param network
     * @param networkCapabilities
     */
    @Override
    public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
        super.onCapabilitiesChanged(network, networkCapabilities);
        Log.d(Constants.LOG_TAG, "NetworkCallbackImpl - onCapabilitiesChanged");
        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
            if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
                Log.d(Constants.LOG_TAG, "NetworkCallbackImpl - wifi");
                //推送
                BaseUtils.post(NetType.WIFI, mNetworkList);
            } else {
                Log.d(Constants.LOG_TAG, "NetworkCallbackImpl - not wifi");
                //推送,这里先用cmwap表示非WiFi
                BaseUtils.post(NetType.CMWAP, mNetworkList);
            }
        }
    }

    /**
     * 页面注册
     *
     * @param object 可以是activity、fragment
     */
    public void registerObserver(Object object) {
        List methodList = mNetworkList.get(object);
        if (methodList == null) {
            methodList = BaseUtils.findAnnotationMethod(object);
            mNetworkList.put(object, methodList);
        }
    }

    /**
     * 页面注销网络监听
     *
     * @param object
     */
    public void unRegisterObserver(Object object) {
        if (!mNetworkList.isEmpty()) {
            mNetworkList.remove(object);
        }
    }

    /**
     * 注销所有页面的网络监听,一般用于首页
     */
    public void unRegisterAllObserver() {
        if (!mNetworkList.isEmpty()) {
            mNetworkList.clear();
        }
    }
}
  1. NetworkManager
/**
 * 中间层,用于调度 NetworkCallbackImpl or NetStateReceiver
 * 5.0以下:NetStateReceiver、5.0及以上:NetworkCallbackImpl
 * 原因:NetworkCallbackImpl并不支持Android5.0之前的设备
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class NetworkManager {
    private static NetworkManager instance = new NetworkManager();
    private Application mApplication;
    private NetStateReceiver mReceiver;
    private NetworkCallbackImpl mNetworkCallback;

    private NetworkManager() {

    }

    public static NetworkManager getDefault() {
        return instance;
    }

    public Application getApplication() {
        if (null == mApplication) {
            throw new RuntimeException("application can't be null");
        }
        return mApplication;
    }

    @SuppressLint("MissingPermission")
    public void init(Application app) {
        mApplication = app;
        if (onLollipop()) {
            mNetworkCallback = new NetworkCallbackImpl();
            NetworkRequest.Builder builder = new NetworkRequest.Builder();
            NetworkRequest request = builder.build();
            ConnectivityManager cm = (ConnectivityManager) NetworkManager.getDefault().getApplication()
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            if (null != cm) {
                cm.registerNetworkCallback(request, mNetworkCallback);
            }
        } else {
            mReceiver = new NetStateReceiver();
            IntentFilter filter = new IntentFilter();
            filter.addAction(Constants.ANDROID_NET_CHANGE_ACTION);
            mApplication.registerReceiver(mReceiver, filter);
        }
    }

    public void registerObserver(Object object) {
        if (onLollipop()) {
            mNetworkCallback.registerObserver(object);
        } else {
            mReceiver.registerObserver(object);
        }
    }

    public void unRegisterObserver(Object object) {
        if (onLollipop()) {
            mNetworkCallback.unRegisterObserver(object);
        } else {
            mReceiver.unRegisterObserver(object);
        }
    }

    public void unRegisterAllObserver() {
        if (onLollipop()) {
            mNetworkCallback.unRegisterAllObserver();
        } else {
            mReceiver.unRegisterAllObserver();
        }
    }

    /**
     * 系统是否在5.0及以上
     *
     * @return
     */
    public static boolean onLollipop() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return true;
        }
        return false;
    }
}

运行效果

4.x机型:


【公开课实践】之即时网络监听架构_第1张图片

8.x机型:


【公开课实践】之即时网络监听架构_第2张图片
【公开课实践】之即时网络监听架构_第3张图片

心得

采用注解标记方法,然后存储到数据中心,后续进行遍历推送。这个思路可用于页面间通信、网络监听等。相比于接口回调更加解耦,在同时开启的页面数不会太多(数据中心不会太庞大)的场景下是个不错的选择。另外推送方式也支持多种场景,如EventBus按需手动post,网络监听由系统post。

后记

仅做了初步实现,还需持续优化,排查bug。
完整项目见github

你可能感兴趣的:(【公开课实践】之即时网络监听架构)