Android實現流量統計和網速監控懸浮窗

LeBron_Six發表於2016-01-28

很多安全衛士類軟體都實現了網速監測功能,也算是一個比較實用的功能。Android下,TrafficStats類實現了對流量的統計。

static long getMobileRxBytes()//獲取通過Mobile連線收到的位元組總數,但不包含WiFi 
static long getMobileRxPackets()//獲取Mobile連線收到的資料包總數 
static long getMobileTxBytes()//Mobile傳送的總位元組數 
static long getMobileTxPackets()//Mobile傳送的總資料包數 
static long getTotalRxBytes()//獲取總的接受位元組數,包含Mobile和WiFi等 
static long getTotalRxPackets()//總的接受資料包數,包含Mobile和WiFi等 
static long getTotalTxBytes()//總的傳送位元組數,包含Mobile和WiFi等 
static long getTotalTxPackets()//傳送的總資料包數,包含Mobile和WiFi等 
static long getUidRxBytes(int uid)//獲取某個網路UID的接受位元組數 
static long getUidTxBytes(intuid) //獲取某個網路UID的傳送位元組數

這些就是TrafficStats提供的一些介面。那麼,我們首先要建立一個桌面懸浮窗,用於顯示網速。然後再Service裡面,顯示懸浮窗。

import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.yyh.utils.WidgetUtils;

public class ManagerService extends Service {
	private static final String TAG = "ManagerService";
	public LinearLayout mFloatLayout;
	public WindowManager.LayoutParams wmParams;
	public WindowManager mWindowManager;
	public TextView mFloatView;
	private ServiceBinder binder = new ServiceBinder();

	@Override
	public void onCreate() {
		super.onCreate();
		createFloatView();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		return START_STICKY_COMPATIBILITY;
	}

	@Override
	public IBinder onBind(Intent intent) {
		return binder;
	}

	@Override
	public void onTrimMemory(int level) {
		Log.i("test", " onTrimMemory...");

	}
        /** 建立懸浮窗 */
	private void createFloatView() {
		wmParams = new WindowManager.LayoutParams();
		mWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE);
		wmParams.type = LayoutParams.TYPE_SYSTEM_ALERT;// 設定window
														// type為TYPE_SYSTEM_ALERT
		wmParams.format = PixelFormat.RGBA_8888;// 設定圖片格式,效果為背景透明
		wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;// 設定浮動視窗不可聚焦(實現操作除浮動視窗外的其他可見視窗的操作)
		wmParams.gravity = Gravity.LEFT | Gravity.TOP;// 預設位置:左上角
		wmParams.width = WidgetUtils.dpToPx(getApplicationContext(), 65);
		wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
		wmParams.x = (WidgetUtils.getScreenWidth(getApplicationContext()) - wmParams.width) / 2;// 設定x、y初始值,相對於gravity
		wmParams.y = 10;
		// 獲取浮動視窗檢視所在佈局
		LayoutInflater inflater = LayoutInflater.from(getApplication());
		mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);
		mWindowManager.addView(mFloatLayout, wmParams);// 新增mFloatLayout
		mFloatView = (TextView) mFloatLayout.findViewById(R.id.speed);
		mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
		// 設定監聽浮動視窗的觸控移動
		mFloatView.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				// getRawX是觸控位置相對於螢幕的座標,getX是相對於按鈕的座標
				wmParams.x = (int) event.getRawX() - mFloatView.getMeasuredWidth() / 2;
				Log.i(TAG, "RawX" + event.getRawX());
				Log.i(TAG, "X" + event.getX());
				wmParams.y = (int) event.getRawY() - mFloatView.getMeasuredHeight() / 2 - 25;// 減25為狀態列的高度
				Log.i(TAG, "RawY" + event.getRawY());
				Log.i(TAG, "Y" + event.getY());
				mWindowManager.updateViewLayout(mFloatLayout, wmParams);// 重新整理
				return false; // 此處必須返回false,否則OnClickListener獲取不到監聽
			}
		});
		mFloatView.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
                            // do something... 跳轉到應用
			}
		});
	}

	public void setSpeed(String str) {
		mFloatView.setText(str.toString());
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		if (mFloatLayout != null && mWindowManager != null) {
			mWindowManager.removeView(mFloatLayout);// 移除懸浮視窗
		}
		startService(new Intent(this, ManagerService.class));
	}

	class ServiceBinder extends Binder {
		public ManagerService getService() {
			return ManagerService.this;
		}
	}
}
然後構造一個網速監測的工具類,用於獲取流量變化資訊。


import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigDecimal;
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.TrafficStats;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * 應用的流量資訊
 * @author yuyuhang
 */
public class TrafficInfo {

	private static final int UNSUPPORTED = -1;
	private static final String LOG_TAG = "test";

	private static TrafficInfo instance;
	
	static int uid;
	private long preRxBytes = 0;
	private Timer mTimer = null;
	private Context mContext;
	private Handler mHandler;
	
	/** 更新頻率(每幾秒更新一次,至少1秒) */
	private final int UPDATE_FREQUENCY = 1;
	private int times = 1;

	public TrafficInfo(Context mContext, Handler mHandler, int uid) {
		this.mContext = mContext;
		this.mHandler = mHandler;
		this.uid = uid;
	}

	public TrafficInfo(Context mContext, Handler mHandler) {
		this.mContext = mContext;
		this.mHandler = mHandler;
	}
	
	public static TrafficInfo getInstant(Context mContext, Handler mHandler) {
		if (instance == null) {
			instance = new TrafficInfo(mContext, mHandler);
		}
		return instance;
	}

	/**
	 * 獲取總流量
	 */
	public long getTrafficInfo() {
		long rcvTraffic = UNSUPPORTED; // 下載流量
		long sndTraffic = UNSUPPORTED; // 上傳流量
		rcvTraffic = getRcvTraffic();
		sndTraffic = getSndTraffic();
		if (rcvTraffic == UNSUPPORTED || sndTraffic == UNSUPPORTED)
			return UNSUPPORTED;
		else
			return rcvTraffic + sndTraffic;
	}

	/**
	 * 獲取下載流量 某個應用的網路流量資料儲存在系統的/proc/uid_stat/$UID/tcp_rcv | tcp_snd檔案中
	 */
	public long getRcvTraffic() {
		long rcvTraffic = UNSUPPORTED; // 下載流量
		rcvTraffic = TrafficStats.getUidRxBytes(uid);
		if (rcvTraffic == UNSUPPORTED) { // 不支援的查詢
			return UNSUPPORTED;
		}
		Log.i("test", rcvTraffic + "--1");
		RandomAccessFile rafRcv = null, rafSnd = null; // 用於訪問資料記錄檔案
		String rcvPath = "/proc/uid_stat/" + uid + "/tcp_rcv";
		try {
			rafRcv = new RandomAccessFile(rcvPath, "r");
			rcvTraffic = Long.parseLong(rafRcv.readLine()); // 讀取流量統計
		} catch (FileNotFoundException e) {
			Log.e(LOG_TAG, "FileNotFoundException: " + e.getMessage());
			rcvTraffic = UNSUPPORTED;
		} catch (IOException e) {
			Log.e(LOG_TAG, "IOException: " + e.getMessage());
			e.printStackTrace();
		} finally {
			try {
				if (rafRcv != null)
					rafRcv.close();
				if (rafSnd != null)
					rafSnd.close();
			} catch (IOException e) {
				Log.w(LOG_TAG, "Close RandomAccessFile exception: " + e.getMessage());
			}
		}
		Log.i("test", rcvTraffic + "--2");
		return rcvTraffic;
	}

	/**
	 * 獲取上傳流量
	 */
	public long getSndTraffic() {
		long sndTraffic = UNSUPPORTED; // 上傳流量
		sndTraffic = TrafficStats.getUidTxBytes(uid);
		if (sndTraffic == UNSUPPORTED) { // 不支援的查詢
			return UNSUPPORTED;
		}
		RandomAccessFile rafRcv = null, rafSnd = null; // 用於訪問資料記錄檔案
		String sndPath = "/proc/uid_stat/" + uid + "/tcp_snd";
		try {
			rafSnd = new RandomAccessFile(sndPath, "r");
			sndTraffic = Long.parseLong(rafSnd.readLine());
		} catch (FileNotFoundException e) {
			Log.e(LOG_TAG, "FileNotFoundException: " + e.getMessage());
			sndTraffic = UNSUPPORTED;
		} catch (IOException e) {
			Log.e(LOG_TAG, "IOException: " + e.getMessage());
			e.printStackTrace();
		} finally {
			try {
				if (rafRcv != null)
					rafRcv.close();
				if (rafSnd != null)
					rafSnd.close();
			} catch (IOException e) {
				Log.w(LOG_TAG, "Close RandomAccessFile exception: " + e.getMessage());
			}
		}
		return sndTraffic;
	}

	/**
	 * 獲取當前下載流量總和
	 */
	public static long getNetworkRxBytes() {
		return TrafficStats.getTotalRxBytes();
	}

	/**
	 * 獲取當前上傳流量總和
	 */
	public static long getNetworkTxBytes() {
		return TrafficStats.getTotalTxBytes();
	}

	/**
	 * 獲取當前網速,小數點保留一位
	 */
	public double getNetSpeed() {
		long curRxBytes = getNetworkRxBytes();
		if (preRxBytes == 0)
			preRxBytes = curRxBytes;
		long bytes = curRxBytes - preRxBytes;
		preRxBytes = curRxBytes;
		//int kb = (int) Math.floor(bytes / 1024 + 0.5);
		double kb = (double)bytes / (double)1024;
		BigDecimal bd = new BigDecimal(kb);
		return bd.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
	}

	/**
	 * 開啟流量監控
	 */
	public void startCalculateNetSpeed() {
		preRxBytes = getNetworkRxBytes();
		if (mTimer != null) {
			mTimer.cancel();
			mTimer = null;
		}
		if (mTimer == null) {
			mTimer = new Timer();
			mTimer.schedule(new TimerTask() {
				@Override
				public void run() {
					if(times == UPDATE_FREQUENCY){
						Message msg = new Message();
						msg.what = 1;
						//msg.arg1 = getNetSpeed();
						msg.obj = getNetSpeed();
						mHandler.sendMessage(msg);
						times = 1;
					} else {
						times++;
					}
				}
			}, 1000, 1000); // 每秒更新一次
		}
	}

	public void stopCalculateNetSpeed() {
		if (mTimer != null) {
			mTimer.cancel();
			mTimer = null;
		}
	}

	/**
	 * 獲取當前應用uid
	 */
	public int getUid() {
		try {
			PackageManager pm = mContext.getPackageManager();
			ApplicationInfo ai = pm.getApplicationInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
			return ai.uid;
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}
		return -1;
	}
}

然後再Activity裡面,就需要去啟動Service,以顯示懸浮窗,然後TrafficInfo通過Handler,把當前網速發給Activity,Activity呼叫Service的方法了來更新懸浮窗的View。

import java.util.List;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.TextView;
import com.yyh.utils.TrafficInfo;

public class MainActivity extends ActionBarActivity {

	private ActivityManager mActivityManager;
	private TextView mTextView;
	Handler mHandler;
	TrafficInfo speed;
	ManagerService service;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mActivityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
		mTextView = (TextView) findViewById(R.id.tv);
		try {
			mHandler = new Handler() {
				@Override
				public void handleMessage(Message msg) {
					if (msg.what == 1) {
						mTextView.setText(msg.obj + "kb/s");
						if(service != null)
							service.setSpeed(msg.obj+"kb/s"); // 設定網速
					}
					super.handleMessage(msg);
				}

			};
			speed = new TrafficInfo(this,mHandler,TrafficInfo.getUid());
			speed.startCalculateNetSpeed(); // 開啟網速監測
		} catch (Exception e) {
			e.printStackTrace();
		}
		Log.i("test","總流量="+speed.getTrafficInfo());
		Intent intent = new Intent(MainActivity.this, ManagerService.class);
		bindService(intent, conn, Context.BIND_AUTO_CREATE);		
	}
	
	private ServiceConnection conn = new ServiceConnection() {
            public void onServiceConnected(ComponentName name, IBinder binder) {
        	service = ((ManagerService.ServiceBinder) binder).getService();
            }
            public void onServiceDisconnected(ComponentName name) {
        	service = null;
            }
        };
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		speed.stopCalculateNetSpeed();
		unbindService(conn); 
	}
}

這樣,就能夠顯示懸浮窗。如果還需要後臺執行,那Service就需要常駐不被殺死,這部分請參考我另一篇博文:Android 通過JNI實現守護程式,保證Service服務不被殺死

附上Demo原始碼:Android 流量與網速監測(懸浮窗) 原始碼


相關文章