2016轉(zhuǎn)眼就要過去了同蜻,剛剛參加完學(xué)院舉辦的元旦晚會疲扎,看了看系里的大牛的各種事跡昵时,內(nèi)心感慨萬分〗飞ィ回來繼續(xù)安心做我的小碼農(nóng)壹甥,順便更一下將近一個月沒有更新的博客。
這次帶來的是一個懸浮窗網(wǎng)速顯示計瓜挽,先看下效果:
這里主要是在桌面上顯示一個懸浮窗盹廷,利用了WindowManager以及Service,接下來看看如何實現(xiàn)這樣一個效果:
首先APP必須獲得在桌面上顯示懸浮窗的機(jī)會,很多第三方ROM都限制了這一權(quán)限久橙,我們首先就是要申請改權(quán)限俄占,代碼如下:
public boolean checkDrawOverlayPermission() {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE);
return false;
}
return true;
}
該函數(shù)首先檢查APP是否有顯示懸浮窗的權(quán)限,如果沒有淆衷,就跳轉(zhuǎn)到該APP設(shè)置懸浮窗權(quán)限的界面缸榄,如下圖所示:
然后先編寫我們的懸浮窗,布局很簡單祝拯,就是兩個TextView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#88000000">
<TextView
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:id="@+id/speed_up"
android:text="upload speed"
android:gravity="left"
android:textSize="10dp"
android:layout_centerHorizontal="true"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:id="@+id/speed_down"
android:layout_below="@id/speed_up"
android:text="download speed"
android:gravity="left"
android:textSize="10dp"
android:layout_centerHorizontal="true"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
SpeedView:
public class SpeedView extends FrameLayout {
private Context mContext;
public TextView downText;
public TextView upText;
private WindowManager windowManager;
private int statusBarHeight;
private float preX,preY,x,y;
public SpeedView(Context context) {
super(context);
mContext=context;
init();
}
private void init() {
statusBarHeight=WindowUtil.statusBarHeight;
windowManager= (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
//a view inflate itself, that's funny
inflate(mContext,R.layout.speed_layout,this);
downText= (TextView) findViewById(R.id.speed_down);
upText= (TextView) findViewById(R.id.speed_up);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
preX=event.getRawX();preY=event.getRawY()-statusBarHeight;
return true;
case MotionEvent.ACTION_MOVE:
x=event.getRawX();y=event.getRawY()-statusBarHeight;
WindowManager.LayoutParams params= (WindowManager.LayoutParams) getLayoutParams();
params.x+=x-preX;
params.y+=y-preY;
windowManager.updateViewLayout(this,params);
preX=x;preY=y;
return true;
default:
break;
}
return super.onTouchEvent(event);
}
在SpeedView里我們重寫了onTouchEvent事件甚带,這樣就能響應(yīng)我們的拖拽事件了她肯,注意這里更新SpeedView的位置是通過改變WindowManager.LayoutParam的x和y來實現(xiàn)的,調(diào)用windowManager.updateViewLayout(this,params)
來更新位置鹰贵。
因為我們的網(wǎng)速顯示懸浮窗要脫離于Activity的生命周期而獨立存在晴氨,因此需要通過Service來實現(xiàn):
public class SpeedCalculationService extends Service {
private WindowUtil windowUtil;
private boolean changed=false;
@Override
public void onCreate() {
super.onCreate();
WindowUtil.initX= (int) SharedPreferencesUtils.getFromSpfs(this,INIT_X,0);
WindowUtil.initY= (int) SharedPreferencesUtils.getFromSpfs(this,INIT_Y,0);
windowUtil=new WindowUtil(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
changed=intent.getBooleanExtra(MainActivity.CHANGED,false);
if(changed){
windowUtil.onSettingChanged();
}else{
if(!windowUtil.isShowing()){
windowUtil.showSpeedView();
}
SharedPreferencesUtils.putToSpfs(this,MainActivity.IS_SHOWN,true);
}
//return super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
WindowManager.LayoutParams params= (WindowManager.LayoutParams) windowUtil.speedView.getLayoutParams();
SharedPreferencesUtils.putToSpfs(this, INIT_X,params.x);
SharedPreferencesUtils.putToSpfs(this, INIT_Y,params.y);
if(windowUtil.isShowing()){
windowUtil.closeSpeedView();
SharedPreferencesUtils.putToSpfs(this,MainActivity.IS_SHOWN,false);
}
Log.d("yjw","service destroy");
}
}
這里的WindowUtil其實就是一個工具類,幫助我們控制懸浮窗SpeedView的顯示與隱藏:
public class WindowUtil {
public static int statusBarHeight=0;
//記錄懸浮窗的位置
public static int initX,initY;
private WindowManager windowManager;
public SpeedView speedView;
private WindowManager.LayoutParams params;
private Context context;
public boolean isShowing() {
return isShowing;
}
private boolean isShowing=false;
public static final int INTERVAL=2000;
private long preRxBytes,preSeBytes;
private long rxBytes,seBytes;
private Handler handler=new Handler(){
@Override
public void dispatchMessage(Message msg) {
super.dispatchMessage(msg);
calculateNetSpeed();
sendEmptyMessageDelayed(0,INTERVAL);
}
};
public void onSettingChanged(){
String setting= (String) SharedPreferencesUtils.getFromSpfs(context,MainActivity.SETTING,MainActivity.BOTH);
if(setting.equals(MainActivity.BOTH)){
speedView.upText.setVisibility(View.VISIBLE);
speedView.downText.setVisibility(View.VISIBLE);
}else if(setting.equals(MainActivity.UP)){
speedView.upText.setVisibility(View.VISIBLE);
speedView.downText.setVisibility(View.GONE);
}else{
speedView.upText.setVisibility(View.GONE);
speedView.downText.setVisibility(View.VISIBLE);
}
}
private void calculateNetSpeed() {
rxBytes=TrafficStats.getTotalRxBytes();
seBytes=TrafficStats.getTotalTxBytes()-rxBytes;
double downloadSpeed=(rxBytes-preRxBytes)/2;
double uploadSpeed=(seBytes-preSeBytes)/2;
preRxBytes=rxBytes;
preSeBytes=seBytes;
//根據(jù)范圍決定顯示單位
String upSpeed=null;
String downSpeed=null;
NumberFormat df= java.text.NumberFormat.getNumberInstance();
df.setMaximumFractionDigits(2);
if(downloadSpeed>1024*1024){
downloadSpeed/=(1024*1024);
downSpeed=df.format(downloadSpeed)+"MB/s";
}else if(downloadSpeed>1024){
downloadSpeed/=(1024);
downSpeed=df.format(downloadSpeed)+"B/s";
}else{
downSpeed=df.format(downloadSpeed)+"B/s";
}
if(uploadSpeed>1024*1024){
uploadSpeed/=(1024*1024);
upSpeed=df.format(uploadSpeed)+"MB/s";
}else if(uploadSpeed>1024){
uploadSpeed/=(1024);
upSpeed=df.format(uploadSpeed)+"kB/s";
}else{
upSpeed=df.format(uploadSpeed)+"B/s";
}
updateSpeed("↓ "+downSpeed,"↑ "+upSpeed);
}
public WindowUtil(Context context) {
this.context = context;
windowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
speedView=new SpeedView(context);
params=new WindowManager.LayoutParams();
params=new WindowManager.LayoutParams();
params.x=initX;
params.y=initY;
params.width=params.height=WindowManager.LayoutParams.WRAP_CONTENT;
params.type=WindowManager.LayoutParams.TYPE_PHONE;
params.gravity= Gravity.LEFT|Gravity.TOP;
params.format= PixelFormat.RGBA_8888;
params.flags=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
//設(shè)置懸浮窗可以拖拽至狀態(tài)欄的位置
// | WindowManager.LayoutParams.FLAG_FULLSCREEN| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
}
public void showSpeedView(){
windowManager.addView(speedView,params);
isShowing=true;
preRxBytes= TrafficStats.getTotalRxBytes();
preSeBytes=TrafficStats.getTotalTxBytes()-preRxBytes;
handler.sendEmptyMessage(0);
}
public void closeSpeedView(){
windowManager.removeView(speedView);
isShowing=false;
}
public void updateSpeed(String downSpeed,String upSpeed){
speedView.upText.setText(upSpeed);
speedView.downText.setText(downSpeed);
}
}
WindowUtil類中也包含了一個很重要的方法碉输,那就是計算網(wǎng)速籽前。這里計算網(wǎng)速的方法很簡單,Android提供了一個類TrafficStats
,這個類里面為我們提供了好多接口敷钾,我們用到了其中的兩個:
1.public static long getTotalTxBytes ()
Return number of bytes transmitted since device boot. Counts packets across all network interfaces, and always increases monotonically since device boot. Statistics are measured at the network layer, so they include both TCP and UDP usage.
2.public static long getTotalRxPackets ()
Return number of packets received since device boot. Counts packets across all network interfaces, and always increases monotonically since device boot. Statistics are measured at the network layer, so they include both TCP and UDP usage.
可以看出枝哄,getTotalTxBytes()方法返回系統(tǒng)自開機(jī)到現(xiàn)在為止所一共傳輸?shù)臄?shù)據(jù)的字節(jié)數(shù),包括上傳的數(shù)據(jù)和下載的數(shù)據(jù)阻荒;而getTotalRxPackets()方法返回的是系統(tǒng)自開機(jī)到現(xiàn)在為止所一共接收到也就是下載的數(shù)據(jù)的字節(jié)數(shù)挠锥,用getTotalTxBytes()-getTotalRxPackets()自然就是系統(tǒng)開機(jī)到現(xiàn)在為止所上傳的數(shù)據(jù)的字節(jié)數(shù)。
這樣每隔一定時間侨赡,我們計算一下系統(tǒng)自開機(jī)到目前所接受的數(shù)據(jù)包的字節(jié)數(shù)和所發(fā)送的數(shù)據(jù)的字節(jié)數(shù)的變化量蓖租,用變化量除以時間,就是這段時間的平均網(wǎng)速了辆毡。
為了每個一段時間計算一下網(wǎng)速菜秦,我們利用了一個Handler來實現(xiàn)這個定時任務(wù)
private Handler handler=new Handler(){
@Override
public void dispatchMessage(Message msg) {
super.dispatchMessage(msg);
calculateNetSpeed();
sendEmptyMessageDelayed(0,INTERVAL);
}
};
這里要注意將SpeedView添加到屏幕上甜害,也就是添加到WindowManager里的時候舶掖,這個WindowManager.LayoutParams十分重要,其參數(shù)都是有用的尔店,這里就不細(xì)講了,詳細(xì)介紹請移步官方文檔.
最后要將我們的懸浮窗設(shè)置為開機(jī)自啟動的眨攘,利用一個BroadcastReceiver就可以了:
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("yjw","receiver receive boot broadcast");
boolean isShown= (boolean) SharedPreferencesUtils.getFromSpfs(context,MainActivity.IS_SHOWN,false);
if(isShown){
context.startService(new Intent(context,SpeedCalculationService.class));
}
}
}
在Manifest里這樣注冊我們的BroadcastReceiver:
<receiver android:name="me.mrrobot97.netspeed.MyBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
這樣當(dāng)系統(tǒng)啟動完成,就會開啟我們的Service嚣州。注意這里<intent-filter>中的<category>不可省略鲫售,親測省略后BroadcastReceiver無法接收到系統(tǒng)廣播。
最后還有一點该肴,在Manifest的MainActivity條目中加一個屬性:android:excludeFromRecents="true"
這樣我們的ManiActivity就不會顯示在最近任務(wù)列表情竹,防止用戶清空任務(wù)列表時將我們的Sercvice進(jìn)程終結(jié)了。
完整的項目地址Github