如何在Java中实现SYSTEM_ALERT_WINDOW应用时长提醒悬浮弹窗
Hey there! I see you've already tackled the SYSTEM_ALERT_WINDOW permission part (quick note: I spotted a tiny logic flip in your onCreate method—right now you're requesting permission only if you already have it; I'll fix that at the end!). Let's walk through building the floating popup and usage monitoring logic step by step, perfect for a new Android dev.
1. Create the Floating Popup Layout
First, make a custom layout for your popup. Create a new XML file in res/layout called layout_floating_popup.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#ffffff" android:orientation="vertical" android:padding="16dp" android:elevation="8dp" android:layout_gravity="center"> <TextView android:id="@+id/popup_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Time's Up!" android:textSize="18sp" android:textStyle="bold"/> <TextView android:id="@+id/popup_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="You've been using this app for X minutes. Take a break!" android:layout_marginTop="8dp"/> <Button android:id="@+id/btn_close" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Got it" android:layout_marginTop="16dp" android:layout_gravity="end"/> </LinearLayout>
2. Build a Service to Manage the Floating Popup
Floating windows need to run independently of your Activity, so we'll use a Service to handle showing/hiding the popup. Create a new class FloatingPopupService.java:
public class FloatingPopupService extends Service { private WindowManager windowManager; private View floatingView; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); // Initialize WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); // Inflate the popup layout floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_popup, null); // Configure WindowManager LayoutParams WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, // For Android 8.0+ use TYPE_APPLICATION_OVERLAY Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, // Flags to make the popup non-focusable (so user can interact with apps behind it) WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT ); // Set popup position (center screen here) params.gravity = Gravity.CENTER; // Add the view to WindowManager windowManager.addView(floatingView, params); // Handle close button click Button closeBtn = floatingView.findViewById(R.id.btn_close); closeBtn.setOnClickListener(v -> { // Remove the popup and stop the service stopSelf(); }); } @Override public void onDestroy() { super.onDestroy(); // Clean up the view when service stops if (floatingView != null) { windowManager.removeView(floatingView); } } }
Don't forget to add this service to your AndroidManifest.xml:
<service android:name=".FloatingPopupService" android:exported="false"/>
3. Implement App Usage Monitoring
To track continuous app usage, we'll use UsageStatsManager. First, add the required permission to your manifest:
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions"/>
3.1 Request Usage Stats Permission
Add this method to your ExcessiveUsageActivity to request the usage stats permission:
private void requestUsageStatsPermission() { Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); startActivity(intent); }
3.2 Monitor App Usage Duration
Add a helper method to check if the user has been using the target app for X minutes. We'll use a Handler to check periodically:
private Handler usageMonitorHandler = new Handler(); private Runnable usageMonitorRunnable = new Runnable() { @Override public void run() { String targetPackage = "com.target.app.package"; // Replace with your target app's package long thresholdMillis = 10 * 60 * 1000; // 10 minutes (adjust as needed) if (isAppUsedContinuously(targetPackage, thresholdMillis)) { // Show the floating popup startService(new Intent(ExcessiveUsageActivity.this, FloatingPopupService.class)); // Stop monitoring once popup is shown (or adjust to repeat) usageMonitorHandler.removeCallbacks(this); } else { // Check again in 1 minute usageMonitorHandler.postDelayed(this, 60 * 1000); } } }; private boolean isAppUsedContinuously(String targetPackage, long thresholdMillis) { UsageStatsManager usageStatsManager = (UsageStatsManager) getSystemService(USAGE_STATS_SERVICE); long endTime = System.currentTimeMillis(); long startTime = endTime - thresholdMillis; // Get usage stats for the time range List<UsageStats> usageStatsList = usageStatsManager.queryUsageStats( UsageStatsManager.INTERVAL_DAILY, startTime, endTime ); if (usageStatsList != null) { for (UsageStats stats : usageStatsList) { if (stats.getPackageName().equals(targetPackage)) { // Check if total time in foreground meets the threshold return stats.getTotalTimeInForeground() >= thresholdMillis; } } } return false; }
4. Fix Your Existing Permission Logic
You had a small logic error in your onCreate method—you were requesting overlay permission only if you already had it. Let's fix that:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_excessiveusageactivity); result = findViewById(R.id.result); // Move this here to avoid null pointer // Request overlay permission if not granted if (!Settings.canDrawOverlays(this)) { Intent requestOverlayPermissionIntent = new Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()) ); startActivityForResult(requestOverlayPermissionIntent, REQUEST_OVERLAY_PERMISSION); result.setText("Requesting overlay permission..."); } else { // Check if we have usage stats permission if (!hasUsageStatsPermission()) { requestUsageStatsPermission(); result.setText("Requesting usage stats permission..."); } else { // Start monitoring app usage usageMonitorHandler.post(usageMonitorRunnable); result.setText("Monitoring app usage..."); } } } // Helper method to check if usage stats permission is granted private boolean hasUsageStatsPermission() { AppOpsManager appOps = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); int mode = appOps.checkOpNoThrow( AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), getPackageName() ); return mode == AppOpsManager.MODE_ALLOWED; }
5. Test and Polish
- Test Permissions: Make sure you grant both overlay and usage stats permissions when prompted.
- Adjust Threshold: Change
thresholdMillisto your desired duration (e.g., 30 minutes =30 * 60 * 1000). - Foreground Service: For Android 8.0+, if your service runs in the background for long periods, you'll need to make it a Foreground Service with a notification to avoid being killed.
- Popup Customization: Tweak the
layout_floating_popup.xmlto match your app's design (colors, icons, etc.).
内容的提问来源于stack exchange,提问作者confusedcoder

