Android 4.4 - APN类型扩展(一)

客户要求同时启动多个apn进行数据连接,包含默认数据及三方app。也就是说只需要扩展一个即可。

由于时间较为久远,本文大概记录下修改内容。

层级 核心组件 在APN连接中的关键作用 对开发者的影响/可见性
应用与框架层 ConnectivityManager 接收应用请求,管理所有网络连接状态的总调度员。 应用主要通过它来查询网络状态getActiveNetworkInfo)。
Telephony框架层 GsmDataConnectionTracker (DCT) APN连接管理的绝对核心。它维护所有APN列表、状态机,决定何时、如何建立连接。 对应用层不可见。但APN的配置(名称、类型、代理)通过它生效。
RIL层 Radio Interface Layer (RIL) 连接Android框架与Modem的"翻译官"。将Java请求转为Modem能懂的AT指令 (如ATD*99***1#)。 普通应用无法触及。定制系统时,可通过RIL.javarild守护进程介入。
基带与网络 Modem (基带芯片) 执行物理层连接,与运营商网络进行PPP/L2TP协商PDP上下文激活,最终建立数据通道。 完全由硬件和运营商网络决定。开发者能做的只有确保APN参数正确。
问题难点:
  1. "一个默认APN"原则 :系统设计上,同一时间只有一个"默认"的蜂窝数据APN(通常是default类型)处于活动状态,用于承载几乎所有互联网流量。mmssupl等特殊类型APN仅在需要时临时建立连接,用完即断,无法长期并存。这是实现真正"多路并发"在系统框架层面的根本障碍。

  2. 缺少网络绑定API :在Android 5.0之前,系统没有提供类似ConnectivityManager.requestNetwork()Network.bindProcessToNetwork()的API。这意味着应用无法主动选择或绑定到某个特定的APN连接,只能被动使用系统当前的默认数据连接。

1、定义新的 APN 类型常量

首先,需要在框架的核心常量定义文件中添加新的类型。

frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneConstants.java

复制代码
    /** SPRD: APN type for XCAP */
    public static final String APN_TYPE_XCAP = "xcap";
    /** lichang: APN type for VOICE */
    public static final String APN_TYPE_VOICE = "data";    //自定义
...

    public static final String FEATURE_ENABLE_DM = "enableDM";
    public static final String FEATURE_ENABLE_WAP = "enableWAP";
    public static final String FEATURE_ENABLE_STK = "enableSTK";
    public static final String FEATURE_ENABLE_XCAP = "enableXCAP";
    /* @} */
    /* lichang */
    public static final String FEATURE_ENABLE_VOICE = "enableVOIP";    //自定义
2、扩展 APN 类型列表和匹配逻辑

直接增加自定义类型 TYPE_MOBILE_VOICE

frameworks/base/core/java/android/net/ConnectivityManager.java

复制代码
    /**
     * The absence of a connection type.
     * @hide
     */
    public static final int TYPE_NONE        = -1;

    /**
     * The Mobile data connection.  When active, all data traffic
     * will use this network type's interface by default
     * (it has a default route)
     */
    public static final int TYPE_MOBILE      = 0;
    /**
     * The WIFI data connection.  When active, all data traffic
     * will use this network type's interface by default
     * (it has a default route).
     */
    public static final int TYPE_WIFI        = 1;
    /**
     * An MMS-specific Mobile data connection.  This network type may use the
     * same network interface as {@link #TYPE_MOBILE} or it may use a different
     * one.  This is used by applications needing to talk to the carrier's
     * Multimedia Messaging Service servers.
     */
    public static final int TYPE_MOBILE_MMS  = 2;
    /**
     * A SUPL-specific Mobile data connection.  This network type may use the
     * same network interface as {@link #TYPE_MOBILE} or it may use a different
     * one.  This is used by applications needing to talk to the carrier's
     * Secure User Plane Location servers for help locating the device.
     */
    public static final int TYPE_MOBILE_SUPL = 3;
    /**
     * A DUN-specific Mobile data connection.  This network type may use the
     * same network interface as {@link #TYPE_MOBILE} or it may use a different
     * one.  This is sometimes by the system when setting up an upstream connection
     * for tethering so that the carrier is aware of DUN traffic.
     */
    public static final int TYPE_MOBILE_DUN  = 4;
    /**
     * A High Priority Mobile data connection.  This network type uses the
     * same network interface as {@link #TYPE_MOBILE} but the routing setup
     * is different.  Only requesting processes will have access to the
     * Mobile DNS servers and only IP's explicitly requested via {@link #requestRouteToHost}
     * will route over this interface if no default route exists.
     */
    public static final int TYPE_MOBILE_HIPRI = 5;
    /**
     * The WiMAX data connection.  When active, all data traffic
     * will use this network type's interface by default
     * (it has a default route).
     */
    public static final int TYPE_WIMAX       = 6;

    /**
     * The Bluetooth data connection.  When active, all data traffic
     * will use this network type's interface by default
     * (it has a default route).
     */
    public static final int TYPE_BLUETOOTH   = 7;

    /**
     * Dummy data connection.  This should not be used on shipping devices.
     */
    public static final int TYPE_DUMMY       = 8;

    /**
     * The Ethernet data connection.  When active, all data traffic
     * will use this network type's interface by default
     * (it has a default route).
     */
    public static final int TYPE_ETHERNET    = 9;

    /**
     * Over the air Administration.
     * {@hide}
     */
    public static final int TYPE_MOBILE_FOTA = 10;

    /**
     * IP Multimedia Subsystem.
     * {@hide}
     */
    public static final int TYPE_MOBILE_IMS  = 11;

    /**
     * Carrier Branded Services.
     * {@hide}
     */
    public static final int TYPE_MOBILE_CBS  = 12;

    /**
     * A Wi-Fi p2p connection. Only requesting processes will have access to
     * the peers connected.
     * {@hide}
     */
    public static final int TYPE_WIFI_P2P    = 13;

    /**
     * The network to use for initially attaching to the network
     * {@hide}
     */
    public static final int TYPE_MOBILE_IA = 14;


    /** SPRD : add by spreadst. @{ */
    /** {@hide} */
    public static final int TYPE_MOBILE_DM = 15;

    /** {@hide} */
    public static final int TYPE_MOBILE_STK = 16;

    /** {@hide} */
    public static final int TYPE_MOBILE_XCAP = 17;

    /** {@hide} */
    public static final int TYPE_MOBILE_WAP = 35;

    /* lichang voice*/
    public static final int TYPE_MOBILE_VOICE = 36;    //自定义

//    /** {@hide} */
//    public static final int MAX_RADIO_TYPE   = TYPE_WIFI_P2P;
//
//    /** {@hide} */
//    public static final int MAX_NETWORK_TYPE = TYPE_WIFI_P2P;

    /** {@hide} */
    public static final int MAX_TYPE_FOR_ONE_SIM = 100;

    /** {@hide} */
    public static final int MAX_RADIO_TYPE   = TYPE_MOBILE_VOICE + TYPE_MOBILE_WAP + TelephonyManager.getPhoneCount() * MAX_TYPE_FOR_ONE_SIM;

    /** {@hide} */
    public static final int MAX_NETWORK_TYPE = TYPE_MOBILE_VOICE + TYPE_MOBILE_WAP + TelephonyManager.getPhoneCount() * MAX_TYPE_FOR_ONE_SIM;
    /** @} */

    /* SPRD this is the origin value of MAX_NETWORK_TYPE, MUST BE MERGED WHEN VALUE CHANGED IN NEW RELEASE */
    /** {@hide} */
    public static final int MAX_NETWORK_TYPE_ORIGIN = TYPE_MOBILE_IA + TYPE_MOBILE_VOICE;

注意最大网络类型也需要把新加入的加上,避免无法启用。

然后将对应关系也加上

frameworks/base/core/java/android/net/ConnectivityManager.java

复制代码
    /**
     * Returns a non-localized string representing a given network type.
     * ONLY used for debugging output.
     * @param type the type needing naming
     * @return a String for the given type, or a string version of the type ("87")
     * if no name is known.
     * {@hide}
     */
    public static String getNetworkTypeName(int type) {
        switch (type) {
            case TYPE_MOBILE:
                return "MOBILE";
            case TYPE_WIFI:
                return "WIFI";
            case TYPE_MOBILE_MMS:
                return "MOBILE_MMS";
            case TYPE_MOBILE_VOICE:    //自定义
                return "MOBILE_VOICE";
            case TYPE_MOBILE_SUPL:
                return "MOBILE_SUPL";

...


    /**
     * Checks if a given type uses the cellular data connection.
     * This should be replaced in the future by a network property.
     * @param networkType the type to check
     * @return a boolean - {@code true} if uses cellular network, else {@code false}
     * {@hide}
     */
    public static boolean isNetworkTypeMobile(int networkType) {
        switch (networkType) {
            case TYPE_MOBILE:
            case TYPE_MOBILE_MMS:
            case TYPE_MOBILE_VOICE:    //自定义
            case TYPE_MOBILE_SUPL:
            case TYPE_MOBILE_DUN:
            case TYPE_MOBILE_HIPRI:
            case TYPE_MOBILE_FOTA:
            case TYPE_MOBILE_IMS:
            case TYPE_MOBILE_CBS:
            case TYPE_MOBILE_IA:
                return true;
            default:
                return false;
        }
    }

完成定义的话app就可以去尝试启动了,然后就会发现没有任何反应,因为自定义类型并没有对应的映射。

3、扩展数据连接管理逻辑 (DCT)

frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java

复制代码
public class DcTracker extends DcTrackerBase {
    protected final String LOG_TAG = "DCT";

    public static boolean DBG = true;
    /* SPRD : supported apn types @{ */
    private ArrayList<HashMap<String,Boolean>> ApnFilters = null;
    private static final String[] mSupportedApnTypesFilters = {
        PhoneConstants.APN_TYPE_ALL,
        PhoneConstants.APN_TYPE_DEFAULT,
        PhoneConstants.APN_TYPE_MMS,
        PhoneConstants.APN_TYPE_SUPL,
        PhoneConstants.APN_TYPE_DUN,
        PhoneConstants.APN_TYPE_HIPRI,
	PhoneConstants.APN_TYPE_VOICE        //自定义
    };

...

    //***** Constructor

    public DcTracker(PhoneBase p) {
        super(p);
        if (DBG) log("GsmDCT.constructor");
        ...
        int phoneId = getPhoneId();
        cm.supplyMessenger(ConnectivityManager.getNetworkTypeByPhoneId(phoneId, ConnectivityManager.TYPE_MOBILE), new Messenger(this));
	// lichang add voice
	cm.supplyMessenger(ConnectivityManager.getNetworkTypeByPhoneId(phoneId, ConnectivityManager.TYPE_MOBILE_VOICE), new Messenger(this));    //自定义,这里很关键
        cm.supplyMessenger(ConnectivityManager.getNetworkTypeByPhoneId(phoneId, ConnectivityManager.TYPE_MOBILE_MMS), new Messenger(this));
        cm.supplyMessenger(ConnectivityManager.getNetworkTypeByPhoneId(phoneId, ConnectivityManager.TYPE_MOBILE_SUPL), new Messenger(this));
        cm.supplyMessenger(ConnectivityManager.getNetworkTypeByPhoneId(phoneId, ConnectivityManager.TYPE_MOBILE_DUN), new Messenger(this));
        cm.supplyMessenger(ConnectivityManager.getNetworkTypeByPhoneId(phoneId, ConnectivityManager.TYPE_MOBILE_HIPRI), new Messenger(this));

frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java

复制代码
    protected int apnTypeToId(String type) {
        if (TextUtils.equals(type, PhoneConstants.APN_TYPE_DEFAULT)) {
            return DctConstants.APN_DEFAULT_ID;
        } else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_VOICE)) {	// lichang add voice
            return DctConstants.APN_VOICE_ID;
        } else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_MMS)) {
            return DctConstants.APN_MMS_ID;

frameworks/base/core/java/android/net/MobileDataStateTracker.java

复制代码
    public static String networkTypeToApnType(int netType) {
        switch(netType) {
            case ConnectivityManager.TYPE_MOBILE:
                return PhoneConstants.APN_TYPE_DEFAULT;  // TODO - use just one of these
            case ConnectivityManager.TYPE_MOBILE_MMS:
                return PhoneConstants.APN_TYPE_MMS;
            case ConnectivityManager.TYPE_MOBILE_VOICE:    //自定义
                return PhoneConstants.APN_TYPE_VOICE;
            case ConnectivityManager.TYPE_MOBILE_SUPL:
                return PhoneConstants.APN_TYPE_SUPL;
            ...
            default:
                sloge("Error mapping networkType " + netType + " to apnType.");
                return null;
        }
    }

至此,系统已经支持自定义APN连接的能力,可以通过app去建立连接了。

app创建

由于我们需要在app创建,并不支持调用隐藏的方法,因此,通过反射

复制代码
        try {
            // 获取 ConnectivityManager 类
            Class<?> connectivityManagerClass = Class.forName("android.net.ConnectivityManager");
            // 获取 startUsingNetworkFeature 方法
            Method startUsingNetworkFeatureMethod = connectivityManagerClass.getMethod(
                    "startUsingNetworkFeature", int.class, String.class);
            Object connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE);
            // 调用 startUsingNetworkFeature 方法
            Object result = startUsingNetworkFeatureMethod.invoke(connectivityManager, 36, "enableVOIP");
            // 结果为-1绑定失败
            if (result instanceof Integer && ((int) result == 0 || (int) result == 1)) {
                Log.d(TAG, "bindVoipApn: success");
            } else {
                Log.d(TAG, "bindVoipApn: failed");
            }
        } catch (Exception e) {
            Log.e(TAG, "Exception occurred", e);
            isBind = false;
        }

同理,断开的方法如下

复制代码
        try {
            // 获取 ConnectivityManager 类
            Class<?> connectivityManagerClass = Class.forName("android.net.ConnectivityManager");
            // 获取 stopUsingNetworkFeature 方法
            Method stopUsingNetworkFeatureMethod = connectivityManagerClass.getMethod(
                    "stopUsingNetworkFeature", int.class, String.class);
            Object connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE);
            // 调用 stopUsingNetworkFeature 方法
            Object result = stopUsingNetworkFeatureMethod.invoke(connectivityManager, 36, "enableVOIP");
            // 结果为-1绑定失败
            if (result instanceof Integer && (int) result != -1) {
                Log.d(TAG, "stopBindVoiceApn: success");
            } else {
                Log.d(TAG, "stopBindVoiceApn: failed");
            }
        } catch (Exception e) {
            Log.e(TAG, "Exception occurred", e);
        }

可以发现,APN创建成功并且可以进行数据连接,但是app与创建的APN没有什么关系,这就是文章开头提出的两个难点,第一个已经实现了,第二个在下一篇文章介绍。

相关推荐
维构lbs智能定位20 小时前
工厂人员定位(一)融合定位技术如何重构安全生产与效率管理?(含系统架构、技术选型对比、实际应用)
python·物联网·智慧工厂·厂区人员定位系统·工厂人员定位·工厂定位系统
yufuu9820 小时前
进阶技巧与底层原理
jvm·数据库·python
2301_8174973320 小时前
使用Flask快速搭建轻量级Web应用
jvm·数据库·python
bubiyoushang88820 小时前
基于MATLAB的局部特征尺度分解(LCD)实现与优化
开发语言·matlab
hgz071020 小时前
堆内存分区
java·开发语言·jvm
索荣荣20 小时前
SpringBoot Starter终极指南:从入门到精通
java·开发语言·springboot
Warren9821 小时前
Allure 常用装饰器:实战用法 + 最佳实践(接口自动化)
运维·服务器·git·python·单元测试·自动化·pytest
lly20240621 小时前
HTML DOM 访问
开发语言
落羽的落羽21 小时前
【Linux系统】文件IO:理解文件描述符、重定向、缓冲区
linux·服务器·开发语言·数据结构·c++·人工智能·机器学习
2401_8414956421 小时前
【LeetCode刷题】翻转二叉树
python·算法·leetcode··递归·节点·翻转二叉树