作者:GentlemanTsao
/GentelmanTsao
每篇一格言
Stay hungry,stay foolish!
——Steve Jobs
文章目录
前言流量检测的时序:流量监听的Controller(亦是主题):dcTracker流量监听的model:android_net_TrafficStats观察者:PhoneStateListener附录1.数据流量相关文件附录2. Log分析与工具分析需抓到下面的log相关分析工具:附录3.重要代码Step 1. 统计数据包(native层)step 2 .检测是否有上下行数据的逻辑step 3. 通知监听者流量状态更新Step 4.通知RSSI状态更新前言
Android Telephony Framework跨越AP和BP,想要深入理解telephony,需要对modem架构(BP)、QCRIL/QMI框架、RIL框架以及Framework telephony框架(Java层)的源码有深入研究——不止于对源码的阅读和框架的理解,还需要大量的debug与trace经验。
不同于app|UI 开发的修改即可见,Telephony debug的复杂度很高,在大多数时候要求通过阅读源码结合log trace来复现、定位并解决问题。这对telephony开发者的技术宽度、逻辑严谨性、动手能力和快速反应力提出了多重挑战。
数据业务,作为telephony的重要部分——很可能是最重要的,是4G/5G时代Android的基础功能,值得Android开发者认真研究。
本篇介绍的是Android Framework中的数据流量统计流程,希望你会喜欢。
流量检测的时序:
你大概发现了,上面的时序图是基于MVC架构(Model-View-Controller). View部分并不在图中,因为这不是本篇关注的重点。MVC是常见的软件设计框架,它将业务逻辑、数据以及UI显示解耦,符合耦合性低,重用性和可维护性高的软件工程思想。MVC的改进版有MVP、MVVM等。
从设计模式的角度看,该时序属于观察者模式。如下图所示:
DcTracker扮演主题(被观察者)的角色,当有数据更新时通知PhoneStateListener. PhoneStateListener扮演观察者的角色,监听DcTracker状态的改变。
下面我们从任务流的角度,具体分析流量检测的流程。
为了阅读更清晰,涉及到源码的部分,只贴出关键源码,省略细节。
流量监听的Controller(亦是主题):dcTracker
dcTracker创建如下的Runnable检测流量变化:
private final Runnable mPollNetStat = new Runnable() {@Overridepublic void run() {updateDataActivity();。。。};
该Runnable默认情况下每隔1秒钟执行一次。
这里的updateDataActivity主要做两件事:
检测发送包(tx)和接收包(rx)的变化,分别调用
TrafficStats.getMobileTxPackets()TrafficStats.getMobileRxPackets()
通知phone有数据变化
关于TrafficStats与NetworkStatsService,它们提供java层数据流量接口,这里撇开不具体分析了。
流量监听的model:android_net_TrafficStats
下面是统计数据流量的方法parseIfaceStats。
该方法打开了设备文件“/proc/net/xt_qtaguid/iface_stat_fmt”,统计6种数据,分别是
rxBytes,
rxPackets,
txBytes,
txPackets,
tcpRxPackets,
tcpTxPackets
关键代码如下:
static int parseIfaceStats(const char* iface, struct Stats* stats) {FILE *fp = fopen(QTAGUID_IFACE_STATS, "r");。。。while (fgets(buffer, sizeof(buffer), fp) != NULL) {int matched = sscanf(buffer, "%31s %" SCNu64 " %" SCNu64 " %" SCNu64" %" SCNu64 " " "%*u %" SCNu64 " %*u %*u %*u %*u ""%*u %" SCNu64 " %*u %*u %*u %*u", cur_iface, &rxBytes,&rxPackets, &txBytes, &txPackets, &tcpRxPackets, &tcpTxPackets);if (matched >= 5) {if (matched == 7) {foundTcp = true;}if (!iface || !strcmp(iface, cur_iface)) {stats->rxBytes += rxBytes;stats->rxPackets += rxPackets;stats->txBytes += txBytes;stats->txPackets += txPackets;if (matched == 7) {stats->tcpRxPackets += tcpRxPackets;stats->tcpTxPackets += tcpTxPackets;}}}}。。。}
下面重点看PhoneStateListener。
观察者:PhoneStateListener
PhoneStateListener有一个onDataActivity方法,它根据流量状态做相应处理(例如,通知UI刷新)。
public void onDataActivity(int direction) {// default implementation empty}
该方法的输入参数direction有下面五种状态:
DATA_ACTIVITY_NONE //无流量
DATA_ACTIVITY_IN //下行
DATA_ACTIVITY_OUT //上行
DATA_ACTIVITY_INOUT //上行和下行
DATA_ACTIVITY_DORMANT // 休眠
该方法默认实现为空,也就是什么也不做。由继承PhoneStateListener的子类重写onDataActivity实现具体业务。
看看下面的例子:
MobilePhoneStateListener继承自PhoneStateListener,并重写onDataActivity,通知UI刷新RSSI。
public void onDataActivity(int direction) {if (DEBUG) {Log.d(mTag, "onDataActivity: direction=" + direction);}setActivity(direction);}
上面我们非常概括的梳理了android framework的流量检测流程。去粗取精后,是不是很easy呢?
你可能想了解更多详细的内容,我以附录列出如下:
附录1.数据流量相关文件
android_net_TrafficStats.cpp (amss\linux\android\frameworks\base\core\jni)
JNI层接口,获取IFace状态,统计数据包
TrafficStats.java (amss\linux\android\frameworks\base\core\java\android\net)
java层接口,读取数据包大小
DcTracker.java (amss\linux\android\frameworks\opt\telephony\src\java\com\android\internal\telephony\dataconnection)
负责数据流量跟踪,开了一个子线程定时更新数据包大小,并发送状态给phoneNotifier.
GsmCdmaPhone.java (amss\linux\android\frameworks\opt\telephony\src\java\com\android\internal\telephony)
向UI提供流量状态的接口
DefaultPhoneNotifier.java (amss\linux\android\frameworks\opt\telephony\src\java\com\android\internal\telephony
执行流量通知,将状态通过TelephonyRegistry发给监听者
TelephonyRegistry.java (amss\linux\android\frameworks\base\services\core\java\com\android\server)
向监听者发送流量更新提示
PhoneStateListener.java (amss\linux\android\frameworks\base\telephony\java\android\telephony)
MobileSignalController.java (amss\linux\android\frameworks\base\packages\systemui\src\com\android\systemui\statusbar
私有类MobilePhoneStateListener继承自PhoneStateListener,通知RSSI更新流量状态。
附录2. Log分析与工具
分析需抓到下面的log
modem log:
(高通平台)QXDM log PDCP; (MTK平台)ELT log
Android log:
radio log 及system log;
logcat -v time -b radio -b system > log.txt
TCP log:
TCP dump抓取方法:
adb root
adb shell tcpdump -i any -s 0 -w /data/tcpdump.pcap
adb pull /data/tcpdump.pcap
相关分析工具:
QXDM、QCAT、WIRESHARK
附录3.重要代码
Step 1. 统计数据包(native层)
见下面的parseIfaceStats方法实现。
这里分别累计了rxBytes,rxPackets,txBytes,txPackets,tcpRxPackets,tcpTxPackets。
根据上层的读取接口可以看出,只有tcpRxPackets和tcpTxPackets被看作有效的下行和上行数据。
static int parseIfaceStats(const char* iface, struct Stats* stats) {FILE *fp = fopen(QTAGUID_IFACE_STATS, "r");if (fp == NULL) {return -1;}char buffer[384];char cur_iface[32];bool foundTcp = false;uint64_t rxBytes, rxPackets, txBytes, txPackets, tcpRxPackets, tcpTxPackets;while (fgets(buffer, sizeof(buffer), fp) != NULL) {int matched = sscanf(buffer, "%31s %" SCNu64 " %" SCNu64 " %" SCNu64" %" SCNu64 " " "%*u %" SCNu64 " %*u %*u %*u %*u ""%*u %" SCNu64 " %*u %*u %*u %*u", cur_iface, &rxBytes,&rxPackets, &txBytes, &txPackets, &tcpRxPackets, &tcpTxPackets);if (matched >= 5) {if (matched == 7) {foundTcp = true;}if (!iface || !strcmp(iface, cur_iface)) {stats->rxBytes += rxBytes;stats->rxPackets += rxPackets;stats->txBytes += txBytes;stats->txPackets += txPackets;if (matched == 7) {stats->tcpRxPackets += tcpRxPackets;stats->tcpTxPackets += tcpTxPackets;}}}}if (!foundTcp) {stats->tcpRxPackets = UNKNOWN;stats->tcpTxPackets = UNKNOWN;}if (fclose(fp) != 0) {return -1;}return 0;}
step 2 .检测是否有上下行数据的逻辑
先看下这个枚举,定义了流量的几种状态
public enum Activity {NONE,DATAIN, //下行DATAOUT, //上行DATAINANDOUT, //同时上行和下行DORMANT}
updateDataActivity该方法从3.1中获取到数据包大小,并与上一次检测的结果比较,进而判断是否有流量。
private void updateDataActivity() {long sent, received;DctConstants.Activity newActivity;TxRxSum preTxRxSum = new TxRxSum(mTxPkts, mRxPkts);TxRxSum curTxRxSum = new TxRxSum();curTxRxSum.updateTxRxSum();mTxPkts = curTxRxSum.txPkts;mRxPkts = curTxRxSum.rxPkts;if (VDBG) {log("updateDataActivity: curTxRxSum=" + curTxRxSum + " preTxRxSum=" + preTxRxSum);}if (mNetStatPollEnabled && (preTxRxSum.txPkts > 0 || preTxRxSum.rxPkts > 0)) {sent = mTxPkts - preTxRxSum.txPkts;received = mRxPkts - preTxRxSum.rxPkts;if (VDBG)log("updateDataActivity: sent=" + sent + " received=" + received);if (sent > 0 && received > 0) {newActivity = DctConstants.Activity.DATAINANDOUT;} else if (sent > 0 && received == 0) {newActivity = DctConstants.Activity.DATAOUT;} else if (sent == 0 && received > 0) {newActivity = DctConstants.Activity.DATAIN;} else {newActivity = (mActivity == DctConstants.Activity.DORMANT) ?mActivity : DctConstants.Activity.NONE;}if (mActivity != newActivity && mIsScreenOn) {if (VDBG)log("updateDataActivity: newActivity=" + newActivity);mActivity = newActivity;mPhone.notifyDataActivity();}}}
step 3. 通知监听者流量状态更新
public void notifyDataActivityForSubscriber(int subId, int state) {if (!checkNotifyPermission("notifyDataActivity()" )) {return;}synchronized (mRecords) {int phoneId = SubscriptionManager.getPhoneId(subId);if (validatePhoneId(phoneId)) {mDataActivity[phoneId] = state;for (Record r : mRecords) {// Notify by correct subId.if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_DATA_ACTIVITY) &&idMatch(r.subId, subId, phoneId)) {try {r.callback.onDataActivity(state);} catch (RemoteException ex) {mRemoveList.add(r.binder);}}}}handleRemoveListLocked();}}
Step 4.通知RSSI状态更新
void setActivity(int activity) {mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT|| activity == TelephonyManager.DATA_ACTIVITY_IN;mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT|| activity == TelephonyManager.DATA_ACTIVITY_OUT;if (mConfig.readIconsFromXml) {mCurrentState.dataActivity = activity;}notifyListenersIfNecessary();}
相关章节:
一篇就够!全面&详细解析android APN
本文为原创,喜欢就点赞吧~
Android:全面详细的解析Android数据流量统计流程与分析方法(流量检测 流量监控 流量提示)相关类PhoneStateListener dcTracker TrafficStats
如果觉得《Android:全面详细的解析Android数据流量统计流程与分析方法(流量检测 流量监控 流》对你有帮助,请点赞、收藏,并留下你的观点哦!