自定义水波球清理内存的悬浮窗小工具

来源:互联网 发布:淘宝产品视频 编辑:程序博客网 时间:2024/04/28 21:57

一、概述

现在一些手机管家都会有一个用来清理内存的悬浮窗小工具,感觉挺实用的,就自己做了一个。首先介绍一下这个工具的功能,除了可以清理内存,还有调节手机屏幕亮度、手电筒、无线网、移动数据、蓝牙、GPS开关的功能。先上图,感受一波:
   
清理手机内存
   
一些常用功能的开关

二、功能实现

1、悬浮窗

   
MainActivity只有两个按钮,控制悬浮窗的打开和关闭。这里我是用Service去控制的。下面我把FloatWindowService的代码贴出来:
public class FloatWindowService extends Service {    /**     * 用于在线程中创建或移除悬浮窗。     */    private Handler handler = new Handler();    private FloatManager floatManager;    private Runnable runnable;    @Override    public IBinder onBind(Intent intent) {        return null;    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        floatManager = new FloatManager(getApplicationContext());        //每隔1秒会更新一次悬浮窗状态        runnable = new Runnable() {            @Override            public void run() {                boolean isHome = isHome();                floatManager.trigger(isHome);                handler.postDelayed(this, 1000);            }        };        handler.post(runnable);        return super.onStartCommand(intent, flags, startId);    }    /**     * 判断当前界面是否是桌面     */    private boolean isHome() {        ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);        List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);        return getHomes().contains(rti.get(0).topActivity.getPackageName());    }    /**     * 获得属于桌面的应用的应用包名称     *     * @return 返回包含所有包名的字符串列表     */    private List<String> getHomes() {        List<String> names = new ArrayList<String>();        PackageManager packageManager = this.getPackageManager();        Intent intent = new Intent(Intent.ACTION_MAIN);        intent.addCategory(Intent.CATEGORY_HOME);        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,                PackageManager.MATCH_DEFAULT_ONLY);        for (ResolveInfo ri : resolveInfo) {            names.add(ri.activityInfo.packageName);        }        return names;    }    /**     * 服务销毁时清除任务     */    @Override    public void onDestroy() {        super.onDestroy();        handler.removeCallbacks(runnable);        floatManager.removeWindow(getApplicationContext());    }}
该悬浮窗只有在桌面才会出现,当打开其他应用的时候会隐藏。我这里用了Handler去每一秒执行一次判断悬浮窗的状态,式显示还是隐藏。注意:在Service的onDestory()的方法中一定要执行handler.removeCallbacks(),否则不能关闭悬浮窗。
下面说一下悬浮窗的创建,定义一个类继承LinearLayout,重写它的onTouchEvent()方法,实现拖动和点击的效果。核心代码如下:
    @Override    public boolean onTouchEvent(MotionEvent event) {        try {            switch (event.getAction()) {                case MotionEvent.ACTION_DOWN:                    // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度                    xInView = event.getX();                    yInView = event.getY();                    xDownInScreen = event.getRawX();                    yDownInScreen = event.getRawY() - getStatusBarHeight();                    xInScreen = event.getRawX();                    yInScreen = event.getRawY() - getStatusBarHeight();                    break;                case MotionEvent.ACTION_MOVE:                    xInScreen = event.getRawX();                    yInScreen = event.getRawY() - getStatusBarHeight();                    // 手指移动的时候更新悬浮窗的位置                    updateViewPosition();                    break;                case MotionEvent.ACTION_UP:                    // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。                    if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {                        Intent intent = new Intent(mContext, CleanActivity.class);                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                        mContext.startActivity(intent);                    }                    break;                default:                    break;            }        } catch (Exception e) {        }        return true;    }
这里我创建了一个管理悬浮窗的类,用来获取悬浮窗的显示状态,控制悬浮窗的显示和移除,并在悬浮小球上显示当前内存使用量,同样也用到了Handler类。代码如下:
public class FloatManager {    private FloatView floatView;    private LayoutParams floatParams;    private WindowManager windowManager;    private Context context;    private Handler handler = new Handler();    private boolean isFirst = true;    private long currentTime;    public FloatManager(Context context) {        this.context = context;    }    /**     * 判断悬浮窗状态     */    public void trigger(boolean isHome) {        try {            if (isHome && !isShowing()) {                handler.post(createRunnable);                handler.post(updateRunnable);                if (isFirst) {                    handler.post(alertRunnable);                    currentTime = System.currentTimeMillis();                    isFirst = false;                }            } else if (!isHome && isShowing()) {                handler.post(destroyRunnable);            } else if (isHome && isShowing()) {                handler.removeCallbacks(updateRunnable);                handler.post(updateRunnable);            }        } catch (Exception e) {            e.printStackTrace();        }    }    private Runnable createRunnable = new Runnable() {        @Override        public void run() {            try {                createWindow(context);            } catch (Exception e) {            }        }    };    private Runnable destroyRunnable = new Runnable() {        @Override        public void run() {            try {                handler.removeCallbacks(updateRunnable);                removeWindow(context);            } catch (Exception e) {            }        }    };    private Runnable updateRunnable = new Runnable() {        @Override        public void run() {            try {                updateUsedPercent(context);            } catch (Exception e) {            }        }    };    /**     * 每1秒判断一次,是否满足内存占用大于80%,时间间隔至少30分钟,且在桌面,则弹出吐司提示用户清理     */    private Runnable alertRunnable = new Runnable() {        @Override        public void run() {            try {                long tmp = System.currentTimeMillis();                long time = tmp - currentTime;                if (MemoryManager.getUsedPercentValue(context) >= 80 && time >= 1000 * 60 * 30 && isShowing()) {                    Toast.makeText(context, "Mobile phone need to clean up!", Toast.LENGTH_LONG).show();                    currentTime = tmp;                }                handler.postDelayed(alertRunnable, 1000);            } catch (Exception e) {            }        }    };    /**     * 创建一个悬浮窗。初始位置为屏幕的右部中间位置     */    public void createWindow(Context context) {        try {            WindowManager windowManager = getWindowManager(context);            int screenWidth = windowManager.getDefaultDisplay().getWidth();            int screenHeight = windowManager.getDefaultDisplay().getHeight();            if (floatView == null) {                floatView = new FloatView(context);                if (floatParams == null) {                    floatParams = new LayoutParams();                    floatParams.type = LayoutParams.TYPE_PHONE;                    floatParams.format = PixelFormat.RGBA_8888;                    floatParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL                            | LayoutParams.FLAG_NOT_FOCUSABLE;                    floatParams.gravity = Gravity.LEFT | Gravity.TOP;                    floatParams.width = FloatView.viewWidth;                    floatParams.height = FloatView.viewHeight;                    floatParams.x = screenWidth;                    floatParams.y = screenHeight / 2;                }                floatView.setParams(floatParams);                windowManager.addView(floatView, floatParams);            }        } catch (Exception e) {        }    }    /**     * 将悬浮窗从屏幕上移除     */    public void removeWindow(Context context) {        try {            if (floatView != null) {                WindowManager windowManager = getWindowManager(context);                windowManager.removeView(floatView);                floatView = null;            }        } catch (Exception e) {        }    }    /**     * 更新悬浮窗的TextView上的数据,显示内存使用的百分比。     */    public void updateUsedPercent(Context context) {        try {            if (floatView != null) {                TextView percentView = (TextView) floatView.findViewById(R.id.float_percent);                percentView.setText(MemoryManager.getUsedPercentValue(context) + "%");            }        } catch (Exception e) {        }    }    /**     * 是否有悬浮窗显示在屏幕上。     */    public boolean isShowing() {        return floatView != null;    }    private WindowManager getWindowManager(Context context) {        try {            if (windowManager == null) {                windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);            }        } catch (Exception e) {        }        return windowManager;    }}

2、水波球

这是一个自定义View,由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。measure操作主要用于计算视图的大小,即视图的宽度和长度。layout操作用于设置视图在屏幕中显示的位置。draw操作利用前两部得到的参数,将视图显示在屏幕上。想实现标准正余弦水波纹,可以用具体函数模拟出具体的轨迹。核心代码如下:
    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        try {            canvas.drawPath(blowWavePath, blowWavePaint);            canvas.drawPath(aboveWavePath, aboveWavePaint);            Paint mPaint = new Paint();            mPaint.setColor(Color.rgb(64, 64, 64));            mPaint.setAntiAlias(true);// 抗锯齿            // 绘制空心圆矩形            canvas.save();            Path path = new Path();            path.reset();            path.setFillType(Path.FillType.EVEN_ODD);            mPaint.setStyle(Paint.Style.FILL);            RectF rectF = new RectF();            rectF.set(0, 0, getWidth(), getHeight());            path.addOval(rectF, Path.Direction.CCW);            rectF.set(0, 0, getHeight(), getBottom());            path.addRoundRect(rectF, 0, 0, Path.Direction.CW);            canvas.drawPath(path, mPaint);            canvas.restore();            // 定义画笔2            Paint paint2 = new Paint();            // 消除锯齿            paint2.setAntiAlias(true);            // 设置画笔的颜色            paint2.setColor(Color.GRAY);            paint2.setStrokeWidth(mStokeWidth);            paint2.setStyle(Paint.Style.STROKE);            // 画一个空心圆            canvas.drawCircle((float) ((getWidth() >> 1)), (float) (getHeight() >> 1),                    (float) ((getWidth() >> 1) - (paint2.getStrokeWidth()) / 2), paint2);        } catch (Exception e) {        }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        try {            setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));        } catch (Exception e) {        }    }    private int measure(int measureSpec, boolean isWidth) {        int result = 0;        try {            int mode = MeasureSpec.getMode(measureSpec);            int size = MeasureSpec.getSize(measureSpec);            int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();            if (mode == MeasureSpec.EXACTLY) {                result = size;            } else {                result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();                result += padding;                if (mode == MeasureSpec.AT_MOST) {                    if (isWidth) {                        result = Math.max(result, size);                    } else {                        result = Math.min(result, size);                    }                }            }        } catch (Exception e) {        }        return result;    }    /**     * 计算波动轨迹     */    private void calculatePath() {        try {            aboveWavePath.reset();            blowWavePath.reset();            getWaveOffset();            aboveWavePath.moveTo(0, getHeight());            for (float i = 0; x_zoom * i <= getRight() + max_right; i += offset) {                aboveWavePath.lineTo((x_zoom * i), (float) (y_zoom * Math.cos(i + aboveOffset)) + waveToTop);            }            aboveWavePath.lineTo(getRight(), getHeight());            blowWavePath.moveTo(0, getHeight());            for (float i = 0; x_zoom * i <= getRight() + max_right; i += offset) {                blowWavePath.lineTo((x_zoom * i), (float) (y_zoom * Math.cos(i + blowOffset)) + waveToTop);            }            blowWavePath.lineTo(getRight(), getHeight());        } catch (Exception e) {        }    }
效果如下:
   
这是清理内存之前和之后的截图,该Activity上面一半是透明的布局。这里我创建了两层水波,更加生动,设置了当内存使用量>=70%的时候,颜色为红色,<70%的时候为蓝色。这里我把获取内存和清理内存的方法贴出来:
    /**     * 获取当前可用内存     */    private static long getAvailableMemory(Context context) {        long ret = 0L;        try {            ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);            manager.getMemoryInfo(memoryInfo);            ret = memoryInfo.availMem;        } catch (Exception e) {        }        return ret;    }    /**     * 获取总共内存     */    public static long getTotalMemory() {        long totalMemorySize = 0L;        try {            String dir = "/proc/meminfo";            FileReader fr = new FileReader(dir);            BufferedReader br = new BufferedReader(fr, 2048);            String memoryLine = br.readLine();            String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));            br.close();            totalMemorySize = Long.parseLong(subMemoryLine.replaceAll("\\D+", "")) * 1024;        } catch (Exception e) {            e.printStackTrace();        }        return totalMemorySize;    }    /**     * 获取正在运行的进程数     */    public static int getRunningProcess(Context context) {        int ret = 0;        try {            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);            ret = manager.getRunningAppProcesses().size();        } catch (Exception e) {        }        return ret;    }    /**     * 清理内存,返回释放的内存     */    public static long clearMemory(Context context) {        long beforeMem = 0L;        long afterMem = 0L;        try {            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);            List<ActivityManager.RunningAppProcessInfo> list = manager.getRunningAppProcesses();            //清理之前的可用内存            beforeMem = getAvailableMemory(context);            if (list != null) {                for (ActivityManager.RunningAppProcessInfo info : list) {                    if (info.importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE) {                        for (String pkg : info.pkgList) {                            Log.d(TAG, "kill package: " + pkg);                            manager.killBackgroundProcesses(pkg);                        }                    }                }            }            //清理之后的可用内存            afterMem = getAvailableMemory(context);        } catch (Exception e) {        }        return afterMem - beforeMem;    }
清理内存的时候,小球的水面是先下降到0,然后再上升到当前内存使用量的位置,变化的速度可以自己设置,代码我就不贴了,文末会把完整的源码给大家。

3、功能开关

这个部分兼容性比较麻烦,因为不同的手机设置可能不太一样,我这里用的测试机是红米Note,像这类定制的系统权限比较多,所以GPS的开关我是直接调用系统的设置界面,我尽量适配大部分机型。
首先,实现这些开关都需要权限,我先把AndroidMainfest.xml中的权限列出来:
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />    <uses-permission android:name="android.permission.INTERNET" />    <!--移动数据流量-->    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />    <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />    <!--亮度-->    <uses-permission android:name="android.permission.WRITE_SETTINGS" />    <uses-permission android:name="android.permission.DEVICE_POWER" />    <!--GPS-->    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />    <!--wifi-->    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />    <!--蓝牙-->    <uses-permission android:name="android.permission.BLUETOOTH" />    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />    <!--闪光灯-->    <uses-permission android:name="android.permission.CAMERA" />    <!--清理后台程序-->    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />    <uses-permission android:name="android.permission.GET_TASKS" />

(1)手电筒

这里我把手电筒状态的保存和设置手电筒状态的方法写在了MyApplication中,因为如果打开了手电筒,当退出这个页面的时候,手电筒应该还是亮着的,所以就必须保存在Application中。代码如下:
    /**     * 判断手电筒是否开启     */    public static boolean isLightOpen() {        boolean isLightOpen = false;        try {            if (camera == null) {                camera = Camera.open();            }            params = camera.getParameters();            isLightOpen = params.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH) ? true : false;        } catch (Exception e) {        }        return isLightOpen;    }    /**     * 打开手电筒     */    public static void openLight(Context context) {        try {            PackageManager pm = context.getPackageManager();            FeatureInfo[] features = pm.getSystemAvailableFeatures();            for (FeatureInfo f : features) {                if (PackageManager.FEATURE_CAMERA_FLASH.equals(f.name))   //判断设备是否支持闪光灯                {                    params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);                    camera.setParameters(params);                    camera.startPreview(); // 开始亮灯                }            }        } catch (Exception e) {        }    }    /**     * 关闭手电筒     */    public static void closeLight() {        try {            if (camera != null) {                params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);                camera.setParameters(params);                camera.stopPreview(); // 关掉亮灯                camera.release(); // 关掉照相机                camera = null;            }        } catch (Exception e) {        }    }

(2)WIFI

Wifi开关由WifiManager这个类控制实现。这里我注册了一个广播接收者,监听WIFI的状态变化,当Wifi开关改变时,系统会向外界发送广播android.net.wifi.WIFI_STATE_CHANGED;核心代码如下:
//注册监听wifi状态的广播接收者      wifiReceiver = new WifiReceiver();      IntentFilter wififilter = new IntentFilter();      wififilter.setPriority(2147483647);      wififilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);      registerReceiver(wifiReceiver, wififilter);      bt_wifi.setOnClickListener(new View.OnClickListener() {          @Override          public void onClick(View v) {              try {                  if (mWifiManager.isWifiEnabled()) {                      mWifiManager.setWifiEnabled(false);                  } else {                      mWifiManager.setWifiEnabled(true);                  }              } catch (Exception e) {              }          }      });

(3)数据流量

移动数据流量由ConnectivityManager类控制实现,这个类实现设置和获取移动流量状态的方法是隐藏的,所以我们只能通过反射来实现。点击这个开关的时候我会先判断当前的数据流量是否可用,即有没有SIM卡,这里我用的测试机没有安装SIM卡,所以点击的时候弹了一个吐司“数据流量不可用”,这也体现了代码的严谨性和健壮性。核心代码如下:
    /**     * 设置数据网络开关     */    public boolean changeNetConnection(Context context, boolean on) {        try {            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.FROYO) {                Method dataConnSwitchmethod;                TelephonyManager telephonyManager = (TelephonyManager) context                        .getSystemService(Context.TELEPHONY_SERVICE);                Class<?> telephonyManagerClass = Class.forName(telephonyManager                        .getClass().getName());                Method getITelephonyMethod = telephonyManagerClass                        .getDeclaredMethod("getITelephony");                getITelephonyMethod.setAccessible(true);                Object ITelephonyStub = getITelephonyMethod                        .invoke(telephonyManager);                Class<?> ITelephonyClass = Class.forName(ITelephonyStub                        .getClass().getName());                if (on) {                    dataConnSwitchmethod = ITelephonyClass                            .getDeclaredMethod("enableDataConnectivity");                } else {                    dataConnSwitchmethod = ITelephonyClass                            .getDeclaredMethod("disableDataConnectivity");                }                dataConnSwitchmethod.setAccessible(true);                dataConnSwitchmethod.invoke(ITelephonyStub);            } else {                final ConnectivityManager conman = (ConnectivityManager) context                        .getSystemService(Context.CONNECTIVITY_SERVICE);                final Class<?> conmanClass = Class.forName(conman.getClass()                        .getName());                final Field iConnectivityManagerField = conmanClass                        .getDeclaredField("mService");                iConnectivityManagerField.setAccessible(true);                final Object iConnectivityManager = iConnectivityManagerField                        .get(conman);                final Class<?> iConnectivityManagerClass = Class                        .forName(iConnectivityManager.getClass().getName());                final Method setMobileDataEnabledMethod = iConnectivityManagerClass                        .getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE);                setMobileDataEnabledMethod.setAccessible(true);                setMobileDataEnabledMethod.invoke(iConnectivityManager, on);            }            return true;        } catch (Exception e) {        }        return false;    }

(4)蓝牙

蓝牙开关主要调用BluetoothAdapter相关方法实现,蓝牙有四种状态:正在打开、打开、正在关闭、关闭。蓝牙状态改变,系统向外界发送广播android.bluetooth.adapter.action.STATE_CHANGED或android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED;核心代码如下:
//蓝牙开关       mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();       updateBluetooth();       //注册监听蓝牙状态的广播接收者       blueToothReceiver = new BlueToothReceiver();       IntentFilter bluefilter = new IntentFilter();       bluefilter.setPriority(2147483647);       bluefilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);       registerReceiver(blueToothReceiver, bluefilter);       bt_bluetooth.setOnClickListener(new View.OnClickListener() {           @Override           public void onClick(View v) {               try {                   switch (getBluetoothStatus()) {                       case BluetoothAdapter.STATE_ON:                       case BluetoothAdapter.STATE_TURNING_ON:                           mBluetoothAdapter.disable();                           break;                       case BluetoothAdapter.STATE_OFF:                       case BluetoothAdapter.STATE_TURNING_OFF:                           mBluetoothAdapter.enable();                           break;                   }               } catch (Exception e) {               }           }       });

(5)GPS

这里我前面也提到了,直接跳转到系统的设置页面,startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
ok,主要内容就这些,大家有什么问题可以在留言里面提出来~
源码下载地址


5 0