Android:全面&详细的解析Android数据流量统计流程与分析方法(流量检测、流量监控、流量提示)相关类PhoneStateListener,dcTracker,TrafficStats

每篇一格言

Stay hungry,stay foolish!
					——Steve Jobs

流量检测的call flow:

在这里插入图片描述
下面我们从任务流的角度,具体分析流量检测的流程。
因而角色关系变成了下图
在这里插入图片描述
为了阅读更清晰,涉及到源码的部分,只贴出关键源码,省略细节。

流量检测发起者:dcTracker

dcTracker创建如下线程检测流量变化

private final Runnable mPollNetStat = new Runnable() {
        @Override
        public void run() {
            updateDataActivity();
。。。
    };

这里的updateDataActivity主要做两件事:
1.检测发送包(tx)和接收包(rx)的变化,分别调用
TrafficStats.getMobileTxPackets()
TrafficStats.getMobileRxPackets()
2.通知phone有数据包变化

TrafficStats与NetworkStatsService提供java层数据流量接口,担当传递任务的角色,略。

流量检测的执行者: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;
                }
            }
        }
    }
。。。
}

GsmCdmaPhone,DefaultPhoneNotifier,TelephonyRegistry,担当传递任务的角色,略。
下面重点看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,通知RSSI刷新。

        public void onDataActivity(int direction) {
            if (DEBUG) {
                Log.d(mTag, "onDataActivity: direction=" + direction);
            }
            setActivity(direction);
        }

上面我们已经从任务流的角度分析了android的流量检测流程。
下面从各个类的角色定位的角度作个总结,如下图所示:

在这里插入图片描述
你可能想了解更多详细的内容,我以附录列出如下:

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

本文为博主原创,可尽情点赞、留言与讨论。
发布了19 篇原创文章 · 获赞 8 · 访问量 2031

猜你喜欢

转载自blog.csdn.net/GentelmanTsao/article/details/103260348