首页 > 代码库 > 自定义水波球清理内存的悬浮窗小工具
自定义水波球清理内存的悬浮窗小工具
一、概述
现在一些手机管家都会有一个用来清理内存的悬浮窗小工具,感觉挺实用的,就自己做了一个。首先介绍一下这个工具的功能,除了可以清理内存,还有调节手机屏幕亮度、手电筒、无线网、移动数据、蓝牙、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个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。