【笔记】Android悬浮窗使用兼容6.0

一直都感觉悬浮窗是个非常好用的东西,无论在什么界面上都能显示不受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);
    }
}

源码上传


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值