欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android数据流量的统计流程与分析方法

程序员文章站 2022-06-03 18:16:28
...

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

(高通平台)QXDM log PDCP; (MTK平台)ELT log
logcat radio log system log;
TCP dump

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. 统计数据包

见下面的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();
}

关系图
待续。

本文为博主原创,可尽情点赞、留言与讨论。