客户要求同时启动多个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.java和rild守护进程介入。 |
| 基带与网络 | Modem (基带芯片) | 执行物理层连接,与运营商网络进行PPP/L2TP协商 、PDP上下文激活,最终建立数据通道。 | 完全由硬件和运营商网络决定。开发者能做的只有确保APN参数正确。 |
问题难点:
"一个默认APN"原则 :系统设计上,同一时间只有一个"默认"的蜂窝数据APN(通常是
default类型)处于活动状态,用于承载几乎所有互联网流量。mms、supl等特殊类型APN仅在需要时临时建立连接,用完即断,无法长期并存。这是实现真正"多路并发"在系统框架层面的根本障碍。缺少网络绑定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没有什么关系,这就是文章开头提出的两个难点,第一个已经实现了,第二个在下一篇文章介绍。