facebook的network-connection-class(测量移动端网络质量)源码详解

最近逛github,突然发现facebook居然写了个检测网络质量的框架源码地址,好奇心驱使那就研究一下吧。分析源码之前,先看一看官方给我们提供的例子效果:

facebook的network-connection-class(测量移动端网络质量)源码详解_第1张图片


这个是faceBook官方为这个小框架提供的demo,点击Test按钮即可测出当前手机网络速度的质量,但是这里出现了一串英文EXCELLENT,什么鬼这表示的速度是什么,查询源码找到包含参数的这个枚举类

public enum ConnectionQuality {
  /**
   * Bandwidth under 150 kbps.
   */
  POOR,
  /**
   * Bandwidth between 150 and 550 kbps.
   */
  MODERATE,
  /**
   * Bandwidth between 550 and 2000 kbps.
   */
  GOOD,
  /**
   * EXCELLENT - Bandwidth over 2000 kbps.
   */
  EXCELLENT,
  /**
   * Placeholder for unknown bandwidth. This is the initial value and will stay at this value
   * if a bandwidth cannot be accurately found.
   */
  UNKNOWN
}

英文已经说明的很清楚了,测试的当前网络速度2000 kbps以上,说明手机当前网络质量不错吆!好,看完例子,接下来进入重点

mConnectionClassManager = ConnectionClassManager.getInstance();
    mDeviceBandwidthSampler = DeviceBandwidthSampler.getInstance();
   .....
    mListener = new ConnectionChangedListener();


 @Override
  protected void onPause() {
    super.onPause();
    mConnectionClassManager.remove(mListener);
  }

  @Override
  protected void onResume() {
    super.onResume();
    mConnectionClassManager.register(mListener);
  }

例子当中与框架交互的首先映入眼帘的就是这几句代码,可以看到,小框架中声明了两个单例ConnectionClassManager类和DeviceBandwidthSampler类,然后通过ConnectionClassManager注册了一个监听回调方法,用于显示网络变化,既然是一直在监听网络速度变化的,那么八成会有死循环一直运行监测网速。ok,先看看DeviceBandwidthSampler类,验证一下推论。

 private DeviceBandwidthSampler(
      ConnectionClassManager connectionClassManager) {
    mConnectionClassManager = connectionClassManager;
    mSamplingCounter = new AtomicInteger();
    mThread = new HandlerThread("ParseThread");
    mThread.start();
    mHandler = new SamplingHandler(mThread.getLooper());
  }
这个类的构造方法声明了一个HandlerThread,看到 HandlerThread,已经能看出来,它是想在在线程当中执行消息队列,start()方法创建一个新的消息队列管理,类Loop,然后进行轮询查看是否有消息,有消息就处理,接下来创建一个绑定这个 HandlerThread的Loop的Handler,用来发消息。按照猜测接下来就是利用Handler来循环传送消息了,在点击按钮下载图片的时候调用了 mDeviceBandwidthSampler.startSampling()方法发送了消息。

 /**
   * 开始检测网络速度
   */
  public void startSampling() {
    if (mSamplingCounter.getAndIncrement() == 0) {
    	//看这里
      mHandler.startSamplingThread();
      mLastTimeReading = SystemClock.elapsedRealtime();
    }
  }
      public void startSamplingThread() {
          sendEmptyMessage(SamplingHandler.MSG_START);
      }
看这个方法链的调用,最终通过发消息的方式实现循环


 public void handleMessage(Message msg) {
          switch (msg.what) {
              case MSG_START:
                  addSample();
                  sendEmptyMessageDelayed(MSG_START, SAMPLE_TIME);

消息处理中每隔一段时间重新发送相通消息,那么计算处理网络速度的也只有 addSample方法了


 protected void addSample() {
	 //得到手机自启动以来的总共消耗的流量
    long newBytes = TrafficStats.getTotalRxBytes();
    long byteDiff = newBytes - sPreviousBytes;
    //有变化的时候才去计算
    if (sPreviousBytes >= 0) {
      synchronized (this) {
    	  //得到当前的时间
        long curTimeReading = SystemClock.elapsedRealtime();
        //传入流量变化的差值和时间差值
        mConnectionClassManager.addBandwidth(byteDiff, curTimeReading - mLastTimeReading);
        mLastTimeReading = curTimeReading;
      }
    }
    sPreviousBytes = newBytes;
  }

这个方法首先获得了手机自启动以来的流量消耗的总量,然后用当前量减去上一次的量,也就是说网络速度是通过单位时间内流量的差值进行计算的,接下来进入 mConnectionClassManager.addBandwidth进行计算

public synchronized void addBandwidth(long bytes, long timeInMs) {

		// Ignore garbage values.
		// 变化量小于10计算终止重新记录值
		if (timeInMs == 0
				|| (bytes) * 1.0 / (timeInMs) * BYTES_TO_BITS < BANDWIDTH_LOWER_BOUND) {
			return;
		}

		double bandwidth = (bytes) * 1.0 / (timeInMs) * BYTES_TO_BITS;
		mDownloadBandwidth.addMeasurement(bandwidth);
		// 如果它为true连续mSampleCounter+1
		if (mInitiateStateChange) {
			mSampleCounter += 1;
			// 判断当前的值和期望的值是否处在同一个范围内,不处在同一个范围内,连续5次计算的值和波动值在同一个范围内的话,减少网络波动造成的误差
			if (getCurrentBandwidthQuality() != mNextBandwidthConnectionQuality
					.get()) {
				// 计数重新开始,mInitiateStateChange
				mInitiateStateChange = false;
				mSampleCounter = 1;
			}
			/**
			 * 网络稳定到5次以上才通知计算结果,波动比较大的时候不通知,优化更准确
			 */
			if (mSampleCounter >= DEFAULT_SAMPLES_TO_QUALITY_CHANGE
					&& significantlyOutsideCurrentBand()) {
				mInitiateStateChange = false;
				mSampleCounter = 1;
				// 确定改变的时候设置通知
				mCurrentBandwidthConnectionQuality
						.set(mNextBandwidthConnectionQuality.get());
				notifyListeners();
			}
			return;
		}
		// 如果上一个速率和现在的计算速率不一样,那么mInitiateStateChange=true,mNextBandwidthConnectionQuality储存当前的速录
		if (mCurrentBandwidthConnectionQuality.get() != getCurrentBandwidthQuality()) {
			mInitiateStateChange = true;

			mNextBandwidthConnectionQuality = new AtomicReference(
					getCurrentBandwidthQuality());
		}
	}

计算的核心都在这个方法里面,首先判断如果前后消耗流量的变化小于10kbs那么,重新记录值计算终止,满足条件通过mDownloadBandwidth.addMeasurement方法计算出变化的平均值,也就是通过指数移动平均值来重新估算这个差值,由于移动网络的不稳定性,通过计算平均值计算会更准确,假设当前网络已经有了上一次的速度,但是由于某种原因,速度发生变化,比如信号不好,那么此时为了排除误差,采取连续计算五次方式,也就是说如果当前网络速度变化值满足五次在同一个范围内并且和上一次计算的速度不在同一范围内的话,那么通知客户端速度改变,当前速度值刷新,也就是网络速度值必须保持5次计算都在同一个范围内,才认为当前网络质量的速度为准确的,也就是说假设当前速度为100kbs,信号突然不好掉到30kbs,但是有可能信号又突然恢复了,那么当前速度还是100kbs,没必要通知客户端网络速度发生了改变。这种方式可以是测量值更接近真实情况的值,当然不肯能100%准确。


最后看一下指数移动平均值是怎么计算的

public void addMeasurement(double measurement) {
		// 0.95
		double keepConstant = 1 - mDecayConstant;
		// 累加次数大于20次
		if (mCount > mCutover) {
			// 自然对数Math.log(mValue),Math.exp() 函数返回 ex
			mValue = Math.exp(keepConstant * Math.log(mValue) + mDecayConstant
					* Math.log(measurement));
		}
		// 0 0) {
			// 如果mCount是4的话
			double retained = keepConstant * mCount / (mCount + 1.0);
			double newcomer = 1.0 - retained;
			mValue = Math.exp(retained * Math.log(mValue) + newcomer
					* Math.log(measurement));
		}
		// 第一次mCount是等于0的
		else {
			mValue = measurement;
		}
		mCount++;
	}
指数平均值通过前后变化的两个数进行权重平均值计算,至于这样计算为什么准确,笔者暂时有点懵逼,但是这个算法是统计学当中的,不管怎么说阅读此代码让笔者了解到,想成为高手中的战斗机算法必须牛,那么接下来的目标就是拾起曾经遗忘的数学



你可能感兴趣的:(facebook的network-connection-class(测量移动端网络质量)源码详解)