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没有什么关系,这就是文章开头提出的两个难点,第一个已经实现了,第二个在下一篇文章介绍。

相关推荐
a程序小傲2 小时前
中国邮政Java面试被问:Kafka的Log Compaction实现和删除策略
java·开发语言·后端·python·面试·职场和发展·kafka
_OP_CHEN2 小时前
【测试理论与实践】(十)Web 项目自动化测试实战:从 0 到 1 搭建博客系统 UI 自动化框架
运维·自动化测试·python·测试开发·selenium·自动化·测试开发工程师
pulinzt2 小时前
【python】第四节引入模版+文件的读取
开发语言·python
@zulnger3 小时前
读写Excel
开发语言·python·excel
带土13 小时前
9. C++ 套接字(Socket)
开发语言·c++
molaifeng10 小时前
Go 语言如何实现高性能网络 I/O:Netpoller 模型揭秘
开发语言·网络·golang
崇山峻岭之间10 小时前
Matlab学习记录33
开发语言·学习·matlab
Evand J10 小时前
【2026课题推荐】DOA定位——MUSIC算法进行多传感器协同目标定位。附MATLAB例程运行结果
开发语言·算法·matlab
小二·10 小时前
Python Web 开发进阶实战:性能压测与调优 —— Locust + Prometheus + Grafana 构建高并发可观测系统
前端·python·prometheus