一直都感觉悬浮窗是个非常好用的东西,无论在什么界面上都能显示不受activity等限制。但是随着Android版本的不断升级,使用却变得越来越麻烦。
坑1、3.0之后获取正在运行的任务需要声明权限了;
在AndroidManifest.xml中声明权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
坑2、5.0之后原先获取正在运行任务的getRunningTasks被废弃了,可以使用usage代替,但查看应用使用情况需要手动开启权限了;
如果想实现类似于360悬浮窗的效果,只在当前界面是桌面的时候才显示悬浮窗,在其它界面情况下不显示,那我们需要判断当前是否是在桌面。5.0之后可以通过UsageStatsManager获取一段时间内运行的程序,我们可以通过这来判断当前是否是在桌面上。
/** * 获取所有桌面应用 * * @param context * @return */ public static List<String> getHomeList(Context context) { List<String> list = new ArrayList<String>(); PackageManager packageManager = context.getPackageManager(); Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resolveInfoList) { list.add(resolveInfo.activityInfo.packageName); } Log.i("homeList",list.toString()); return list; } /** * 当前界面是否是桌面 * 分5.0之前和之后版本 * * @param context * @return */ public static boolean isHome(Context context) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); String packname = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //5.0之后getRunningTasks废弃,引入usage //<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" /> UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); long currentTime = System.currentTimeMillis(); List<UsageStats> usageStatsList = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, currentTime - 2000, currentTime); if (usageStatsList != null || usageStatsList.isEmpty()) { SortedMap<Long, UsageStats> usageStatsSortedMap = new TreeMap<Long, UsageStats>(); for (UsageStats usageStats : usageStatsList) { usageStatsSortedMap.put(usageStats.getLastTimeUsed(), usageStats); } if (!usageStatsSortedMap.isEmpty()) { packname = usageStatsSortedMap.get(usageStatsSortedMap.lastKey()).getPackageName(); } } } else { //5.0之前可直接使用getRunningTasks //<uses-permission android:name="android.permission.GET_TASKS"/> List<ActivityManager.RunningTaskInfo> runningTaskInfoList = activityManager.getRunningTasks(1); packname = runningTaskInfoList.get(0).topActivity.getPackageName(); } Log.i("isHome", packname == null ? "null" : packname); return getHomeList(context).contains(packname); }在5.0之后我获取了当前时间之前2秒到当前时间运行在界面上的应用程序,但是我发现,如果我停留在一个界面超过2秒,我获取到的应用使用列表为空,我就无法获取最近一次使用的程序的包名。想了个非常傻的方法,另外又加了一个获取最近2秒内运行在界面上的程序包名的方法,如果一直停留就返回null,在后台服务判断当前是否在桌面运行时多加一个判断。
/** * 获取最近2秒内开始运行在界面上的程序包名 * 若最近2秒一直停留在某一界面,则返回null * @param context * @return */ public static String getLatestPackage(Context context){ ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); String packname = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //5.0之后getRunningTasks废弃,引入usage //<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" /> UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); long currentTime = System.currentTimeMillis(); List<UsageStats> usageStatsList = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, currentTime - 2000, currentTime); if (usageStatsList != null || usageStatsList.isEmpty()) { SortedMap<Long, UsageStats> usageStatsSortedMap = new TreeMap<Long, UsageStats>(); for (UsageStats usageStats : usageStatsList) { usageStatsSortedMap.put(usageStats.getLastTimeUsed(), usageStats); } if (!usageStatsSortedMap.isEmpty()) { packname = usageStatsSortedMap.get(usageStatsSortedMap.lastKey()).getPackageName(); } } } else { //5.0之前可直接使用getRunningTasks //<uses-permission android:name="android.permission.GET_TASKS"/> List<ActivityManager.RunningTaskInfo> runningTaskInfoList = activityManager.getRunningTasks(1); packname = runningTaskInfoList.get(0).topActivity.getPackageName(); } Log.i("latest",packname==null?"null":packname); return packname; }
/** * 后台检测刷新任务 */ class RefreshTask extends TimerTask { @TargetApi(Build.VERSION_CODES.M) @Override public void run() { if(AppUtil.getLatestPackage(context)==null){ //最近运行的app包名为null执行刷新小悬浮窗操作 handler.post(new Runnable() { @Override public void run() { //已显示悬浮窗,更新小悬浮窗 FloatWindowManager.getInstance(context).updateFloatWindowSmall(); } }); return; } if(AppUtil.isHome(context)){ //当前在桌面上则显示或更新悬浮窗 if (FloatWindowManager.getInstance(context).isFloatWindowShowing()) { handler.post(new Runnable() { @Override public void run() { //已显示悬浮窗,更新小悬浮窗 FloatWindowManager.getInstance(context).updateFloatWindowSmall(); } }); } else if (!FloatWindowManager.getInstance(context).isFloatWindowShowing()) { handler.post(new Runnable() { @Override public void run() { //未显示悬浮窗,则显示小悬浮窗 FloatWindowManager.getInstance(context).showFloatWindowSmall(context); } }); } }else{ //当前不在桌面则关闭所有悬浮窗 handler.post(new Runnable() { @Override public void run() { FloatWindowManager.getInstance(context).closeAllFloatWindow(); } }); } } }
关于如何开启查看应用使用情况的权限,我们也只能跳到设置界面让用户手动去开启
/** * 授权查看应用使用情况权限的点击事件 * @param view */ public void authorizationUsage(View view){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)); } else { Toast.makeText(this, "只有5.0以上手机需要查看应用使用情况权限", Toast.LENGTH_SHORT).show(); } }
坑3、6.0之后悬浮窗权限被默认关闭,需要手动开启了;
Google在Android6.0中收紧了悬浮窗口的权限,因为大量App使用悬浮窗口的目的和当初Google预想的大相径庭,Google当初只是想让悬浮窗口被老老实实拿来做用户通知,但这个特性成为了令人眼花缭乱甚至会影响使用的交互工具。因此,Google在Android6.0中默认禁止了悬浮窗口(FloatWindow)的权限,并让它不能被轻易打开。我们需要像开启查看应用使用情况的权限一样,跳转至设置界面让用户手动开启。
/** * 授权悬浮窗的点击事件 * @param view */ public void authorizationFloatWindow(View view) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)); } else { Toast.makeText(this, "只有6.0以上手机需要开启悬浮窗权限", Toast.LENGTH_SHORT).show(); } }
悬浮窗使用
踩完坑之后就是常规使用了,参考了郭神的博客。
/** * 小悬浮窗 * 简单的数字时钟,可点击打开大悬浮窗,可随意拖动位置 * * @author SJL * @date 2016/11/30 21:36 */ public class FloatWindowSmall extends LinearLayout { private Context context; private WindowManager windowManager; public WindowManager.LayoutParams layoutParams; public FloatWindowSmall(Context context) { super(context); this.context = context; init(); } private void init() { LayoutInflater.from(context).inflate(R.layout.float_window_small, this); windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); View view = findViewById(R.id.floatWindowSmallParent); layoutParams = new WindowManager.LayoutParams(); //设置宽高 layoutParams.width = view.getLayoutParams().width; layoutParams.height = view.getLayoutParams().height; //设置位置 layoutParams.x = AppUtil.getScreen(context).x - layoutParams.width; layoutParams.y = AppUtil.getScreen(context).y / 2 - layoutParams.height / 2; //设置交互 layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; //设置图片格式 layoutParams.format = PixelFormat.RGBA_8888; //设置对齐 layoutParams.gravity = Gravity.TOP | Gravity.LEFT; //设置显示类型 layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; update(); } public void update() { ((TextView) findViewById(R.id.tvTime)).setText(new SimpleDateFormat("HH:mm:ss", Locale.US).format(new Date())); } private float startX = -1; private float startY = -1; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); startY = event.getY(); break; case MotionEvent.ACTION_MOVE: layoutParams.x = (int) (event.getRawX() - startX); layoutParams.y = (int) (event.getRawY() - startY); windowManager.updateViewLayout(this, layoutParams); break; case MotionEvent.ACTION_UP: if (event.getX() == startX && event.getY() == startY) { openFloatWindowBig(); } break; } return super.onTouchEvent(event); } /** * 打开大悬浮窗 */ private void openFloatWindowBig() { FloatWindowManager.getInstance(context).closeFloatWindowSmall(); FloatWindowManager.getInstance(context).showFloatWindowBig(context); } }
作者:q1113225201 发表于2016/12/2 0:19:51 原文链接
阅读:36 评论:0 查看评论