Android實現流量統計和網速監控懸浮窗
阿新 • • 發佈:2019-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服務不被殺死