美文网首页
Android 判断当前网络类型是否为5G

Android 判断当前网络类型是否为5G

作者: cain07 | 来源:发表于2021-02-26 09:43 被阅读0次

项目中若存在判断当前网络类型,一般都会用到如下方法:

/**
 * get the network type
 *
 * @param ctx Context
 * @return networktype
 */
public static int getNetWorkType(Context ctx) {
    int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
    TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);
    try {
        int defaultDataSubId = getSubId();
        if (defaultDataSubId == -1) {
            networkType = tm.getNetworkType();
        } else {
            try {
                Method dataNetworkType = TelephonyManager.class
                        .getDeclaredMethod("getDataNetworkType", new Class[]{int.class});
                dataNetworkType.setAccessible(true);
                networkType = (int) dataNetworkType.invoke(tm, defaultDataSubId);
            } catch (Throwable t) {
                networkType = tm.getNetworkType();
            }
        }
    } catch (Throwable t) {
        // do nothing
    }
    return networkType;
}

/**
 * get data sub id
 *
 * @return subId
 */
private static int getSubId() {
    int defaultDataSubId = -1;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
        defaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
    }
    return defaultDataSubId;
}

至于networkType的取值,可参考TelephonyManager类。

使用上述方法,在5G网络之前都没问题,到5G网络时,查看Android Q源码,发现TelephonyManager类里增加了这么一个类型常量:

/** Current network is NR(New Radio) 5G. */
public static final int NETWORK_TYPE_NR = TelephonyProtoEnums.NETWORK_TYPE_NR; // 20.

意味着在5G网络下上述两个方法返回的networkType应该是20,实际测试了下,然并卵,返回的是13(NETWORK_TYPE_LTE,表示4G)。
是Android系统出问题了?还是厂商定制ROM出问题了?
查看运营商5G网络的搭建,发现现在5G网络基本都是通过4G增强型基站发射,也就是所谓的非独立组网(NSA),所以系统获取到的是NETWORK_TYPE_LTE,是没错的。只有独立5G组网(SA),系统才会返回NETWORK_TYPE_NR。
可厂商的手机明明就可以显示5G网络标识,不一致了,怎么搞?

网传华为提供了自己的接口判断是否为5G网络,亲试确实可正常使用,如下代码:

/**
 * get huawei network type
 *
 * @return networkType
 */
public static int getHwNetworkType(Context ctx) {
    int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
    if (VERSION.SDK_INT >= VERSION_CODES.O
            && ctx.checkSelfPermission(permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
        try {
            TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);
            ServiceState ss;
            int subId = SubscriptionManager.getDefaultDataSubscriptionId();
            if (subId < 0) {
                ss = tm.getServiceState();
            } else {
                Class<TelephonyManager> classTm = TelephonyManager.class;
                Method method = classTm
                        .getDeclaredMethod("getServiceStateForSubscriber", new Class[]{int.class});
                method.setAccessible(true);
                Object objSs = method.invoke(tm, subId);
                ss = (ServiceState) objSs;
            }

            Method hwGetNetworkMethod = ServiceState.class.getMethod("getHwNetworkType");
            hwGetNetworkMethod.setAccessible(true);
            Integer hwNetType = (Integer) hwGetNetworkMethod.invoke(ss);
            if (hwNetType != null) {
                networkType = hwNetType;
            }
        } catch (Exception e) {
            QLog.e(tag, QLog.USR, "getHwNetworkType throw ex", e);
        }
    }
    return networkType;
}

华为的搞定了,其它厂商呢?找了半天没找到类似接口,有点小无奈...

仔细看华为提供的接口,里面用到了ServiceState,在Android Q上此类有getNrState方法,"nr"跟NETWORK_TYPE_NR貌似有点儿像,也许这个方法正确判断5G,但是由于系统限制,此方法无法反射调用。但里面的nrState在toString()方法里面出现了,是不是可以搞事情?于是有了下面通用的方法:

public class NetworkUtil {

public static final int NETWORK_TYPE_NR = 20;
public static final int SDK_VERSION_Q = 29;

/**
 * get the network type
 *
 * @param ctx Context
 * @return networktype
 */
public static int getNetWorkType(Context ctx) {
    int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
    TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);
    try {
        int defaultDataSubId = getSubId();
        if (defaultDataSubId == -1) {
            networkType = tm.getNetworkType();
        } else {
            try {
                Method dataNetworkType = TelephonyManager.class
                        .getDeclaredMethod("getDataNetworkType", new Class[]{int.class});
                dataNetworkType.setAccessible(true);
                networkType = (int) dataNetworkType.invoke(tm, defaultDataSubId);
            } catch (Throwable t) {
                networkType = tm.getNetworkType();
            }
        }
    } catch (Throwable t) {
        // do nothing
    }
    if (networkType == TelephonyManager.NETWORK_TYPE_LTE) {
        networkType = adjustNetworkType(ctx, networkType);
    }
    return networkType;
}

/**
 * get the 5G network type
 *
 * @param ctx Context
 * @param networkTypeFromSys this method can be call only when networkTypeFromSys = 13(LET)
 * @return correct network type
 */
private static int adjustNetworkType(Context ctx, int networkTypeFromSys) {
    int networkType = networkTypeFromSys;
    if (VERSION.SDK_INT >= SDK_VERSION_Q
            && ctx.checkSelfPermission(permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED) {
        try {
            TelephonyManager tm = (TelephonyManager) ctx
                    .getSystemService(Context.TELEPHONY_SERVICE);
            ServiceState ss;
            int defaultDataSubId = getSubId();
            if (defaultDataSubId == -1) {
                ss = tm.getServiceState();
            } else {
                try {
                    Class<TelephonyManager> infTm = TelephonyManager.class;
                    Method method = infTm
                            .getDeclaredMethod("getServiceStateForSubscriber",
                                    new Class[]{int.class});
                    method.setAccessible(true);
                    ss = (ServiceState) method.invoke(tm, defaultDataSubId);
                } catch (Throwable t) {
                    ss = tm.getServiceState();
                }
            }
            if (ss != null && isServiceStateFiveGAvailable(ss.toString())) {
                networkType = NETWORK_TYPE_NR;
            }
        } catch (Exception e) {
            // do nothing
        }
    }
    return networkType;
}

/**
 * get data sub id
 *
 * @return subId
 */
private static int getSubId() {
    int defaultDataSubId = -1;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
        defaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
    }
    return defaultDataSubId;
}

/**
 * check the service state str is 5G
 *
 * @param ss services state str
 * @return true if is 5G
 */
private static boolean isServiceStateFiveGAvailable(String ss) {
    boolean available = false;
    if (!TextUtils.isEmpty(ss)
            && (ss.contains("nrState=NOT_RESTRICTED")
            || ss.contains("nrState=CONNECTED"))) {
        available = true;
    }
    return available;
}

}

此方法为通用方法,使用时只需调用NetworkUtil#getNetWorkType(Context)即可,当然前提还得先获取下READ_PHONE_STATE权限,各大厂商皆可用。

特别说明:因上面反射使用到的系统API有版本的限制,因此只能在targetSdkVersion P(28)及以下版本才可正常获取5G网络类型

相关文章

网友评论

      本文标题:Android 判断当前网络类型是否为5G

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