一個簡單的網(wǎng)速顯示懸浮窗

2016轉(zhuǎn)眼就要過去了同蜻,剛剛參加完學(xué)院舉辦的元旦晚會疲扎,看了看系里的大牛的各種事跡昵时,內(nèi)心感慨萬分〗飞ィ回來繼續(xù)安心做我的小碼農(nóng)壹甥,順便更一下將近一個月沒有更新的博客。

這次帶來的是一個懸浮窗網(wǎng)速顯示計瓜挽,先看下效果:

demo

這里主要是在桌面上顯示一個懸浮窗盹廷,利用了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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匀哄,一起剝皮案震驚了整個濱河市秦效,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涎嚼,老刑警劉巖阱州,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異法梯,居然都是意外死亡苔货,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夜惭,“玉大人姻灶,你說我怎么就攤上這事≌┘耄” “怎么了木蹬?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長若皱。 經(jīng)常有香客問我镊叁,道長,這世上最難降的妖魔是什么走触? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任晦譬,我火速辦了婚禮,結(jié)果婚禮上互广,老公的妹妹穿的比我還像新娘敛腌。我一直安慰自己,他們只是感情好惫皱,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布像樊。 她就那樣靜靜地躺著,像睡著了一般旅敷。 火紅的嫁衣襯著肌膚如雪生棍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天媳谁,我揣著相機(jī)與錄音涂滴,去河邊找鬼。 笑死晴音,一個胖子當(dāng)著我的面吹牛柔纵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锤躁,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搁料,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了系羞?” 一聲冷哼從身側(cè)響起郭计,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎觉啊,沒想到半個月后拣宏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡杠人,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年勋乾,在試婚紗的時候發(fā)現(xiàn)自己被綠了宋下。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辑莫,死狀恐怖学歧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情各吨,我是刑警寧澤枝笨,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站揭蜒,受9級特大地震影響横浑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屉更,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一徙融、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瑰谜,春花似錦欺冀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渤早,卻和暖如春职车,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蛛芥。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工提鸟, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留军援,地道東北人仅淑。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像胸哥,于是被迫代替她去往敵國和親涯竟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容