I.概要
現在、一部の携帯電話執事は、メモリをクリーンアップするためのフローティングウィンドウガジェットを備えています。非常に実用的だと感じたので、自分で作成しました。最初にこのツールの機能を紹介します。メモリのクリーニングに加えて、電話の画面、懐中電灯、ワイヤレスネットワーク、モバイルデータ、Bluetooth、GPSスイッチの明るさを調整する機能も備えています。上の写真を撮って波を感じてください:
電話のメモリをきれいにする
いくつかの一般的な機能のスイッチ
2.機能実現
1.中断されたウィンドウ
MainActivityには、フローティングウィンドウの開閉を制御するボタンが2つしかありません。ここでは、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.removeCallbacks()は、ServiceのonDestory()メソッドで実行する必要があります。そうしないと、フローティングウィンドウを閉じることができません。
フローティングウィンドウの作成について説明しましょう。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.水波ボール
これは、3つの関数(measure()、layout()、およびdraw())によって完成するカスタムビューです。3つのサブメソッド:onMeasure()、onLayout()、およびonDraw()が含まれています。メジャー操作は主に、ビューのサイズ、つまりビューの幅と長さを計算するために使用されます。レイアウト操作は、画面上のビューの位置を設定するために使用されます。描画操作では、最初の2つの部分で取得したパラメーターを使用して、画面にビューを表示します。標準の正弦波および余弦波の波紋を実現したい場合は、特定の関数を使用して特定の軌道をシミュレートできます。コアコードは次のとおりです。
@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) {
}
}
効果は次のとおりです。
これは、メモリが消去される前と後のスクリーンショットです。アクティビティの半分は透明なレイアウトです。ここでは、より鮮やかな2層の水の波を作成し、メモリ使用量が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.機能スイッチ
互換性のこの部分は、携帯電話の設定が異なる可能性があるため、より厄介です。ここで使用したテストマシンはRedmi 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に設定する方法を記述します。これは、懐中電灯がオンになっている場合、このページを終了すると、懐中電灯がまだオンであるため、アプリケーションに保存する必要があるためです。コードは以下のように表示されます:
/**
* 判断手电筒是否开启
*/
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)Bluetooth
Bluetoothスイッチは、主にBluetoothAdapter関連のメソッドを呼び出すことによって実装されます。Bluetoothには、開く、開く、閉じる、閉じるの4つの状態があります。Bluetoothの状態が変化すると、システムはブロードキャスト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));
わかりました、それがメインのコンテンツです。質問がある場合はメッセージを残してください〜