天天看點

【Demo記錄】懸浮窗—通過服務顯示棧頂app包名到懸浮窗

1、 懸浮窗的基本操作

  1. 1) 建立懸浮窗
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

if (floatView == null) {
    //設定懸浮窗的ui 
View floatView = LayoutInflater.from(this).inflate(R.layout.float_window_text, null);
    //設定懸浮窗的參數
    params = new WindowManager.LayoutParams();
    //使用 TYPE_SYSTEM_ALERT 需要有使用者選擇app 進行權限申請
    //使用Toast方式,不需要權限
    params.type = WindowManager.LayoutParams.TYPE_TOAST;
    params.format = PixelFormat.RGBA_8888;
    params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    params.gravity = Gravity.LEFT | Gravity.TOP;
    params.width = WindowManager.LayoutParams.WRAP_CONTENT;
    params.height = WindowManager.LayoutParams.WRAP_CONTENT;

    windowManager.addView(floatView,params);//建立懸浮窗
}
           
  1. 2) 更新懸浮窗

修改View 或者參數之後進行更新

windowManager.updateViewLayout(floatView, params);
           
  1. 3) 移除懸浮窗

2、 懸浮窗與Activity

在Activity中對懸浮窗進行操作,遇到了一些列問題。

原:通過下列界面來控制懸浮窗

【Demo記錄】懸浮窗—通過服務顯示棧頂app包名到懸浮窗

模拟器(版本号17)運作效果

在開啟懸浮窗時銷毀activity,懸浮窗一起銷毀了。再開啟activity進行操作,就是重新開始。

樂視2(android 6.0)運作效果

在開啟懸浮窗時銷毀activity,懸浮窗沒有一起銷毀,再開啟activity進行操作,不能關閉原來的懸浮窗,activity 重新開始,不能對原來的懸浮窗進行操作。

懸浮窗顯示效果分析:在activity中建立懸浮窗,懸浮窗沒有被銷毀,當重新進入activity的時候,activity擷取到的懸浮窗對象并不是之前的懸浮窗對象,對之前的懸浮窗已經失去了可控性。而想要保持對懸浮窗的可控性,就需要将懸浮窗與activity的生命周期綁定,懸浮窗和建立它的這個activity共存亡。

3、 懸浮窗與服務

場景:在activity中開啟服務,使用懸浮窗顯示手機棧頂app所在的應用包名。

如何擷取棧頂app所在的應用包名—另一篇demo中記錄。

Service代碼:

/**
 * 自定義服務  時刻記錄手機的棧頂activity 所在包  
* 并将包名顯示到懸浮窗 服務停止的時候關閉懸浮窗
 */
public class TopAppService extends Service {
    private Timer timer;
    private WindowManager windowManager;
    private View floatView;
    private WindowManager.LayoutParams params;
    private boolean isFirst;
    private TextView tvFloatWindow;
    Handler handler = new Handler() {
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        initFloatWindow();
        isFirst = true;//預設是第一次
        if (timer == null) {
            timer = new Timer();
            timer.scheduleAtFixedRate(new FloatWindowTimer(), , );//每隔5s 執行一次
        }
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 擷取棧頂app的應用包名,顯示到懸浮窗
     *
     * @param topApp 棧頂app的應用包名
     */
    private void uploadFloatWindow(final String topApp) {
        //第一次執行時,建立懸浮窗,後面的操作,隻對懸浮窗進行更新
        if (isFirst) {
            //建立懸浮窗
            handler.post(new Runnable() {
                @Override
                public void run() {
                    tvFloatWindow.setText(topApp);
                    windowManager.addView(floatView, params);
                }
            });
            isFirst = false;
        } else {
            //更新懸浮窗
            handler.post(new Runnable() {
                @Override
                public void run() {
                    tvFloatWindow.setText(topApp);
                    windowManager.updateViewLayout(floatView, params);
                }
            });
        }
    }

    /**
     * 初始化FloatWindow
     */
    private void initFloatWindow() {
        windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

        if (floatView == null) {
            floatView = LayoutInflater.from(this).inflate(R.layout.float_window_text, null);
            params = new WindowManager.LayoutParams();
            //使用 TYPE_SYSTEM_ALERT 需要有使用者選擇app 進行權限申請
            //使用Toast方式,不需要權限
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.format = PixelFormat.RGBA_8888;
            params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            params.gravity = Gravity.LEFT | Gravity.TOP;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        }
        tvFloatWindow = (TextView) floatView.findViewById(R.id.tv_float_window);
    }

    /**
     * 判斷  使用者檢視曆史記錄的權利是否給予app(擷取棧頂app的權限)
     *
     * @return
     */
    private boolean isUseGranted() {
        Context appContext = MyApplication.getAppContext();
        AppOpsManager appOps = (AppOpsManager) appContext
                .getSystemService(Context.APP_OPS_SERVICE);
        int mode = -;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
            mode = appOps.checkOpNoThrow("android:get_usage_stats",
                    android.os.Process.myUid(), appContext.getPackageName());
        }
        boolean granted = mode == AppOpsManager.MODE_ALLOWED;
        return granted;
    }

    /**
     * 高版本:擷取頂層的activity的包名
     *
     * @return
     */
    private String getHigherPackageName() {
        String topPackageName = "";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

            UsageStatsManager mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);

            long time = System.currentTimeMillis();
            // We get usage stats for the last 10 seconds
            //time - 1000 * 1000, time 開始時間和結束時間的設定,在這個時間範圍内 擷取棧頂Activity 有效
            List<UsageStats> stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time -  * , time);
            // Sort the stats by the last time used
            if (stats != null) {
                SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
                for (UsageStats usageStats : stats) {
                    mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
                }
                if (mySortedMap != null && !mySortedMap.isEmpty()) {
                    topPackageName = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
                    Log.e("TopPackage Name", topPackageName);
                }
            }
        }
        return topPackageName;
    }

    /**
     * 低版本:擷取棧頂app的包名
     *
     * @return
     */
    private String getLowerVersionPackageName() {
        String topPackageName;//低版本  直接擷取getRunningTasks
        ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        ComponentName topActivity = activityManager.getRunningTasks().get().topActivity;
        topPackageName = topActivity.getPackageName();
        return topPackageName;
    }

    /**
     * 計時器:擷取棧頂app的應用包名顯示到懸浮窗
     */
    class FloatWindowTimer extends TimerTask {
        public void run() {
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
                boolean useGranted = isUseGranted();
                Log.e("TopAppService", "檢視曆史記錄權限 是否允許授權=" + useGranted);
                if (useGranted) {
                    String topApp = getHigherPackageName();
                    uploadFloatWindow(topApp);
                } else {
                    //開啟應用授權界面
                    Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);
                }
            } else {
                String topApp = getLowerVersionPackageName();
                uploadFloatWindow(topApp);
            }
        }
    }
    @Override
    public void onDestroy() {
           super.onDestroy();
          //如果懸浮窗是顯示的  需要移除懸浮窗
          timer.cancel();
          timer = null;
          windowManager.removeView(floatView);
    }
}
           

模拟器(版本号17)效果:

【Demo記錄】懸浮窗—通過服務顯示棧頂app包名到懸浮窗

樂視2 (android 6.0)效果:

【Demo記錄】懸浮窗—通過服務顯示棧頂app包名到懸浮窗

4、 Demo下載下傳位址

http://download.csdn.net/detail/u012391876/9716855